Los Punteros de Java

Los punteros de java Hay un par de ideas sobre java muy extendidas: java no tiene punteros y en java todo se pasa por re

Views 72 Downloads 1 File size 507KB

Report DMCA / Copyright

DOWNLOAD FILE

Recommend stories

Citation preview

Los punteros de java Hay un par de ideas sobre java muy extendidas: java no tiene punteros y en java todo se pasa por referencia. La realidad, es que java se entiende mucho mejor si lo pensamos exactamente al revés. En java sólo hay punteros (con excepción de los tipos primitivos) y en java todo se pasa por valor (por copia). Por ejemplo, en C++ hacemos esto MiClase a; y ya está todo correcto. La variable a está perfectamente inicializada. Si en java hacemos eso, tenemos una variable a sin inicializar. Es necesario hacerle un new, exactamente igual que un puntero en C++ MiClase a = new MiClase(); // Esto en Java MiClase *a = new MiClase(); // Esto en C++ // o este otro tipo de inicialización extrañamente parecida ... MiClase a = null; // en java MiClase *a=NULL; // en C++ La única diferencia es la notación con el asterisco. Si pensamos que en java TODO son punteros, no es necesario poner el asterisco para distinguir lo que es puntero de lo que no lo es, por lo que símplemente lo han quitado. Ahora imaginemos un método que recibe una clase y que le hacemos una llamada // en java... void medodo (MiClase a) { a = new MiClase(); } ... MiClase b = null; metodo (b); Bueno, pues cuando salimos del método b sigue valiendo null, "apuntando a null". Eso quiere decir que a y b son variables disintas, es decir, se ha pasado la variable b por valor al método. ¿Cómo podemos crear una nueva instancia y devolverla?. En java no queda más remedio que hacerlo en el return, es imposible hacerlo a través de parámetros. Sin embargo, en C++ tenemos más posibilidades. Podemos usar un puntero al puntero, es decir, hacer esto void metodo (MiClase **a) { *a = new MiClase(); } ...

MiClase *b=NULL; metodo (&b); o bien, incluso usar referencias de verdad // El & en la declaración es lo que hace que realmente sea una referencia. void metodo (MiClase * &a) { a=new MiClase(); } ... MiClase *b=NULL; metodo (b); // Aqui b apunta al MiClase creado dentro del método. Haciéndolo así, el puntero b de fuera del método y el puntero a del parámetro son exactamente la misma variable. Ojo, no quiero decir que sean dos punteros distintos que apunten al mismo lado, sino que son realmente el mismo puntero. Cuando hacemos que a apunte a otro sitio, b también apuntará al mismo sitio. Esto es lo que yo entiendo realmente por referencia.

La clase Object Objetivos: Mostrar que en ocasiones no se necesita saber nada de un objeto y que en esos casos es útil trabajar con él, como objeto pertenciente a la clase Object. Temas:   

La clase Object. Implementación de contenedores de objetos. Mezclando objetos con tipos primitivos.

La clase Object Todas las clases son en realidad subclases de una clase más amplia: la clase Object. Esta clase incluye todos los objetos (los lectores de archivos, las tortuga, los arreglos, los glyphs, etc.). Por lo tanto siempre es posible colocar cualquier objeto en donde se espera un expresión de tipo Object. Por ejemplo: Object Object Object Object

o1= o2= o3= o4=

"Hola"; new TextReader("datos.txt"); new Nodo(3, "hola", null, null); new Box( ... );

Recuerde que la relación ser subclase de es transitiva: como Box es subclase de Glyph y Glyph es subclase de Object entonces Box es subclase de Object también y por eso es válido realizar la última operación del ejemplo. El único problema es que no hay muchas operaciones que se puedan realizar con una variable de tipo Object. Quizas la más importante es que se puede obtener un string que describa el objeto: println(o1.toString()); println(o3.toString());

// despliega "Hola" en pantalla // despliega un mensaje no muy útil

Este método se puede redefinir en las subclases de modo que se entregue alguna información más util. De todas formas algunas de las clases contenedoras que hemos visto durante el curso aceptan objetos como parámetros. Por ejemplo se puede construir una cola con objetos: Queue cola= new Queue();

cola.put(o1); cola.put(o2); cola.put(o3); cola.put(o4);

En realidad este último ejemplo debe ser visto como una humorada porque rara vez es útil juntar elementos tan diferentes en un mismo contenedor. Por otra parte, para extraer los objetos de la cola se debe usar el método get que retorna un objeto: Object stringO= cola.get();

