Temas:
Observe que en (*) se necesita accesar el nombre de un objeto que
no es this. En este caso es necesario entonces colocar el
prefijo post. para diferenciarlo del acceso a las variables de
this.
class Post extends Program {
String ci;
String nombre;
Post(String ci, String nombre) {
this.ci= ci;
this.nombre= nombre;
}
Post(TextReader lect) {
if (lect.eofReached())
ci= null;
else {
ci= lect.readString();
nombre= lect.readString();
lect.skip();
}
}
void escribir(TextWriter escr) {
escr.print(ci);
escr.println(nombre);
}
int compararCon(Post post) {
return compare(nombre, post.nombre); // (*)
}
}
Al igual que en las variables de instancia, se puede suprimir this
e invocar orden2 simplemente con:
class Robot extends Program {
void orden1(...) {
...
this.orden2(...); // o también orden2(...);
...
}
void orden2(...) { ... }
...
}
El compilador agrega automáticamente el prefijo this.
orden2(...);
¿Cómo distingue Java que Tarea es un programa y que run y ordenar son
procedimientos? ¿No podría interpretar que Tarea es una clase de
objetos y que run y ordenar son los métodos de la clase?
class Tarea extends Program {
void run() {
...
ordenar(...);
...
}
void ordenar(int[] a, int n) {
...
}
...
}
La respuesta es que Java no lo distingue: Java no posee funciones ni procedimientos. En realidad Tarea sí es una clase como cualquier otra, y run y ordenar son métodos de la clase Tarea. Es perfectamente legal invocar:
De hecho, cuando no se pone, el compilador lo agrega automáticamente.
¿Y qué objeto referencia this en este caso? Es un objeto que
es creado por la clase Run. En realidad la forma estándar de invocar
programas en Java es:
this.ordenar(...);
Y no es obligatorio que la clase sea Run. Pero en este curso hemos estado colocando siempre la clase Run en primer lugar. Esta clase se encarga de crear un objeto de la clase que se suministra como argumento. Por ejemplo al invocar:
la clase Run pertenece a la biblioteca del curso. Ejecutará las siguientes instrucciones:
o simplemente new Tarea().run();. Es decir, crea un objeto
de la clase Run y luego invoca el método run.
Tarea tarea= new Tarea();
tarea.run();
Por lo tanto, dado que Java no posee funciones ni procedimientos, se simulan mediante métodos. El lector se preguntará por qué entonces se estudiaron los métodos como funciones y procedimientos cuando en realidad no existen.
Los lenguajes O-O se usan desde hace relativamente poco tiempo. Solo en los 90 se comenzaron a usar a nivel de empresas. En los 70 y 80 se usaban lenguajes tradicionales (no orientados a objetos). Estos lenguajes sólo ofrecen mecanismos para definir funciones, procedimientos y records. Por ejemplo Fortran, Pascal, Cobol y C.
Un record es la agrupación de un conjunto de datos en una sola estructura que puede ser manipulada como un solo dato o también a través de sus partes. Típicamente, en los lenguajes tradicionales, no era posible definir métodos asociados a los records y por lo tanto los records son un mecanismo de abstracción más débil que los objetos.
Uso de objetos como records
A pesar de que Java no posee un mecanismo para definir records, éstos se pueden simular mediante clases: basta accesar directamente las variables de instancia como se hizo en las clases de apoyo para almacenar la información contenida en una línea de un archivo.
En este punto, es importante mencionar la disyuntiva existente en la actualidad con respecto a la enseñanza de Computación utilizando un lenguaje O-O como primer lenguaje. Hay profesores que opinan que es conveniente enseñar métodos y clases desde un comienzo, sin pasar por funciones y procedimientos. Otros profesores, como el autor de este documento, opinan que resulta más sencillo comenzar por programación no orientada a objetos y por ello se han simulado las funciones y procedimientos con los métodos de Java.
Cuando se diseñan los métodos que necesita un clase, inevitablemente
se llega a casos en que no se necesita un objeto de la invocación.
Por ejemplo, podríamos agregar a la clase Tiempo un método horaActual
que entrega la hora del día.
t.sumar(t2);
Para este tipo de caso, Java ofrece los métodos estáticos. Este tipo de métodos no va asociado a ningún objeto. Se invocan colocando al lado izquierdo el nombre de la clase (y no un objeto):
Este tipo de método se define en la clase agregándoles el atributo static:
Tiempo ahora= Tiempo.horaActual();
Al programar métodos estáticos se debe tener mucho cuidado, porque
dado que no van asociados a un objeto, no se puede usar el identificador
this:
class Tiempo extends Program {
...
static Tiempo horaActual() {
... averiguar horas y minutos actuales ...
return new Tiempo(horas, minutos);
}
}
Explicación:
class Ejemplo extends Program {
int vari; // una variable de instancia
static void mets(String s) { // Un método estático
... s ... // se pueden usar argumentos
XXX this.vari XXX // no se puede usar this
XXX vari XXX // tampoco, porque esto equivale a this.vari
XXX meti() XXX // no se pueden invocar métodos normales
}
void meti() { // un método normal
mets("hola"); // se puede invocar Ejemplo.mets(...)
}
}
Si se tiene una referencia de un objeto de la clase Ejemplo en la variable e, entonces sí se puede accesar e.vari.
Si se trata de un método estático de otra clase, siempre hay que
colocar explícitamente el nombre de la clase.
mets(...); // o también Ejemplo.mets(...)
Por lo tanto, en esta parte veremos cuál es la forma estándar de escribir aplicaciones en Java. Es decir, sin recurrir a bibliotecas especiales.
Como se dijo previamente, la forma estándar de ejecutar programas en Java es por medio del comando:
sin especificar Run. En estos casos, el comando java busca algún método estático que se llame main y que reciba como argumento un arreglo de strings y lo invoca. Por ejemplo, un programa que despliega Hola Mundo en la pantalla se escribiría:
Observaciones:
class Jalisco {
static void main(String[] args) {
System.out.println("Hola Mundo");
}
}
Estas funciones son en realidad métodos estáticos de la clase Program. En una clase sólo son visibles cuando esa clase extiende Program.
System.out.println(...);
Cuando los proyectos son grandes, como para que el desarrollo de un programa abarque varios meses e involucre muchos programadores, la metodología puede fallar por falta de abstracción por parte de algunos programadores. Para entender mejor el problema examinaremos el siguiente escenario: un programador requiere realizar una operación con un objeto, pero esta operación no ha sido definida en la clase a la que pertenece ese objeto, aunque se puede llevar a cabo con la información que se mantiene en las variables de instancia.
Como normalmente los programadores pueden examinar el código que ha escrito otro programador, el programador que usa una clase se ve tentado por realizar la operación accesando directamente las variables de instancia del objeto. Este acceso podría ocurrir en cualquier parte del programa completo. Este es un atajo que permite resolver rápidamente el problema. Sin embargo, más tarde el programador que ha implementado la clase podría decidir introducir cambios en las variables de instancia, provocando que el código del primer programador deje de funcionar.
Si se abusa de este tipo de atajos en un programa de gran envergadura, éste se puede tornar incomprensible para todo el grupo. Simplemente, ningún programador puede modificar el código que ya escribió, porque esto haría que el todo deje de funcionar.
La forma correcta de proceder en estos casos es agregar la operación a la clase y en lo posible accesar las variables de instancia en la misma clase, de modo que si se introducen modificaciones en las variables de instancia, será evidente que hay que cambiar el nuevo método, gracias a su cercanía con el resto de los métodos. Una de las gracias del mecanismo de clases es que permite concentrar todos los posibles usos de las variables de instancia en un solo archivo, evitando así que queden diseminados por todas partes. La única forma de operar con las variables de instancia debería ser por medio de los métodos de la clase.
La modificación de una clase la puede realizar tanto el programador que usa la clase como el que la definió, pero lo importante es que la operación quede definida en la misma clase en donde se han declarado las variables de instancia.
Con este atributo, si un programador quisiera accesar las variables
de instancia horas y min fuera de la clase, se producirá un error
de compilación:
class Tiempo extends Program {
private int horas;
private int min;
...
}
De esta forma, a futuro se podrá cambiar la representación de los
objetos de la clase Tiempo, asegurándose que ninguna clase usuaria
de Tiempo se verá afectada por el cambio.
class Uso extends Program {
void run() {
Tiempo t= new Tiempo(10, 20);
t.horas= -4; // error en tiempo de compilación
...
}
}
Para ello Java ofrece el mecanismo de encapsulación por medio de paquetes de clases. La idea es que fuera de un paquete de clases sólo es posible accesar las variables de instancia y métodos que hayan sido definidos con el atributo public. Los programadores del paquete de clases especifican como públicas sólo aquellas clases y operaciones contempladas para los usuarios del paquete.
A modo de ejemplo, supongamos que se desea empaquetar las clases para realizar animaciones en un paquete llamado anim. Para lograr esto será necesario indicar al comienzo de cada archivo que las clases contenidas pertenecen al paquete anim:
Luego, se definen las clases señalando las clases y métodos que son públicas.
Es decir que se pueden usar fuera del paquete:
package anim;
Observe que sólo se declaran públicos los métodos getX, getY y moveTo,
por lo que podrán ser usados fuera del paquete (o desde otros paquetes).
En cambio el método draw no es público y por lo tanto no puede
ser usado por clases que están fuera del paquete anim.
package anim;
public class Glyph extends Program {
int x, y;
...
public int getX() { ... }
public int getY() { ... }
public void moveTo(int x, int y) { ... }
void draw(Pizarra p) { ... }
}
Aunque un programador puede examinar el código y saber que existe
el método draw y las variables de instancia x, y y p, no podrá
utilizarla porque el compilador se lo impedirá, entregando
un mensaje de error en caso que se usen fuera del paquete.
package anim;
public class Animator extends Program {
Pizarra p;
public void drawAndSleep(...) {
...
gl.draw(p);
...
}
...
}
La variables de instancia pueden ser declaradas como públicas pero un buen estilo de programación es que no lo sean. Si se requiere conocer su valor desde fuera del paquete, se definen métodos que entregan su valor (como getX y getY). Pero como no hay método para obtener la pizarra asociada a un objeto de la clase Animator, no será posible dibujar cosas distintas de un glyph en esa pizarra.
Observación:
import anim.*;
...
Como no es un objetivo de este curso que los alumnos realicen programas de gran envergadura como para que se hagan necesarios los mecanismos de encapsulación, no seguiremos profundizando al respecto. Sólo utilizaremos la palabra public a título de comentario para señalar que un método puede ser invocado por los usuarios de la clase.
Debe quedar claro que el uso de public es inútil cuando las clases no se han organizado en paquetes. Por otra parte, en programas pequeños resulta sencillo ser disciplinados en cuanto a la abstracción y por lo tanto los mecanismos de encapsulación pierden efectividad.