Sería un error tratar de almacenar la referencia del objeto entregada por get en una variable de tipo String, aún cuando se sabe que el primer objeto de la cola era un string. Para poder ver el objeto como string, se debe aplicar el cast a String: String s= (String)stringO; String starS= "*** "+s+" ***"; // Ahora sí se puede trabajar println(starS); // como un string

Como se sabe que el siguiente objeto de la cola es un lector de archivos, se puede aplicar directamente el cast al momento de extraerlo: TextReader lect= (TextReader)cola.get(); String lin= lect.readLine(); ...

Por lo tanto la clase Object es importante porque evita tener que programar contenedores para cada posible tipo de objetos. Por ejemplo, la clase PilaNodos del capítulo anterior no es necesaria, porque se puede usar la clase más general Stack para almacenar todo tipo de objetos. El único cuidado es que al extraer con la operación pop, se debe aplicar el cast a Nodo.

Implementación de contenedores de objetos Para implementar una cola de objetos con una lista enlazada, basta cambiar los datos contenidos en los eslabones. En vez de colocar una referencia de un String, se coloca una referencia de un objeto: class EslabonCola { Object o; EslabonCola prox; EslabonCola(Objecto o, EslabonCola prox) { this.o= o; this.prox= prox; } }

La implementación es la misma sólo que cambia el tipo del dato que se manipula: class Queue extends Program { EslabonCola primero; EslabonCola ultimo; ... Object get() { Object o= primero.prox.o; primero.prox= primero.prox.prox; return o; } void put(Object o) { ultimo.prox= new EslabonCola(o, null); ultimo= ultimo.prox; } }

El problema de esta implementación de Queue es que no acepta colocar enteros o números reales: cola.put(4); // error en tiempo de compilación cola.put(3.14); // idem cola.put(true); // idem

En todos estos caso el compilador entregará un error diciendo que la operación put no recibe enteros, reales o valores de verdad como parámetro. Esto se debe a que estos tipos de datos no son objetos. En Java se dice que son tipos de datos primitivos. (En realidad este es un error de diseño del lenguaje: no hay ninguna razón de fondo para que no sean objetos, como sí lo son los Strings.)

Mezclando objetos con tipos primitivos Para solucionar el problema mencionado más arriba Java ofrece la clase Integer que sí es una subclase de Object. La operaciones que acepta esta clase son las siguientes: Ejemplo

Significado

Declaración

Integer objent= new Integer(1); Construye un objeto Integer que almacena el 1 Integer(int i) int i= objent.intValue()

Entrega el valor entero almacenado en objent int intValue()

El objeto objent es un objeto y por lo tanto puede ser colocado en una cola:

q.put(objent); // Correcto

Sin embargo, al ser un objeto pierde sus operaciones como entero y no se puede operar como tal: objent= objent+1; // Error de tipos en tiempo de compilación

Con la clase Integer ahora es posible almacenar cualquier entero en una cola. Supongamos que necesitamos agregar el entero e en una cola y posteriormente extraerlo. Entonces el siguiente código es una receta para lograr esto: q.put(new Integer(e)); // Coloca el entero e en la cola ... int f= ((Integer)q.get()).intValue(); // Recupera el valor e de la cola

Java también ofrece las clases estándares Double y Boolean para trabajar con los reales y los valores de verdad como objetos. Observe que se diferencian únicamente de los tipos de datos primitivos double y boolean porque Double y Boolean comienzan con una letra mayúscula y porque Double y Boolean son subclases de Object. De todas formas, esta solución todavía no es satisfactoria porque obliga a los programadores usuarios de la clase Queue a crear estos objetos adicionales. La siguiente implementación de Queue acepta enteros primitivos en la operación put: class Queue extends Program { EslabonCola primero; EslabonCola ultimo; ... void put(int i) { put(new Integer(i)); // (*) } int getInt() { return ((Integer)get()).intValue(); } }

Ahora cuando se invoque put con un entero, el compilador determina que el parámetro es entero y que hay una versión especial de put que recibe un entero. Observe que la definición de put no es recursiva en ningún caso puesto que en (*) se invoca la otra versión de put: aquella que recibe un objeto como parámetro. En el caso general, en Java se puede definir varios métodos con el mismo nombre, siempre y cuando los tipos de los parámetros sean diferentes. Durante la

compilación, Java determina el método que mejor se aproxima a los argumentos especificados en la invocación del método. Ejercicio: modifique esta implementación para que acepte números reales y valores booleanos en el put.