Sun Certified Java Programmer 6, CX-310-065 - Parte 2:
Orientacin a Objetos En este segundo post para la certificacin de Java 6 hablaremos sobre conceptos de la orientacin a objetos, que abarca temas como la herencia, el polimorfismo, la cohesin, bajo acoplamiento (loose coupling), etc.
Empezaremos hablando sobre la encapsulacin: Encapsulacin
La encapsulacin es un mecanismo que nos permite que, aunque nuestras clases utilicen muchas variables y mtodos para su correcto funcionamiento, no todas sean visibles desde el exterior. O sea que solo exponemos lo que los clientes necesitan para poder hacer uso de los objetos de nuestra clase.
Para entender mejor este concepto imaginemos el siguiente escenario:
Tenemos la siguiente clase:
public class Examen { public String pregunta1;
public static void main(String... args) { Examen examen = new Examen(); examen.pregunta1 = "Que es Java?"; // Legal pero no recomendable } }
El ejemplo anterior compilar y se ejecutar de forma correcta. Sin embargo no es recomendable que los atributos de la clase (las variables de instancia) estn expuestas de forma que los clientes puedan leer y escribir sus valores directamente. De esta forma cualquiera podra colocar el valor que quisiera en la variable "pregunta1", an valores que nuestra aplicacin no puede o no est preparada para manejar.
Ahora hagamos una pregunta: Cmo poder cambiar la clase cuando alguien cambia el valor de "pregunta1" por un valor incorrecto? La nica forma es volver a la clase e implementar un mtodo de slo escritura (un mtodo setter: "setPregunta1(String pregunta1)") y ocultar la variable "pregunta1" establecindola como privada, pero de esta manera al no establecerla desde un principio con los mtodos "set" o "get" todas las personas que han utilizado este cdigo anteriormente y de la manera en la que estaba implementada se encontraran perdidas.
La capacidad de realizar cambios en el cdigo de aplicacin sin romper el cdigo de otras personas que utilizan este cdigo es un beneficio clave de la encapsulacin. Ocultando los detalles de la implementacin a travs de mtodos de acceso nos brinda la ventaja de poder rehacer el cdigo dentro de los mtodos sin forzar a las dems personas a cambios, ya que ellas usan dichos mtodos de acceso.
Con todo esto obtenemos las dos promesas/beneficios de la programacin orientada a objetos: Flexibilidad y Mantenimiento, pero como vemos estos dos beneficios no llegan solos, nosotros tenemos que implementar nuestro cdigo de manera que brinde y soporte estos beneficios.
Estas son algunas recomendaciones para lograr la encapsulacin: Mantener las variables de instancia protegidas (mayormente con el modificador de acceso private). Mantener publicos los mtodos de acceso a las variables de instancia (modificador de acceso public), as forzamos a llamar a las variables de instancia y a la implementacin del mismo a travs de estos mtodos en lugar de ir directamente por las variables de instancia. Para los mtodos de acceso se recomienda usar las reglas de las convenciones de nombres de JavaBean:set<nombrePropiedad> y get<nombrePropiedad>, de las cuales se habl en el post anterior.
A continuacin mostramos un ejemplo ms prctico de lo que nos estamos refiriendo:
La clase "A" no puede acceder a las variables de instancia de la Clase "B" sin antes ir a los mtodos de acceso ("set" y "get") de dichas variables, las variables de instancia son marcadas privadas y los mtodos de acceso son pblicos.
Debemos tener presente que el cdigo de estos mtodos no solo se utiliza para devolver o establecer los valores de las variables de instancia, tambin se puede establecer lgica o implementacin de muchas ms cosas o reglas que queramos definir, por ejemplo:
El encapsulamiento es uno de los mecanismos que nos proporciona la orientacin a objetos para poder darle una funcionalidad rica a nuestras clases sin que las personas que las utilicen sepan los detalles exactos de cmo est implementada dicha funcionalidad.
Sin embargo, este no es el nico mecanismo proporcionado por la orientacin a objetos. A continuacin veremos otro de ellos. La herencia.
Herencia, ESUN, TIENEUN (ISA, HAS-A)
En Java, la herencia se encuentra en todos. Es seguro decir que en Java casi nada se puede hacer sin hacer uso de la herencia. Para dar un ejemplo a continuacin usaremos del operador "instanceof" (por ahora no ahondaremos mucho en la explicacin del uso de este operador ya que se tocar ms adelante con mayor detalle, slo cabe recordar ahora que este operador devuelve un "true" si la variable puesta al principio es del tipo de la variable con la que se est comparando):
public class Test { public static void main(String... args) { Test t1 = new Test(); Test t2 = new Test();
if(!t1.equals(t2)) { System.out.println("No son iguales") } if(t1 instanceof Object) { System.out.println("t1 es un objeto"); } } }
De dnde saca "t1" el mtodo "equals"? Si no hemos implementado ningn mtodo dentro de la clase con ese nombre, o s? Por otro lado se pregunta si "t1" es una instancia de la clase "Object" y de ser as, la condicin if ser exitosa.
Cmo puede ser "t1" del tipo "Object" si solo lo declaramos que sea del tipo de la clase "Test"? Esto ocurre porque todas las clases en java (las que ya estn escritas, las que escribimos y las que escribiremos) son una subclase de la clase "Object" (excepto por supuesto la clase "Object" misma) y siempre tendrn mtodos como "equals", "clone", "notify", "wait" y otros ms. Siempre que creamos una clase, esta hereda todos los mtodos de la clase "Object".
El mtodo "equals" por ejemplo: Los creadores de Java asumieron correctamente que nosotros comnmente desearamos comparar instancias de las clases para comprobar la igualdad; si la clase "Object" no tuviera un mtodo "equals" tendramos que implementar nosotros mismos un mtodo para este propsito.
Tambin debemos recordar que las 2 razones ms importantes para el uso de la herencia son: Reutilizacin de cdigo Uso del polimorfismo
Empecemos con la reutilizacin. Un enfoque de diseo comn es crear una versin de una clase bastante genrica y despus crear subclases muy especializadas que hereden de esta, por ejemplo:
public class Animal { public void muevete() { System.out.println("Me estoy moviendo"); } }
public class Perro extends Animal { public void ladra() { System.out.println("Estoy ladrando"); } }
public class PruebaAnimal { public static void main(String... args) { Perro perro = new Perro(); perro.muevete(); perro.ladra(); } }
La salida del cdigo anterior seria:
Me estoy moviendo Estoy ladrando
Como podemos ver, la clase "Perro" est heredando el mtodo "muevete" de la clase "Animal" y que tambin tiene su propio mtodo agregado, en este caso es el mtodo "ladra". Aqu se est haciendo uso de la reusabilidad al utilizar un mtodo genrico de una clase padre que en este caso es el mtodo "muevete", con esto podemos crear diferentes tipos de animales y todos van a poder utilizar el mtodo "muevete" sin necesidad de implementarlo ellos mismos.
El segundo objetivo de la herencia es poder acceder a las clases polimrficamente. Imaginemos este escenario: digamos que tenemos una clase llamada "Entrenador" que quiere recorrer todos los diferentes tipos de animal e invocar al mtodo "muvete" en cada uno de ellos, al momento de escribir la clase "Entrenador" no sabemos cuntas clases de "Animal" podra haber y seguro no vamos a querer cambiar el cdigo slo porque a alguien se le ocurri crear un nuevo tipo de Animal.
Lo bonito del polimorfismo es que podemos tratar cualquier tipo de "Animal" como un "Animal", en otras palabras podemos decir lo siguiente: No me importa qu tipo de Animal se pueda crear, siempre y cuando extienda (herede) de Animal todos van a poder moverse (mtodo "muevete").
Ahora miremos esto con un ejemplo:
public class Animal { public void muevete() { System.out.println("Me estoy moviendo"); } }
public class Perro extends Animal { public void ladra() { System.out.println("Estoy ladrando"); } }
public class Gato extends Animal { public void ronronea() { System.out.println("Estoy ronroneando"); } }
Ahora imaginemos una clase llamada "Entrenador" que tiene un mtodo que tiene como argumento un "Animal", esto significa que puede tomar cualquier tipo de "Animal", cualquier tipo de animal puede ser pasado a un mtodo con un argumento del tipo "Animal", ejemplo:
public class Entrenador { public static void main(String... args) { Gato gato = new Gato(); Perro perro = new Perro(); mueveteAnimal(gato); mueveteAnimal(perro); }
public static void mueveteAnimal(Animal animal) { animal.muevete(); } }
La salida del cdigo anterior es:
Me estoy moviendo Me estoy moviendo
El mtodo "mueveteAnimal" est declarando un "Animal" como argumento pero se le puede pasar cualquier sub-clase o sub-tipo de esta clase "Animal", este mtodo podra invocar a cualquier mtodo dentro de la clase "Animal". Lo que si debemos de tener en cuenta es que slo podemos llamar a los mtodos declarados por "Animal", los mtodos declarados dentro de las subclases de "Animal" son dependiente del tipo declarado, esto significa que no podramos llamar al mtodo "ladra" incluso si el "Animal" que se est pasando es del tipo "Perro". RELACIONES IS A, HAS A
IS A
En Orientacin a objetos el concepto de "IS-A" (es-un) esta basado en la herencia de una clase ("extends") o en la implementacin de una interface ("implements"). IS-A es una forma de decir "Esta cosa es del tipo de esta cosa" (o estos dos tipos pueden ser equivalentes), por ejemplo un "Perro" es del tipo "Animal", en OO nosotros podemos decir: "Perro IS-A Animal", "Lechuga IS-A Vegetal", en java podemos expresar esta relacin IS-A por medio de las palabras reservadas "extends" (para la herencia de clases) y de "implements" (para la implementacin de interfaces), veamos un ejemplo:
public class Carro { //cualquier cdigo aqu }
public class Toyota extends Carro { /*Toyota est heredando de carro, no olvidemos que Toyota hereda los miembros de Carro incluido mtodos y variables*/ }
"Carro" tambin es un vehculo as que se podra implementar un rbol de herencia de la siguiente manera:
public class Vehiculo{...} public class Carro extends Vehiculo{...} public class Toyota extends Carro{...}
En trminos de OO podramos decir lo siguiente: Vehculo es la sper clase de Carro Carro es la subclase de Vehculo Carro es la sper clase de Toyota Toyota es la subclase de Carro Toyota hereda de Carro y de Vehiculo Toyota deriva de Carro Carro deriva de Vehculo Toyota es subtipo de Carro y Vehiculo Retornando a la relacin IS-A, lo siguiente es correcto:
Toyota extends Carro significa Toyota IS-A Carro
Carro extends Vehiculo significa Carro IS-A Vehiculo
Ahora recordemos que al principio usamos el operador instanceof, bueno, si la expresin "Toyota instanceof Carro" es verdadera, entonces es lo mismo que decir "Toyota IS-A Carro", la expresin "Toyota instanceof Vehiculo" tambin es verdadera aunque explcitamente no dice esto ya que "Toyota" extiende de "Carro", pero por otro lado "Carro" si extiende de "Vehiculo" a esto se le llama herencia indirecta ya que una clase puede ser hijo, nieto, bisnieto, etc. de otra clase, una clase puede extender o heredar de otra directa o indirectamente ya que en el rbol de herencia pueden haber clases intermedias. HASA
La relacin HAS-A esta basada en el uso en lugar de la herencia, por ejemplo "A HAS-A B" si la clase "A" tiene una referencia a una instancia de la clase "B", por ejemplo:
public class Animal{ }
public class Gato extends Animal { CajaDeArena miCajaDeArena; }
En el cdigo anterior la clase "Gato" tiene una referencia una variable de instancia del tipo "CajaDeArena", entonces podemos decir "Gato HAS-A CajaDeArena", en otra palabras, "Gato" tiene una referencia a una "CajaDeArena", la clase "Gato" puede tener un mtodo llamado "llenarCaja(Arena miArena)", de esta manera los usuarios de la clase "Gato" no podrn saber nunca que cuando se invoca al mtodo "llenarCaja" este mtodo delega toda la responsabilidad a la clase "CajaDeArena" llamando a su mtodo "llenarCaja", veamos esto con un ejemplo:
public class Gato extends Animal { private CajaDeArena miCajaDeArena;
public void llenarCaja(Arena miArena) { miCajaDeArena.llenarCaja(miArena); /*delegando comportamiento al objeto CajaDeArena */ } }
public class CajaDeArena { public void llenarCaja(Arena miArena) { System.out.println("Llenando la caja de arena"); } }
En OO muchas veces no queremos que la gente se preocupe por cual clase u objeto est haciendo realmente el trabajo, los usuarios de la clase "Gato" hacen llamado del mtodo "llenarCaja" pero estos no saben si la misma clase hace el trabajo o no, sin embargo a ellos les parece que la propia clase "Gato" lo hace, no tienen ni idea de que existe algo como una clase llamada "CajaDeArena" que es quien realmente hace el trabajo.
Toda la explicacin anterior nos servir para entender mejor otro de los conceptos de la programacin orientada a objetos, uno de los ms tiles si sabemos cmo utilizarlo correctamente: el polimorfismo.
Polimorfismo Cualquier clase que pase la prueba de tener la relacin "IS-A" puede ser considerada polimrfica, todos los objetos en Java son polimrficos ya que pasan la prueba de la relacin IS-A, tanto para su propio tipo como para con la clase "Object". Debemos recordar la nica forma de a un objeto es a travs de una variable de referencia, hay algunas cosas claves que debemos recordar de las variables de referencia: Una variable de referencia puede ser slo de un tipo y una vez declarado este tipo nunca podr cambiar (aunque el objeto al que hace referencia puede cambiar). Una referencia es una variable, por lo cual su valor puede ser reasignada a otros objetos a menos que esta sea declarada como final. Un tipo de variable de referencia determina los mtodos que se pueden invocar en el objeto que esta variable est referenciando. Una variable de referencia puede referirse a cualquier objeto del mismo tipo que el que est declarando, o puede referirse a cualquier subtipo del tipo declarado. Una variable de referencia puede ser declarado como un tipo de clase o un tipo de interfaz. Si la variable se declara como un tipo de interfaz, se puede hacer referencia a cualquier objeto de cualquier clase que implementa la interfaz.
Anteriormente creamos una clase "Animal" que era extendida por dos clases, "Perro" y "Gato", ahora imaginemos que tenemos una clase llamada "Gaviota", despus queremos hacer que algunos tipos de Animal vuelen o se puedan elevar en el aire y otros no como el caso de "Gaviota" que si puede elevar, por el contrario "Perro" y "Gato" no pueden hacerlo, para esto podramos crear una clase llamada "Elevable" con un mtodo "elevar" y hacer que unos tipos de Animales puedan elevarse y otros no, pero tambin queremos que todos los tipos de Animal se muevan que es lo que nos permite el mtodo "muevete" de la clase "Animal", pero esto no funcionara ya que Java solo soporta la herencia simple, esto significa que una sub clase slo puede tener una clase padre, es decir lo siguiente es incorrecto:
public class Gaviota extends Animal, Elevable // NO!! { //Cualquier codigo aqu }
Una clase NO puede extender de ms que de slo una clase, ante estos casos la solucin sera crearse una interface llamada "Elevable" y solo las sub-clases de "Animal" que puedan volar implementen esta interfaz, dicha interfaz quedara de la siguiente manera:
public interface Elevable { public void volar(); }
public class Animal { public void muevete() { System.out.println("Me estoymoviendo"); } }
A continuacin mostramos la clase "Gaviota" que implementa esta interfaz:
public class Gaviota extends Animal implements Elevable { public void volar() { System.out.println("Estoy volando!"); } }
Ahora tenemos a la clase "Gaviota" que pasa la prueba de la relacin IS-A tanto para la clase "Animal" como para la interfaz "Elevable", esto significa que una "Gaviota" puede ser tratada polimrficamente como una de estas cuatro cosas en un momento dado, dependiendo del tipo declarado de la variable de referencia: Como un "Object" (ya que todas las clases heredan de la clase Object). Como un "Animal" (ya que est extendiendo de la clase Animal). Como una "Gaviota" (ya que es lo que es). Como un "Elevable" (ya que implementa de la interface Elevable).
Aqu solo hay un objeto Gaviota pero cuatro diferentes variables de referencia, hagamos una pregunta: Cul de las cuatro variables de referencia puede llamar al mtodo "muevete"? Recuerden que las llamadas a mtodos permitidos por el compilador se basan nicamente en el tipo declarado de la referencia, con independencia del tipo de objeto. As que buscando en los cuatro tipos de referencia de nuevo, "Object", "Gaviota", "Animal" y "Elevable" cul de estos cuatro tipos puede llamar al mtodo "muevete()"? La respuesta es la siguiente: El objeto "animal" como el objeto "gaviota" son conocidos por el compilador para poder llamar o invocar al mtodo "muevete".
Qu mtodos pueden ser invocados cuando el objeto "gaviota" est utilizando la referencia a la inteface "Elevable"? Slo al mtodo "volar()".
Un beneficio es que cualquier clase desde cualquier lugar en el rbol de herencia puede invocar a la interface "Elevable", que sucedera si tenemos un mtodo que tiene como argumento declarado con tipo "Elevable", podras pasar una instancia de objeto del tipo "gaviota" y cualquier otra instancia de clase que implemente a la interface "Elevable", podra usar el parmetro del tipo "Elevable" para invocar al mtodo "volar()" pero no al mtodo "muevete()" o cualquier otro objeto que se sabe que el compilador conocer basado en el tipo de referencia.
Otra cosa que debemos saber es que si bien el compilador solo conoce al tipo de referencia declarado, la JVM en tiempo de ejecucin sabe lo que el objeto realmente es, y eso significa que incluso si el objeto "gaviota" hace una llamada al mtodo "muevete" usando la variable de referencia "animal", si el objeto "gaviota" sobre-escribe el mtodo "muevete", la JVM invocara a esa versin del mtodo "muevete". La JVM mira al objeto real que se encuentra al otro extremo de la referencia, puede ver que se ha sobre escrito el mtodo del tipo de variable de referencia declarado e invoca al mtodo del objeto de la clase actual.
Siempre se puede hacer referencia a un objeto con un tipo de referencia variable ms general (una superclase o interfaz), pero en tiempo de ejecucin, las nicas cosas que son seleccionadas dinmicamente basndose en el objeto real (en lugar de tipo de referencia) son mtodos de instancia (no los mtodos estticos, no las variables), slo los mtodos de instancia sobre-escritos se invocan de forma dinmica en funcin del tipo de objeto real.
Ejemplo:
public class Animal { public void muevete() { System.out.println("Me estoy moviendo"); }
public class Gaviota extends Animal { public void muevete() { System.out.println("La gaviota se mueve"); }
public static void respirar() //Mtodo esttico { System.out.println("La gaviota est respirando"); } }
public class Prueba { public static void main(String... args) { Animal a = new Gaviota(); a.muevete(); a.respirar(); } }
Salida:
La gaviota se mueve Estoy respirando
El segundo resultado que vemos se trata de la invocacin de un mtodo esttico. El comportamiento que vemos pasa porque cuando el compilador ve que se est invocando un mtodo esttico, cambia la referencia al objeto por el tipo de la clase, o sea que al final queda de la siguiente manera:
Animal a = new Gaviota(); Animal.respirar();
En el primer caso el mtodo que se invoca es el de "Gaviota" y no el de "Animal", ya que el mtodo "muevete" no es esttico sino un mtodo de instancia; por eso dice se dice que slo los mtodos de instancia sobrecargados se invocan de forma dinmica en funcin del tipo de objeto real.
Sobrecarga y sobre escritura
Sobre escritura de mtodos En cualquier momento que se tenga un mtodo que hereda de una superclase, se tendr la oportunidad de realizar una sobre-escritura de mtodos, a menos que el mtodo este marcado con la palabra reservada final. El principal beneficio de la sobre-escritura de mtodos es que se puede definir un comportamiento especial para un mtodo de una subclase. En el siguiente ejemplo veremos cmo la clase "Gaviota" sobre-escribe el mtodo "muevete" de la clase "Animal":
public class Animal { public void muevete() { System.out.println("Me estoy moviendo"); } }
public class Gaviota extends Animal { public void muevete() { System.out.println("La gaviota se mueve"); } }
Para mtodos abstractos que se heredan desde una superclase, no se tiene otra opcin ms que sobre escribir-dichos mtodos. Se deben implementar los mtodos a menos que la subclase que los herede tambin este marcada como abstracta. Los mtodos abstractos deben ser implementados por una subclase concreta, esto quiere decir que la subclase concreta sobre- escribe el mtodo abstracto de la superclase. Entonces debemos pensar que los mtodos abstractos son mtodos que forzosamente deben ser sobre-escritos.
El creador de la clase "Animal" podra haber decidido que, a efectos de polimorfismo, todos los subtipos de Animal deben implementar el mtodo "moverse", de una manera nica y especifica. Polimrficamente, cuando alguien tiene una referencia de Animal que no refiere a una instancia de Animal, sino a una instancia de una subclase de Animal, la persona que llama debe ser capaz de invocar al mtodo "muevete" en la referencia a "Animal", pero el objeto en tiempo de ejecucin real ejecutar su propio y especifico mtodo "muevete". Marcando el mtodo "muevete" como abstracto es la forma que el programador de la clase "Animal" dice a todos los desarrolladores de las dems subclases: "No tiene ningn sentido para el nuevo subtipo utilizar el mtodo genrico "muevete", por lo que t debes implementar tu propio mtodo "muevete". A continuacin mostraremos un ejemplo de clases no abstractas:
public class Prueba { public static void main(String... args) { Animal a = new Animal(); Animal b = new Gaviota(); //Referencia a Animal, pero objeto Gaviota a.muevete(); b.muevete(); } }
public class Animal { public void muevete() { System.out.println("Me estoy moviendo"); } }
public class Gaviota extends Animal { public void muevete() { System.out.println("La gaviota se mueve"); }
public void volar() { System.out.println("La gaviota vuela"); } }
En el cdigo anterior la clase "Prueba" utiliza una referencia a "Animal" para invocar un mtodo en el objeto "Gaviota", hay que recordar que el compilador solo permitir que se invoquen mtodos que se encuentran en la clase "Animal", cuando usas una referencia a un "Animal". Por lo tanto, el siguiente cdigo no es legal:
Animal c = new Gaviota(); c.volar(); // No puedes invocar a "volar()", Animal no tiene un mtodo volar
Para reiterar: el compilar solo mira el tipo de referencia y no el tipo de instancia en tiempo de ejecucin. El polimorfismo nos permite tener referencias a sper tipos o a tipos ms abstractos (incluyendo las interfaces) para referir a uno de estos subtipos (incluyendo implementacin de interfaces).
El mtodo que sobre-escribe no debe tener un modificador de acceso ms restringido que el del mtodo a ser sobre-escrito. Por ejemplo no se puede sobre-escribir un mtodo marcado con el modificador de acceso public y cambiarlo a protected. Pensemos en los siguiente: si la clase "Animal" tiene un mtodo "comer()" marcado "public" y alguien tiene una referencia de "Animal", es decir, una referencia declarada del tipo "Animal", se asume que es seguro llamar al mtodo "comer()" en la referencia de "Animal", independientemente de la instancia actual que la referencia a "Animal" est invocando. Si una subclase cambia el modificador de acceso del mtodo sobre-escrito, entonces cuando la JVM invoque la verdadera versin del objeto "Gaviota" en lugar de la versin del tipo de referencia "Animal", el programa perecer, a continuacin un ejemplo:
public class Prueba { public static void main(String... args) { Animal a = new Animal(); Animal b = new Gaviota(); // Referencia a Animal, pero objeto Gaviota a.muevete(); b.muevete(); } }
public class Animal { public void muevete() { System.out.println("Me estoy moviendo"); } }
public class Gaviota extends Animal { private void muevete() //Marcado como privado { System.out.println("La gaviota se mueve"); } }
Si el cdigo anterior compilara (cosa que no har) obtendramos el siguiente error en tiempo de ejecucin:
Animal b = new Gaviota(); //Referencia a Animal, pero objeto Gaviota a.muevete(); //Ocurre una crisis en tiempo de ejecucin
La variable "b" es del tipo "Animal", el cual tiene un mtodo pblico llamado "muevete", pero hay que recordar que en tiempo de ejecucin, Java utiliza la invocacin de mtodos virtuales para seleccionar dinmicamente la versin real del mtodo que se ejecutar, basado en la del tipo del objeto en tiempo de ejecucin. Una referencia de "Animal" puede referir siempre a una instancia de "Gaviota" porque "Gaviota" IS-A "Animal" ("Gaviota" ES UN "Animal"). Lo que hace posible que una instancia de una superclase referencie a una instancia de la subclase es que la subclase es capaz de hacer todo lo que la superclase puede hacer (ya que recibe el comportamiento de esta por herencia). Esto quiere decir que cualquiera con una referencia a "Gaviota" usando una instancia deAnimal (Animal a = new Gaviota();) es libre de llamar a todos los mtodos accesibles de "Animal", no importa si "Gaviota" sobre-escribe los mtodos de "Animal" o simplemente los hereda. Un mtodo-sobre escrito debe cumplir con el contrato de la superclase.
Las reglas para sobre escribir un mtodo son las siguientes: La lista de argumentos debe ser exactamente igual (del mismo tipo y en el mismo orden) que el mtodo a sobre-escribir. Si esta lista no coincide en realidad lo que se est haciendo es una sobrecarga del mtodo (que puede que no sea nuestra intencin). El tipo de retorno del mtodo sobre escrito debe ser el mismo o un subtipo del declarado en el mtodo de la sper clase. El modificador de acceso no debe ser ms restrictivo que del mtodo a sobre-escribir. Por ejemplo, si en la clase base tenemos un mtodo "public" NO podemos sobre-escribirlo ponindole un modificador "protected". El modificador de acceso puede ser menos restrictivo que el del mtodo a sobre-escribir. Por ejemplo, si en la clase base tenemos un mtodo "protected" SI podemos sobre-escribirlo ponindole un modificador "public". Los mtodos de instancia solo pueden ser sobre-escritos si estos son heredados por la subclase. Una subclase dentro del mismo paquete que su superclase puede sobre escribir cualquier mtodo de la sper clase que NO est marcado comoprivate o final. Una subclase en diferente paquete puede sobre-escribir solo los mtodos no finales marcados public oprotected (los mtodos protected son heredados por la subclase). Los mtodos sobre-escritos no deben lanzar excepciones marcadas (en las que sean necesarias un try catch) que sean nuevas o ms amplias que aquellas declaradas en el mtodo que sobre-escribe. Por ejemplo un mtodo que declara un "FileNotFoundException" no puede ser sobre-escrito por un mtodo que declara un "SQLException", "Exception", "IOException" o cualquier otra excepcin a menos que esta sea una subclase de "FileNotFoundException". Los mtodos sobre-escritos pueden lanzar excepciones ms especficas (excepciones que extiendan de la excepcin que se est declarando) o lanzar menos excepciones. Solo porque el mtodo a sobre-escribir puede lanzar excepciones no quiere decir que el mtodo sobre-escrito lanzara estas mismas excepciones, un mtodo sobre-escrito no tiene que declarar una excepcin que nunca lanzar. Independientemente de que el mtodo a sobre-escribir las declare. No se puede sobre-escribir un mtodo marcado con final. No se puede sobre-escribir un mtodo marcado con static. Si un mtodo no puede ser heredado entonces no puede ser sobre-escrito. Hay que recordar que la sobre-escritura implica que se est re-implementando un mtodo que est heredanddo. Por ejemplo el siguiente cdigo no es legal:
public class Prueba { public static void main(String... args) { Gaviota a = new Gaviota(); a.muevete(); //No es legal porque Gaviota no hereda muevete(), muevete() es declarado private en Animal } }
public class Animal { private void muevete() { System.out.println("Me estoy moviendo"); } }
public class Gaviota extends Animal {}
Invocando a la versin de la superclase de un mtodo sobre escrito Tal vez queramos tomar ventaja de una parte del cdigo en la versin de la sper clase de un mtodo y aun as sobre-escribirlo para proveer algn comportamiento especifico adicional. Esto es como decir: "Ejecuta la versin de la superclase de un mtodo, despus vuelve y termina con mi cdigo para hacer un comportamiento adicional en el mtodo de la subclase", esto es fcil de hacer utilizando la palabra reservada super como en el ejemplo siguiente:
public class Prueba { public static void main(String... args) { Gaviota a = new Gaviota(); a.muevete(); } }
public class Animal { public void muevete() { System.out.println("Me estoy moviendo"); } }
public class Gaviota extends Animal { public void muevete() { //podemos agregar alguna cosa mas super.muevete(); //invoca al cdigo de la superclase (Animal) } }
Usar la palabra reservada "super" para invocar a un mtodo sobre-escrito slo se aplica para mtodos de instancia, hay que recordar quelos mtodos marcados con static no pueden ser sobre escritos.
Sobrecarga de mtodos La sobrecarga de mtodos permite usar el mismo nombre de un mtodo en una clase, pero con diferentes argumentos y, opcionalmente, un tipo de retorno diferente, el cdigo asume la carga de hacer frente a diferentes tipos de argumentos en lugar de obligar a la persona que llama a hacer las conversiones antes de invocar el mtodo. Las reglas a seguir para la sobrecarga son simples: Para sobrecargar mtodos se debe cambiar la lista de argumentos. Para sobrecargar mtodos se puede cambiar el tipo de datos de retorno. Para sobrecargar mtodos se puede cambiar el modificador de acceso. Para sobrecargar mtodos se pueden declarar excepciones que sean nuevas o ms amplias. El mtodo puede ser sobrecargado en la misma clase o en una subclase. Si la clase "A" define un mtodo "hacerAlgo(int i)", la subclase "B" puede definir un mtodo llamado "hacerAlgo(String s)", sin sobre-escribir el mtodo "hacerAlgo(int i)" de la sper clase que toma como argumento un entero. Dos mtodos con el mismo nombre pero en diferentes clases pueden ser consideradas sobrecargados, si la sub-clase hereda una versin del mtodo y luego declara otra versin sobrecargada en la definicin de la clase.
Formas legales de sobrecargar un mtodo El mtodo que queremos sobrecargar es el siguiente:
public void unMetodo(String str, int t, double d){}
Los siguientes mtodos son legales para sobrecargar el mtodo anterior:
public void unMetodo(String str, int t){}
public int unMetodo(String str, double d){}
public void unMetodo(String str, int t)throws IOException{}
Invocando a mtodos sobrecargados Cuando un mtodo es invocado, ms de un mtodo con el mismo nombre puede existir para el tipo de objeto que estamos invocando. Por ejemplo la clase "Gaviota" puede tener tres mtodos con el mismo nombre pero con diferente lista de argumentos, estos sern mtodos sobrecargados. Definir cul de los diferentes mtodos se desea invocar depender de la lista de argumentos que contenga. Si estamos invocando a un mtodo que tiene un "String" como argumento, el mtodo que tiene un "String" como argumento ser llamado. A continuacin veremos un ejemplo:
public class UnaClase { public int unMetodo(int x, int y) { return x + y; }
public double unMetodo(double x, double y) { return x + y; } }
public class Prueba { public static void main(String... args) { UnaClase a = new UnaClase(); int x = 9; int y = 5;
int resultado = a.unMetodo(x, y); //Qu versin de "unMetodo" es invocado?
En el primer caso se llama a la primera versin del mtodo "unMetodo(x,y)" ya que se est pasando a dos enteros y en el segundo caso se llama a la versin del mtodo "unMetodo" que recibe dos argumentos del tipo double.
La invocacin de mtodos sobrecargados que reciben objetos en lugar de tipos primitivos es un poco ms interesante. Si tenemos un mtodo sobrecargado, que en un mtodo toma un objeto del tipo "Animal" y otro en el que toma un objeto del tipo "Gaviota" (subclase de "Animal"). Si pasamos un objeto del tipo Gaviota cuando invocamos al mtodo, invocaremos a la versin sobrecargada que toma una "Gaviota". O al menos eso parece a primera vista:
class Animal{}
class Gaviota extends Animal{}
public class UsaAnimales { public void muevete(Animal a) { System.out.println("Estas en la versin de Animal"); }
public void muevete(Gaviota g) { System.out.println("Estas en la versin de Gaviota"); }
public static void main(String... args) { UsaAnimales ua = new UsaAnimales(); Animal a = new Animal(); Gaviora g = new Gaviota(): ua.muevete(a); ua.muevete(g); } }
La salida es la que esperamos:
Estas en la versin de Animal Estas en la versin de Gaviota
Pero qu sucede si usamos una referencia de "Animal" para un objeto "Gaviota"?
Animal animalReferenciaGaviota = new Gaviota(); ua.muevete(animalReferenciaGaviota);
Cul de las versiones del mtodo "muvete" es invocada?, podramos pensar que la respuesta es: "El que toma una "Gaviota", ya que es un objeto del tipo "Gaviota" el que en tiempo de ejecucin est siendo pasado al mtodo". Pero no es as como funciona. El cdigo anterior imprime lo siguiente:
Estas en la versin de Animal
El objeto en tiempo de ejecucin es una "Gaviota" y no un "Animal". La eleccin de cul mtodo sobrecargado se deber llamar no es decidido dinmicamente en tiempo de ejecucin. Slo hay que recordar que el tipo de referencia, no el tipo de objeto, determina qu mtodo sobrecargado es llamado.
Para resumir lo que hemos visto hasta ahora de sobrecarga y sobre-escritura: cul versin de un mtodo sobre-escrito es llamado (en otras palabas, desde cul clase en el rbol de herencia) es decidido en tiempo de ejecucin por el tipo de objeto; pero cul versin del mtodo sobrecargado se llamar est basado en el tipo de referencia del argumento que se pasa en tiempo de compilacin. Si invocamos un mtodo pasando una referencia de "Animal" para un objeto "Gaviota", el compilador solo sabe que est recibiendo un "Animal", por lo que elige la versin sobrecargada que toma un "Animal". No importa que en tiempo de ejecucin realmente se pase una "Gaviota".
Polimorfismo en mtodos sobrecargados y sobre-escritos Cmo trabaja el polimorfismo en mtodos sobrecargados? Si pasamos una referencia de "Animal", el mtodo que toma un "Animal" ser llamado, incluso si el objeto actual pasado es una "Gaviota". Una vez que la "Gaviota" disfrazada de "Animal" entra en el mtodo, sin embargo, el objeto "Gaviota" sigue siendo una "Gaviota" a pesar de ser pasado a un mtodo que recibe un "Animal". Entonces es verdad que el polimorfismo no determina que versin del mtodo sobrecargado ser llamado. El polimorfismo entra en juego cuando se decide cul versin de un mtodo sobre-escrito es llamado. Pero algunas veces un mtodo puede ser ambos, sobre- escrito y sobrecargado. Imaginemos lo siguiente:
public class Animal { public void comer() { System.out.println("El Animal genrico est comiendo"); } }
public class Gaviota extends Animal { public void comer() { System.out.println("La gaviota est comiendo"); }
public void comer(String s) { System.out.println("La gaviota est comiendo esto: " + s); } }
Noten que la clase "Gaviota" tiene sobrecargado y sobre-escrito el mtodo "comer" (el mtodo sobrecargado es el que recibe unString y el sobre-escrito el que no recibe nada).
La siguiente tabla muestra cul de las versiones del mtodo "comer" ser llamado dependiendo de cmo sea invocado: Cdigo de Invocacin al mtodo Resultado Animal a = new Animal(); a.comer(); "el Animal genrico est comiendo" Gaviota g = new Gaviota(); g.comer(); "La gaviota est comiendo" Cdigo de Invocacin al mtodo Resultado Animal ag = new Gaviota(); ag.comer(); "La gaviota est comiendo" Polimorfismo trabajando: El objeto actual ("Gaviota"), no el tipo de referencia ("Animal"), es usado para determinar cul versin del mtodo "comer" es llamado. Gaviota gp = new Gaviota(); gp.comer("peces") "La gaviota est comiendo esto: peces" El mtodo sobre cargado "comer(String s)" es llamado. Animal a2 = new Animal(); a2.comer("manzanas"); Error de compilacin!! El compilador mira la clase Animal y ve que no tiene un mtodo comer que reciba un String. Animal ag2 = new Gaviota(); ag2.comer("peras"); Error de compilacin!! El compilador ve solo la referencia y sabe que "Animal" no tiene un mtodo "comer" que reciba unString. Al compilador no le importa que el objeto actual pueda ser una "Gaviota" en tiempo de ejecucin.
Diferencias entre mtodos sobre cargados y sobre escritos:
Mtodo sobrecargado Mtodo sobre-escrito Argumento(s) Debe cambiar. No debe cambiar. Tipo de retorno Puede cambiar. No puede cambiar, excepto para retornos covariantes. Excepciones Puede cambiar. Pueden no declarar una excepcin del mtodo que sobre-escribe, o declarar una subclase de esta excepcin. No debe lanzar nuevas excepciones o ms "amplias" que aquellas declaradas en el mtodo que sobre-escribe. Nivel de Acceso Puede cambiar. No pueden tener un nivel de acceso ms restrictivo, pero si uno igual o menos restrictivo.
Mtodo sobrecargado Mtodo sobre-escrito Invocacin El tipo de referencia determina cul mtodo sobrecargado es seleccionado, basado en el tipo de argumentos declarados. Sucede en tiempo de compilacin. El mtodo real que se invoca es todava una invocacin de mtodo virtual que siempre sucede en tiempo de ejecucin, pero el compilador ya sabe la firma del mtodo que se invoca. El tipo de objeto determina cul mtodo es seleccionado. Sucede en tiempo de ejecucin.
Casting de variables de referencia Hasta el momento hemos visto como es posible y comn el uso de variables de referencia genricas para referirse a tipos de objetos ms especficos, eso es el corazn del polimorfismo, como ejemplo veamos la siguiente lnea de cdigo:
Animal animal = new Gaviota();
Pero qu sucede si queremos usar una variable de referencia "Animal" para invocar un mtodo que solo la clase "Perro" tiene? Sabemos que nos estamos refiriendo a un Perro y queremos hacer una cosa especfica de Perro.
En la siguiente lnea de cdigo tendremos un arreglo de Animales y cada vez que encontremos una Perro en el arreglo, haremos algo especial de Perro. Asumamos por el momento que todo el cdigo escrito a continuacin est correcto, solo que no estamos seguros de la lnea de cdigo que invocara al mtodo "hacerRuido".
public class Animal { void hacerRuido() { System.out.println("Ruido Generico"); } }
public class Perro extends Animal { void hacerRuido() { System.out.println("Ladrando"); }
public class Prueba { public static void main(String... args) { Animal[] a = {new Animal(), new Perro(), new Animal()};
for(Animal animal: a) { animal.hacerRuido();
if(animal instanceof Perro) { animal.hacerseElMuerto(); //Se intenta invocar un metodo de Perro? } } } }
Cuando compilemos el cdigo anterior, el compilador nos enviara un mensaje como:
Cannot find Symbol
El compilador nos est diciendo: "Oye, la clase "Animal" no tiene un mtodo "hacerseElMuerto"".
Ahora modifiquemos el bloque de la condicional if:
if(animal instanceof Perro) { Perro perro = (Perro) animal; // Casteando la variable de referencia perro.hacerseElMuerto(); }
El nuevo y mejorado bloque de cdigo contiene un "cast", el cual en algunos casos es llamado down casting, porque estamos bajando en el rbol de herencia a una categora mas especifica.
Ahora el compilador no nos dar problemas, casteamos la variable "animal" a un tipo "Perro". El compilador nos est diciendo lo siguiente: "Sabemos que nos estamos refiriendo a un objeto del tipo Perro, est bien hacer una nueva variable de referencia de tipoPerro, para referirnos a este objeto", en este caso estamos bien, porque antes de intentar el "cast" hicimos una prueba deinstanceof para asegurarnos.
Es importante saber que el compilador confa en nosotros cuando hacemos un down casting aun cuando pudiramos hacer algo como lo siguiente:
public class Animal{}
public class Perro extends Animal{}
public class Prueba { public static void main(String... args) { Animal animal = new Animal(); Perro perro = (Perro)animal; //Compila pero fallara despus } }
Este cdigo compilara correctamente pero cuando intentemos ejecutarlo vamos a obtener una excepcin como la siguiente:
java.lang.ClassCastException
Por qu no podemos confiar en el compilador para que nos ayude en esto? No puede ver que animal es del tipo "Animal"? Todo lo que el compilador puede hacer es comprobar que los dos tipos pertenezcan al mismo rbol de herencia, por lo que dependiendo de lo que el cdigo puede hacer antes del down casting, es posible de que "animal" sea del tipo "Perro". El compilador puede permitir cosas que posiblemente puedan funcionar en tiempo de ejecucin. Sin embargo si el compilador sabe con certeza que el cast no puede funcionar, entonces la compilacin fallara. El siguiente bloque de cdigo NO compilar:
Animal animal = new Animal(); Perro p = (Perro) animal; String s = (String) animal; //animal nunca puede ser un String
En este caso obtendremos un error como el siguiente:
inconvertible types
A diferencia del down-casting, el up-casting (convertir a un tipo ms general en el rbol de herencia) trabaja implcitamente porque cuando estmos haciendo up-casting estamos implcitamente restringiendo el numero de mtodos que podemos invocar, lo que impide que ms adelante podamos invocar a un mtodo ms especifico, por ejemplo:
class Animal{}
class Perro extends Animal{}
class Prueba { public static void main(String args) { Perro p = new Perro(); Animal a1 = p; //upCasting, se puede sin conversin explicita Animal a2 = (Animal)p; // upCasting, se puede con conversin explicita } }
Ambos up-castings anteriores compilaran y se ejecutaran sin excepcin, porque un "Perro" ES-UN "Animal", lo que significa que lo que cualquier "Animal" pueda hacer, el "Perro" lo har. Un "Perro" puede hacer ms, pero en este punto, cualquiera con una referencia de "Animal" puede llamar con seguridad a los mtodos de "Animal" en una instancia de "Perro". Los mtodos de "Animal" pueden haber sido sobre-escritos en la clase "Perro", pero todo lo que importa ahora es saber que un "Perro" puede hacer todo lo que un "Animal" puede hacer. El compilador y la JVM saben esto tambin, entonces el up-casting implcito es siempre legar para la asignacin de un objeto de un subtipo para una referencia a su sper tipo o interface. Si "Perro" implementa la interface "Mascota", y "Mascota" define el mtodo "seAmigable()", un "Perro" puede implcitamente hacer casting a una "Mascota", pero el nico mtodo de "Perro" que se podr invocar es "seAmigable()", el cual "Perro" fue forzado a implementar porque "Perro" implement a la interface "Mascota".
Una cosa ms, si "Perro" implementa a la interface "Mascota", y si "Sabueso" extiende a "Perro", pero "Sabueso" no declara que este implementando a "Mascota", "Sabueso" sigue siendo una "Mascota", "Sabueso" es una "Mascota" simplemente porque este est extendiendo a "Perro". La clase "Sabueso" puede siempre sobre-escribir cualquier mtodo que este heredando desde "Perro", incluyendo los mtodos que "Perro" implementa para cumplir con el contrato de la interfaz.
Por ltimo, si "Sabueso" declara que implementa a "Mascota", es solamente para que los que busquen en "Sabueso" puedan ver fcilmente que "Sabueso" ES-UNA "Mascota", sin tener que mirar a la superclase de "Sabueso". "Sabueso" no tiene la necesidad de implementar el mtodo "seAmigable()" si la clase "Perro" (sper clase de "Sabueso") ya se ha ocupado de eso. En otras palabras, si "Sabueso" ES-UN "Perro", y "Perro" ES-UNA "Mascota", entonces "Sabueso" ES-UNA "Mascota", y ya ha cumplido con los mtodos de "Mascota" para la aplicacin del mtodo "seAmigable()", ya que hereda el mtodo "seAmigable()". El compilador es suficientemente inteligente para decir: "S que Sabueso es un Perro, pero est bien para que sea ms obvio".
As que no hay que dejarse engaar por el cdigo que muestra una clase concreta que declara que implementa una interfaz, pero no implementa el mtodo de la interface, antes de poder decir si el cdigo es legal, debemos mirar cul es la superclase de la clase que esta implementado esta interface. Si alguna clase en el rbol de herencia ya ha implementado mtodos concretos y ha declarado que ella (la superclase) implementa la interfaz, entonces la subclase no tiene ninguna obligacin de volver a implementar (sobre-escribir) los mtodos.
Implementando una interface Cuando implementamos una interface estamos aceptando el contrato definido por la interfaz. Eso quiere decir que estamos obligados a implementar todos los mtodos definidos por la interfaz y que cualquiera que conozca los mtodos de la interfaz (no como lo implementa pero si como se llaman y que retornan) puede invocar estos mtodos en una clase que implementa la interfaz.
Por ejemplo si creamos una clase que implemente a la interfaz "Runnable" (para que el cdigo pueda ejecutarse en otro un hilo), debemos implementar el mtodo "public void run()", de lo contrario el hilo ver que no tenemos implementado el mtodo "run" de la interface "Runnable" y provocar un error. Afortunadamente, Java impide que esta crisis se produzca mediante la ejecucin de un proceso de comprobacin, en el compilador, de cualquier clase que pretende implementar una interfaz.
Si la clase est implementando una interface, deberamos tener una implementacin para cada mtodo de la interface, con unas pocas excepciones que veremos en un momento.
Por ejemplo, imaginemos que tenemos la interfaz "Rebotable", con dos mtodos "rebotar()" y "setFactorRebote", la siguiente clase compilar:
public class Pelota implements Rebotable //palabra reservada implements { public void rebotar(){} public void setFactorRebote(int fr){} }
El contrato garantiza que una clase tenga todos los mtodos de una interface pero no garantiza que tenga una buena o correcta implementacin en el cuerpo del mtodo. El compilador nunca se fijar que haya algo entre las llaves del mtodo, nunca dir que es un mtodo y que debera hacer algo, solo se fija en que tenga los mtodos que se describen en la interface, nada ms.
Las clases que implementan una interface deben de seguir las mismas reglas para una clase que extiende a una clase abstracta.
Las reglas para que una clase NO abstracta implemente correctamente una interface son las siguientes: Proveer implementacin para todos los mtodos declarados en la interface. Seguir todas las reglas para una sobre-escritura legal (overrides). No declarar excepciones, en la implementacin del mtodo, que no estn en el mtodo definido en la interface. Se pueden declarar subclases de las declaradas por el mtodo de la interfaz. Mantener el mismo nombre del mtodo de la interface y el mismo tipo de dato de retorno (o un subtipo).
Una clase que implementa una interface puede ser abstracta. Por ejemplo:
abstract class Pelota implements Rebotable{}
Notan que algo falta?, pues no estamos implementando los mtodos de la interfaz "Rebotable", y no tenemos ningn error. Si la clase que implementa una interface es una clase abstracta esta puede simplemente pasar la implementacin de los mtodos a la primera clase concreta que se implemente.
Por ejemplo si tenemos la clase "PelotaPlaya" y esta extiende de la clase "Pelota", entonces la clase "PelotaPlaya" debe de implementar todos los mtodos de la interfaz "Rebotable":
public class PelotaPlaya extends Pelota { /*A pesar que no se dice en la declaracin anterior (no hay implements a Rebotable) PelotaPlaya tiene que implementar a la interface Rebotable ya que la super clase abstracta de PelotaPlaya (Pelota) implementa a Rebotable */
public void rebotar(){}
public void setFactorRebote(int fr){}
/*Si la clase Pelota hubiera declarado algun mtodo abstracto, entonces ese mtodo debera estar implementado aqu tambin*/ }
A menos que la implementacin sea de una clase abstracta, la implementacin debe tener todos los mtodos definidos por la interface.
Hay dos reglas ms que debemos saber: 1. Una clase puede implementar ms de una interface, por ejemplo:
public class Pelota implements Rebotable, Runnable, Serializable{} Podemos extender solo a una clase, pero implementar a muchas interfaces. Pero recuerden que la sub-clasificacin (extends) define quin y qu es nuestra clase, mientras que la implementacin (implements) define una funcin que puede desempear o un sombrero que nuestra clase puede usar, a pesar de lo diferente que podra ser de la otra clase que implemente la misma interface (pero de un rbol de herencia diferente). Por ejemplo: "Persona extends SerHumano", pero una "Persona" tambin puede ser (implements) "Programador", "Empleado", "Pariente". 2. Una interface puede tambin extender a otra interface, pero nunca implementar nada, por ejemplo:
public interface Rebotable extends Movible{} Qu significa esto? La primera clase concreta que implemente "Rebotable" debe implementar todos los mtodos de esta interface, pero tambin debe implementar todos los mtodos de la interface "Movible". La sub-interface simplemente est agregando ms requerimientos al contrato de la super-interface.
Una interface puede extender a ms de una interface, pero sabemos que cuando hablamos de clases esto es ilegal, por ejemplo:
public class Programador extends Empleado, Geek{} //Esto es ilegal!!
Como mencionamos anteriormente una clase no permite extender a ms de una clase en Java, una interface, sin embargo, puede implementar a ms de una interface, por ejemplo:
En el prximo ejemplo, "Pelota" requiere implementar la interface "Rebotable", pero tambin debe implementar todos los mtodos de las interfaces que "Rebotable" ha extendido, incluyendo cualquier interface que aquellas interfaces extienden, y as sucesivamente hasta llegar a la parte superior de la pila. "Pelota" debe tener lo siguiente:
public class Pelota implements Rebotable { public void rebotar(){} //implementando metodos de la public void setFactorRebote(int fr){} //interface Rebotable
public void muevete(){} //implementando de Movible
public void inflate(){} //implementando de Inflable }
Si la clase "Pelota" no implementa cualquiera de los mtodos de "Rebotable", "Movible" o "Inflable", el compilador encontrar errores, al menos que "Pelota" sea una clase abstracta. En el caso de que "Pelota" sea una clase abstracta esta puede implementar todos, algunos o ningn mtodo de las interfaces, puede dejar la implementacin a una sub clase concreta de "Pelota", por ejemplo:
abstract class Pelota implements Rebotable { public void rebotar(){} //define comportamiento de la public void setFactorRebote(int fr){} //interface Rebotable
/*no implementa el resto de los mtodos, deja el resto para una sub- clase concreta*/ }
class PelotaFutbol extends Pelota { /*implementando mtodos que pelota no hizo*/ public void muevete(){} //implementando de Movible
public void inflate(){} //implementando de Inflable
/*PelotaFutbol puede elegir sobre escribir el mtodo rebotar implementado en pelota*/ public void rebotar(){} }
Comparando ejemplos de abstracto y concreto de extender e implementar:
Ya que "PelotaFutbol" es la primera clase concreta que Implementa "Rebotable", esta debe implementar todos los mtodos de la interface "Rebotable", excepto aquellos definidos en la clase abstracta "Pelota". Ya que "Pelota" no provee implementacin de los mtodos de la interface "Rebotable", es necesario que "PelotaFutbol" implemente todos aquellos mtodos.
Tipos Correctos de Retorno El objetivo de esta seccin es cubrir dos aspectos de los tipos de retorno: 1. Qu se puede declarar como un tipo de retorno, y 2. Qu se puede retornar como un valor. Qu se puede y no se puede declarar es muy sencillo, pero esto depende si estamos sobre- escribiendo un mtodo, heredado o simplemente declarando un mtodo nuevo (el cual incluye sobrecarga de mtodos). Daremos solo una pequea mirada a las diferencias entre las reglas para los tipos de retorno para mtodos sobre cargados y sobre-escritos (overloaded y overriding). Declaracin de tipos de retorno Veremos qu est permitido para declarar como un tipo de retorno, lo cual depende primero en que si estamos sobre-escribiendo, sobrecargando o declarando un nuevo mtodo. Tipos de retorno en mtodos sobre cargados Recuerden que un mtodo sobrecargado no es ms que una forma para la reutilizacin del mismo nombre de un mtodo pero con diferentes argumentos. Un mtodo sobrecargado es un mtodo completamente diferente de cualquier otro mtodo con el mismo nombre. Si heredamos un mtodo pero lo sobrecargamos este en una subclase, este no est sujeto a las restricciones de sobre- escritura, lo cual implica que podemos declarar cualquier tipo de retorno. Lo que no podemos hacer es cambiar solo el tipo de retorno. Para sobre cargar un mtodo solo debemos cambiar la lista de argumentos, por ejemplo:
public class Animal { void muevete(){}; }
public class Perro extends Animal { String muevete(int numeroPasos) { return null; } }
Noten que el mtodo de "Animal" tiene un tipo de retorno diferente que el mtodo de "Perro". Esto est bien. En el momento que se ha cambiado la lista de argumentos, hemos sobrecargado el mtodo, entonces el tipo de retorno no tiene que coincidir con el del mtodo de la sper clase, pero por ejemplo, lo siguiente no est permitido:
public class Animal { void muevete(){}; }
public class Perro extends Animal { String muevete() //Error, no se puede cambiar solo el tipo de retorno { return null; } }
Sobre escritura y tipos de retorno, retornos covariantes Cuando una clase quiere cambiar la implementacin del mtodo de un mtodo heredado (sobre- escrito), la subclase debe definir un mtodo que debe coincidir exactamente con el mtodo heredado. O, a partir de Java 5, se le permite cambiar el tipo de retorno en el mtodo sobre- escrito siempre y cuando el nuevo tipo de retorno es un subtipo del tipo de retorno declarado en el mtodo a sobre-escribir (superclase).
class Alpha { Alpha hacerAlgo(char c) { return new Alpha(); } }
class Beta extends Alpha { Beta hacerAlgo(char c) //sobre escritura legal en Java 1.5 { return new Beta(); } }
En Java 5 este cdigo compilara sin problemas, pero si intentamos compilar este cdigo con Java 1.4, mostrar el siguiente error:
attempting to use incompatible return type
Otras reglas aplicadas a la sobre-escritura, incluyen aquellas para modificadores de acceso y declaracin de excepciones. Retornando un valor Tenemos que recordar solo 6 reglas para retornar un valor: 1. Podemos retornar null a los mtodos que tengan como tipo de retorno un objeto, ejemplo:
public Button hacerAlgo { return null; } 2. Podemos declarar un arreglo como tipo de retorno sin problemas, ejemplo:
public String[] go() { return new String[] {"Alan", "Alex", "Diego", "y Peke", "Henry", "Cesar", "Pedroww"}; } 3. En un mtodo con un tipo de retorno primitivo, podemos retornar cualquier valor o variable que pueda ser implcitamente convertido al tipo de retorno declarado, ejemplo:
public int go() { char c = c; return c; //char es compatible con int } 4. En un mtodo con un tipo de retorno primitivo, podemos retornar cualquier valor o variable que pueda ser explcitamente convertido al tipo de retorno declarado, ejemplo:
public int go() { float f = 32.5f; return (int) f } 5. No debemos retornar nada desde un mtodo que tiene como tipo de retorno "void", ejemplo:
public void go() { return "Esto es todo"; //Error, no es legal } 6. En un mtodo con un tipo de retorno con referencia a un objeto, podemos retornar cualquier tipo de objeto que pueda ser implcitamente convertido al tipo de retorno declarado, ejemplo:
public Animal getAnimal() { return new Perro(); //Asumiendo que Perro extiende de Animal }
public Object getObject() { int[] nums = {1, 2, 3}; return nums; //retorna un arreglo de enteros, el cual sigue siendo un //objeto }
public interface Rebotable{}
public class Pelota implements Rebotable{}
public class TestPelota { //Mtodo con una interface como tipo de retorno public Rebotable getRebotable() { return new Pelota(); //Retorna un implementador de la interface } } Y con eso terminamos todo lo concerniente con las reglas para el retorno de tipos de valor. Ahora veremos algunos detalles interesantes sobre constructores e instanciacin de objetos. Constructores e Instanciacin Los objetos son construidos. No podemos crear un nuevo objeto sin invocar a un constructor. No podemos crear un nuevo objeto sin invocar al constructor del objeto actual y a los constructores de sus superclases.
Los constructores son cdigo que se ejecuta cuando usamos la palabra reservada "new". Tambin pueden ser bloques de inicializacin que se ejecutan al escribir "new" (son como constructores pero un poco distintos), pero vamos a cubrir estos (bloques de inicializacin), y sus homlogos de la inicializacin esttica, ms adelante. Vamos a ver cmo se codifican los constructores y como trabajan en tiempo de ejecucin. Constructores Bsicos Todas las clases, incluyendo las clases abstractas, deben tener un constructor, pero solo porque una clase deba de tener un constructor no quiere decir que necesitamos escribirlo explcitamente. Un constructor es algo como esto:
public class Rectangulo { Rectangulo(){} //Constructor para la clase Rectangulo }
Un constructor no retorna ningn tipo de valor. Hay dos cosas que siempre debemos recordar acerca de los constructores: 1. 1. No retornan ningn tipo de valor y 2. 2. Llevan el mismo nombre que la clase. Tpicamente los constructores son utilizados para inicializar el estado de las variables de instancia, por ejemplo:
public class Rectangulo { private int ancho; private int altura;
public Rectangulo (int ancho, int altura) { this.ancho = ancho; this.altura = altura; } }
En el caso anterior, la clase "Rectangulo" no tienen un constructor sin argumentos, lo que significa que el siguiente cdigo fallar en tiempo de compilacin:
Rectangulo r = new Rectangulo(); //No compila, no coinciden el constructor
Pero el siguiente cdigo, compilar correctamente:
Rectangulo r = new Rectangulo(4,2); //No hay problema, los argumentos coinciden con el constructor
Es muy comn (y deseable) tener un constructor sin argumentos, independientemente del nmero de constructores sobrecargados que nuestra clase pueda tener (s, los constructores pueden ser sobrecargados). De vez en cuando se tiene una clase en la que no tiene sentido crear una instancia sin necesidad de suministrar informacin al constructor. Un "java.awt.Color", por ejemplo, no puede ser llamado mediante una llamada a un constructor sin argumentos, porque es como decirle a la JVM: "Hazme un nuevo color, y no me importa qu tipo de color sea este... tu decide", realmente creen que la JVM pueda tomar decisiones de ese tipo? Encadenamiento de constructores Sabemos que los constructores son invocados en tiempo de ejecucin cuando usamos la palabra reservada "new" de la siguiente manera:
Perro p = new Perro();
Pero qu es lo que realmente sucede cuando escribimos "new Rectangulo()"?
Asumamos que "Perro" extiende (hereda) de "Animal" y "Animal" extiende de "Object" 1. 1. El constructor de "Perro" es invocado. Cada constructor invoca al constructor de su superclase con una llamada implcita a "super()", a menos que el constructor invoque a un constructor sobrecargado de la misma clase, pero esto lo veremos ms adelante. 2. 2. El constructor de "Animal" es invocado ("Animal" es la superclase de "Perro") 3. 3. El constructor de "Object" es invocado ("Object" es la ltima superclase de todas las clases, entonces "Animal" extiende de "Object" a pesar de que no escribimos explcitamente "extends Object" en la declaracin de la clase "Animal". Esto es Implcito), en este punto estamos en el cima de la pila. 4. 4. Se asignan los valores explcitos a las variables de instancia. Nos referimos a las variables que declaramos como "int x = 24", donde 24 es el valor explicito (en comparacin con el valor por defecto) de la variable de instancia. 5. 5. El constructor de Object se completa. 6. 6. Se asignan los valores explcitos a las variables de instancia de "Animal" (si tuviera). 7. 7. El constructor de "Animal" completa. 8. 8. Se asignan los valores explcitos a las variables de instancia de "Perro" (si tuviera). 9. 9. El constructor de "Perro" completa.
A continuacin mostramos como trabajan los constructores en la pila de llamadas: 1. 4.- Object() 2. 3.- Animal() llamada a super() 3. 2.- Perro() llamada a super() 4. 1.- main() llamada a new Perro()
Reglas para los constructores: A continuacin mostramos lo que debemos recordar acerca de los constructores: Un constructor puede tener cualquier modificador de acceso, incluso "private" (un constructor privado quiere decir que solo el cdigo dentro de la clase misma puede instanciar un objeto de ese tipo, as que si la clase con constructor privado quiere permitir que una clase pueda instanciarla, la clase debe proveer un mtodo esttico o variable que permitan acceder a una instancia creada dentro de la clase). El constructor debe tener el mismo nombre que la clase. El constructor no debe retornar ningn tipo de valor. Es legal (pero tonto) tener un mtodo con el mismo nombre de la clase, pero esto no hace que sea un constructor. Si ven que tiene un tipo de retorno, entonces es un mtodo en vez de un constructor. Podemos tener ambos, un mtodo y un constructor con el mismo nombre (el nombre de la clase) en una misma clase y esto no es problema para Java. Si no escribimos ningn constructor dentro de la clase, un constructor por defecto ser automticamente generado por el compilador. El constructor por defecto SIEMPRE es un constructor sin argumentos. Si deseamos tener un constructor sin argumentos y ya hemos escrito algn otro constructor dentro de la clase, el compilador NO proporcionara un constructor sin argumentos (o cualquier otro constructor). Es decir si hemos escrito algn constructor con argumentos no vamos a tener un constructor sin argumentos a menos que nosotros mismos lo declaremos. Todos los constructores tienen como primera declaracin ya sea una llamada a un constructor sobre cargado "this()" o una llamada al constructor de la superclase "super()", aunque recuerden que esta llamada puede ser insertada por el compilador. Si escribimos un constructor (en lugar de confiar en el constructor por defecto generado por el compilador), y no escribimos una llamada a this() o a super(), el compilador insertar una llamada sin argumentos a super() por nosotros, como una primera declaracin en el constructor. Una llamada a super() puede ser una llamada sin argumentos o podemos incluir argumentos en l. Un constructor sin argumentos no es necesariamente el constructor por defecto, aunque el constructor por defecto siempre es uno sin argumentos. El constructor por defecto es el nico que el compilador provee, pero podemos insertar nuestro propio constructor sin argumentos. No podemos hacer una llamada a un mtodo de instancia o acceder a una variable de instancia hasta que se ejecute el constructor super. Solo variables y mtodos estticos pueden ser accedidos como parte de la llamada a super() o this(). Por ejemplo: super(Animal.NOMBRE) est bien ya que NOMBRE est declarada como variable esttica. Las clases abstractas tienen constructores, y estos constructores son siempre llamados cuando una clase concreta es instanciada. Las interfaces no tienen constructores, las interfaces no son parte del rbol de herencia de un objeto. La nica forma de que un constructor pueda ser invocado es dentro de otro constructor, en decir, no podemos invocar a un constructor de la siguiente manera:
public class Perro { public Perro(){} //Contructor
public void hacerAlgo() { Perro(); //llamada al constructor. Error!! } }
Determinar si un constructor por defecto ser creado El siguiente cdigo muestra a la clase "Perro" con dos constructores:
public class Perro { public Perro(){} public Perro(String nombre){} }
El compilador insertar un constructor por defecto para la clase anterior? NO!!
Y para la siguiente modificacin en la clase?
public class Perro { public Perro(String nombre){} }
Ahora el compilador insertar un constructor por defecto? NO!!
Y qu hay de esta clase?
public class Perro { }
El compilador SI generar un constructor por defecto para la clase anterior, porque la clase no tiene ningn constructor definido. Y ahora Qu hay de esta siguiente clase?:
public class Perro { void Perro(){} }
Podra parecer que el compilador no crea un constructor ya que ya hay un constructor en la clase "Perro". Pero realmente hay un constructor? Revisemos nuevamente la clase anterior, pues eso no es un constructor, es solo un mtodo que tiene el mismo nombre que la clase. Recordemos que el tipo de retorno es un claro indicador de que eso es un mtodo y no un constructor.
Cmo se sabe con seguridad que un constructor por defecto ser creado?
Porque no declaramos NINGUN constructor en nuestra clase.
Cmo se ver el constructor por defecto creado por el compilador? El constructor por defecto tiene el mismo modificador de acceso que la clase. El constructor por defecto no tiene argumentos. El constructor por defecto incluye una llamada sin argumentos al sper constructor (super()).
Qu sucede si el sper constructor tiene argumentos?
Los constructores pueden tener argumentos as como los mtodos, si tratamos de invocar a un mtodo que toma por ejemplo un int y no le pasamos nada al mtodo, el compilador se comportar de la siguiente manera:
class Ejemplo { void tomaUnInt(int valor){} }
class Test { public static void main(String args) { Ejemplo e = new Ejemplo(); e.tomaUnInt(); //Se est tratando de invocar al metodo "tomaUnInt()" sin argumentos } }
El compilador nos dir que estamos tratando de invocar a "tomaUnInt()" sin pasarle ningn un int. El compilador, segn la versin de la JVM, responder ms o menos de la siguiente manera:
Test.java:7: tomaUnInt(int) in Ejemplo cannot be applied to ()e. tomaUnInt();
Esto quiere decir que debemos pasar los mismos valores o variables que el mtodo acepta y en el mismo orden en el que est declarado en el mtodo, a lo que queremos llegar es que este mecanismo funciona exactamente igual en los constructores.
A continuacin mostramos el cdigo que genera el compilador con respecto a los constructores: Cdigo de clase (lo que nosotros escribimos) Cdigo de constructor generado por el compilador class Perro { }
class Perro { Perro() { super(); } }
class Perro { Perro(){} }
class Perro { Perro() { super(); } } public class Perro { }
public class Perro { public Perro() { super(); } }
class Perro { Perro(String nombre){}
class Perro { Perro(String nombre) Cdigo de clase (lo que nosotros escribimos) Cdigo de constructor generado por el compilador } { super(); } }
class Perro { Perro(String nombre) { super(); } } Nada, el compilador no necesita insertar nada
class Perro { void Perro(){} }
class Perro { void Perro(){}
Perro() { super(); } }
("void Perro()", es un mtodo no un constructor)
Si el sper constructor (el constructor de su inmediata sper clase o clase padre) tiene argumentos, debemos escribir en la llamada asuper() los argumentos adecuados.
Un punto crucial: si nuestra superclase no tiene un constructor sin argumentos, debemos escribir un constructor en la clase (subclase), porque necesitamos un lugar donde insertar, en la llamada a super, los argumentos adecuados.
El siguiente cdigo da un ejemplo del problema:
class Animal { Animal(String nombre){} }
class Perro extends Animal { Perro() { super(); //He aqu el problema } }
El compilador nos arrojar algo como esto:
Perro.java:7: cannot resolve symbol symbol : constructor Animal () location: class Animal super(); // Problema! ^
Otra forma de explicar es la siguiente: Si nuestra superclase no tiene un constructor sin argumentos entonces la subclase no ser capaz de usar el constructor por defecto que te provee el compilador, es decir, el compilador puede solo insertar una llamada a "super()" sin argumentos, no ser capaz de compilar algo como esto:
class Animal { Animal(String nombre){} }
class Perro extends Animal{}
El compilador nos arrojar algo como esto:
Animal.java:4: cannot resolve symbol symbol : constructor Animal () location: class Animal class Perro extends Animal { } ^
El compilador explcitamente est haciendo el cdigo que se muestra a continuacin, donde vamos a proveer a "Perro" el mismo constructor que el compilador le proveer:
class Animal { Animal(String nombre){} }
class Perro extends Animal { //el constructor a continuacin es idntico al que el compilador proveer Perro() { super(); //Se invoca al constructor sin argumentos de "Animal" el cual no existe!! } }
Una ltima cosa que debemos recordar es que los constructores no se heredan, estos no son mtodos los cuales si se heredan. Estos no pueden ser sobre-escritos pero, como hemos visto a lo largo de esta parte de constructores, estos si se pueden sobrecargar.
Sobrecarga de constructores Sobrecargar un constructor quiere decir que escribimos diferentes versiones del constructor, cada uno tiene diferente nmero de argumentos, como por ejemplo:
class Perro { Perro(){}
Perro(String nombre){} }
La clase "Perro" anterior muestra dos constructores sobrecargados, uno de ellos toma un String como argumento y el otro no tiene argumentos; este es exactamente igual al constructor que el compilador provee, pero recordemos que una vez que nosotros escribamos un constructor en la clase, como por ejemplo el que toma el String, el compilador ya no nos proveer un constructor por defecto. Si queremos un constructor sin argumentos para sobrecargar el que tiene argumentos nosotros mismo tendremos que escribirlo como en el ejemplo anterior.
Sobrecargando un constructor proveemos diferentes maneras de que se pueda instanciar un objeto de nuestra clase, por ejemplo si sabemos el nombre del Perro, podemos pasarselo a un constructor de "Perro" que toma una cadena. Pero si no sabemos el nombre podemos llamar al constructor sin argumentos para que nos provea un nombre por defecto, a continuacin mostramos un ejemplo de lo que estamos hablando:
1. public class Perro { 2. String nombre; 3. Perro (String nombre){ 4. this.nombre = nombre; 5. } 6. 7. Perro(){ 8. this(tomaNombreAleatorio()); 9. } 10. 11. static String tomaNombreAleatorio(){ 12. int x = (int) (Math.random() * 5); 13. String nombre = new String[]{"fido", "tango", "aguao", "rex" , "lassy"}[x]; 14. return nombre; 15. } 16. 17. public static void main(String args){ 18. Perro a = new Perro(); 19. System.out.println(a.nombre); 20. Perro b = new Perro("flafy"); 21. System.out.println(b.nombre); 22. } 23. }
Ejecutando el cdigo unas cuantas veces tendremos un resultado como el siguiente:
A continuacin mostramos la pila de llamadas para la invocacin del constructor cuando un constructor es sobrecargado. 4. Object 3. Perro(String nombre) llamada a super() 2. Perro() llamada a this(tomaNombreAleatorio()) 1. main() llamada a new Perro()
Ahora vamos a describir el cdigo desde la parte superior: Lnea 2: declaramos una variable de instancia nombre de tipo String Lnea 3 5: El constructor toma un String y lo asigna a la variable de instancia nombre Lnea 7: Aqu viene lo interesante. Asumiendo que cada Animal necesita de un nombre, pero la persona que lo invoca no siempre debe saber que nombre ser (cdigo de llamada), entonces nosotros establecemos un nombre aleatorio. El constructor sin argumentos genera un nombre aleatorio invocando al mtodo "tomaNombreAleatorio()". Lnea 8: El constructor sin argumentos invoca a su propio constructor sobrecargado que toma un String llamandolo de la misma manera que sera llamado si se hace una nueva instancia del objeto, pasndole un String para el nombre. La invocacin al constructor sobrecargado se hace mediante la palabra reservada "this", pero la utiliza como si fuese un nombre de mtodo, this(). Entonces en la lnea 8 simplemente se est llamando al constructor sin parmetros que est en la lnea 3, pasndole un nombre aleatorio en lugar nosotros establecer el nombre. Lnea 11: Notemos que el mtodo "tomaNombreAleatorio()" est marcado como esttico (static), eso es porque no podemos invocar a un mtodo de instancia (en otras palabras, no esttico) o acceder a una variable de instancia hasta despus de que el super constructor haya sido ejecutado, y hasta que el sper constructor sea invocado desde el constructor en la lnea 3, en lugar que de la lnea 7, la lnea 8 puede usar solo un mtodo esttico para generar un nombre. Si nosotros quisiramos especificar algn nombre en especfico en vez de que se genere aleatoriamente, por ejemplo, "Thor", entonces en la lnea 8 simplemente pondramos this("Thor") en lugar de llamar al mtodo "tomaNombreAleatorio()" para generar un nombre aleatorio. Lnea 12: Esto no tiene nada que ver con el constructor pero es bueno aprenderlo, esto genera un numero entero aleatorio entre 0 y 4;
Lnea 13: Estamos creando un nuevo String, pero queremos que este String sea seleccionado aleatoriamente desde una lista, entonces necesitamos hacer esto. Para explicarlo con mejor detalle, en esta sola lnea de cdigo hacemos lo siguiente: 1. 1. Declaramos una variable del tipo String. 2. 2. Creamos un arreglo de Strings (annimo ya que no asignamos el arreglo) 3. 3. Obtenemos el String en index [x] (x contiene el numero aleatorio generado en la lnea 12) del recientemente creado arreglo de Strings. 4. 4. Asignamos el String obtenido del arreglo a la variable de instancia "nombre". Esto hubiera sido ms fcil de leer si hubiramos escrito esto:
String[] lista = {"fido", "tango", "aguao", "rex" , "lassy"}; String nombre = lista[x];
Lnea 18: Invocamos al constructor sin argumentos (generando un nombre aleatorio que despus ser enviado al constructor que recibe un String). Lnea 20: Invocamos al constructor sobrecargado que toma un String que representa el nombre.
El punto cable en el cdigo antes escrito est en la lnea 8, en lugar de llamar a super(), estamos llamando a "this()", y "this()" siempre significa llamar a otro constructor en la misma clase. Pero qu sucede al momento de llamar a "this()"? Tarde o temprano el constructor "super()" ser llamado, una llamada a "this()" solo significa que estamos retrasando lo inevitable, ya que algn constructor en algn lugar debe hacer una llamada a "super()".
La regla clave: la primera lnea de un constructor debe ser una llamada a "super()" o una llamada a "this()".
Sin excepciones. Si el constructor "A()" tiene una llamada a "this()", el compilador sabe que el constructor "A()" no ser e que invoque a "super()".
La regla anterior significa que un constructor nunca podr tener ambas llamadas, es decir no podr llamar a la vez a "this()" y a "super()". Porque alguna de estas llamadas debe ser la primera sentencia en un constructor, no podemos usar ambas en un mismo constructor. El compilador tampoco insertar una llamada a "super()" si el constructor tiene una llamada a "this()".
Qu suceder si nosotros tratamos de compilar el siguiente cdigo?
class A { A() { this("test"); }
A(String s) { this(); } }
El compilador puede que no capte el problema (esto depende del compilador). Este asume que sabemos lo que estamos haciendo. Captan el problema? Sabemos que el sper constructor siempre debe de ser llamado, entonces Dnde podra ir la llamada a "super()"? Recordemos que el compilador no inserta ningn constructor por defecto si nosotros ya insertamos uno o ms constructores en nuestra clase, y cuando el compilador no inserta un constructor por defecto todava puede insertar una llamada a super() en algn constructor que no tenga explcitamente una llamada a super(), a menos que, el constructor ya tenga una llamada a this() y recordemos que un constructor no puede tener ambas llamadas (this() y super()). Entonces en el cdigo anterior Dnde va la llamada a super()? Si los nicos dos constructores en la clase tienen una llamada a this(), en efecto nosotros tenemos el mismo problema que tendramos si escribimos los siguientes mtodos:
public void ir() { hacerAlgo(); }
public void hacerAlgo() { ir(); }
Ahora pueden ver el problema? Podemos ver que el stack explota. Ya que se llamaran uno a otro indefinidamente hasta que la maquina o la JVM estalle (esperamos que lo segundo suceda primero o lo primero?). Regresando al ejemplo de los constructores podemos decir que dos constructores sobrecargados y ambos con una llamada a this() son dos constructores llamndose uno a otro una y otra y otra vez, resultando en:
% java A Exception in thread "main" java.lang.StackOverflowError
El beneficio de tener constructores sobrecargados es que ofrecen formas flexibles de poder instanciar un objeto de nuestra clase. El beneficio de que un constructor invoque a otro constructor sobrecargado es evitar duplicacin de cdigo, en el ejemplo anterior no hubo algn otro cdigo para establecer el nombre, pero imaginen que despus de la lnea 4 an hay muchas cosas que podramos hacer.
Variables y mtodos estticos El modificador "static" tiene un impacto tan profundo en el comportamiento de un mtodo o una variable que debemos tratando como un concepto totalmente separado de los dems modificadores. Para entender la manera en que un miembro esttico trabaja, primero veremos las razones por las cuales utilizaramos alguno.
Imaginen que tenemos una clase de utilidad la cual siempre se ejecuta de la misma manera, su nica funcin es devolver, digamos, un nmero al azar. No nos importa en cul instancia de la clase se ejecuta el mtodo, ya que siempre se comporta de la misma manera. En otras palabras, el comportamiento del mtodo no tiene ninguna dependencia en el estado (valores de variable de instancia) de un objeto. Entonces Porque es necesario un objeto cuando el mtodo nunca ser instanciado? Por qu no solo pedimos a la clase en si misma que ejecute el mtodo?
Ahora imaginemos otro escenario: supongamos que queremos mantener un contador corriendo para todas las instancias de una clase en particular. En dnde incluiremos la variable? No va a trabajar si la incluimos como variable de instancia dentro de las clases en las cuales se quiere hacer el seguimiento, ya que el contador siempre se iniciara a un nuevo valor por defecto cada que nosotros creamos una nueva instancia.
La respuesta a los dos escenarios anteriores (el mtodo de utilidad que se ejecuta siempre de la misma manera, y el contador para mantener el total de instancias actualizado) es utilizar el modificador static.
Las variables y mtodos marcados con static pertenecen a la clase y no a una instancia en particular. Podemos usar un mtodo o variable static sin tener instancias de esa clase, solo necesitamos que la clase est disponible para invocar un mtodo static o acceder a una variable static.
Una variable esttica de una clase ser compartida por todas las instancias de esa clase, slo hay una copia.
El siguiente cdigo utiliza una variable static que ser utilizada como contador:
class Oveja { static int contadorOvejas = 0; //Declaramos e inicializamos la variable esttica
public Oveja() { contadorOveja += 1; //Modificamos el valor en el constructor }
public static void main(String args) { new Oveja(); new Oveja(); new Oveja();
System.out.println("El contador de ovejas est ahora en: " + contadorOveja); } }
En el cdigo anterior, la variable esttica "contadorOveja" es establecida en cero cuando la clase "Oveja" es cargada por primera vez por la JVM, antes de que cualquier instancia de "Oveja" sea creada (en realidad no se necesita inicializar la variable esttica en cero, las variable estticas obtienen los mismos valores por defecto que las variables de instancia obtienen). Cada vez que una instancia de la clase "Oveja" es creada el constructor de "Oveja" es ejecutado y la variable "contadorOveja" es incrementada. Cuando este cdigo es ejecutado, tres instancias de "Oveja" son creadas en el "main()", y el resultado es el siguiente:
El contador de ovejas est ahora en: 3
Ahora imaginemos que sucedera si la variable "contadorOveja" fuera una variable de instancia (es decir, una variable no esttica):
class Oveja { int contadorOvejas = 0; //Declaramos e inicializamos la variable de instancia
public Oveja() { contadorOveja += 1; //Modificamos el valor en el constructor }
public static void main(String... args) { new Oveja(); new Oveja(); new Oveja();
System.out.println("El contador de ovejas est ahora en: " + contadorOveja); } }
Cuando este cdigo es ejecutado, este puede todava crear tres instancias de la clase "Oveja" en el "main()", pero el resultado es un error de compilacin. No podemos compilar este cdigo, mucho menos ejecutarlo, porque obtenemos el siguiente error:
Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - non-static variable contadorOveja cannot be referenced from a static context System.out.println("El contador de ovejas est ahora en: " + contadorOveja); ^
La JVM no sabe a cul objeto "contadorOveja" de "Oveja" estamos intentando de acceder. El problema est en que el mtodo "main()" en s mismo es un mtodo esttico. Un mtodo esttico no puede acceder a una variable no esttica (variable de instancia) porque hay que recordar que para acceder a una variable esttica no se necesita una instancia de la clase, ya que la variable pertenece a la clase misma.
Eso no quiere decir que no hay instancias de la clase con vida en la heap, si las hay, pero el mtodo esttico no sabe nada de ellas.
Lo mismo se aplica a los mtodos de instancia, un mtodo esttico no pueden invocar directamente un mtodo no esttico.
Piensen que static=clase, no-esttico=instancia. Haciendo que el mtodo llamado por la JVM ("main()") sea un mtodo esttico laJVM no tiene que crear una instancia de la clase slo para iniciar la ejecucin de cdigo.
Accediendo a mtodos y variables estticos Puesto que no es necesario tener una instancia para invocar un mtodo esttico o acceder a una variable esttica, entonces cmo invocar o utilizar un miembro esttico? Cul es la sintaxis? Sabemos que con un mtodo de instancia regular, se utiliza el operador punto "." en una referencia, por ejemplo:
class Oveja { static int contadorOvejas = 0; //Declaramos e inicializamos la variable esttica
public Oveja() { contadorOveja += 1; //Modificamos el valor en el constructor }
public static void main(String... args) { new Oveja(); new Oveja(); new Oveja(); System.out.println("El contador de ovejas est en: " + Oveja.contadorOveja); } }
Pero para que sea realmente confuso, el lenguaje Java tambin permite el uso de una variable de referencia de objeto para acceder a un miembro esttico:
Oveja ov = new Oveja(); ov.contadorOveja; //Accediendo a la variable esttica contadorOveja usando o
En el cdigo anterior, hemos instanciado una "Oveja" asignando "new Oveja" a la variable de referencia "ov", y despus usamos la referencia "ov" para invocar al mtodo esttico. Pero a pesar de que se est utilizando una instancia especfica para acceder al mtodo esttico, las reglas no han cambiado. Esto no es ms que un truco de sintaxis que nos permite utilizar una variable de referencia a objeto (pero no el objeto que se refiere) para llegar a un mtodo o variable esttica, pero el miembro esttico sigue ignorando la instancia utiliza para invocar al miembro esttico. En el ejemplo de la Oveja, el compilador sabe que la variable de referencia "ov" es del tipo de Oveja, por lo que el mtodo esttico de la clase Oveja se ejecuta sin conocimiento o inters para la instancia de la Oveja del otro extremo de la referencia "ov". En otras palabras al compilador le importa solo que la variable de referencia "ov" sea del tipo Oveja.
El compilador en realidad hace una transformacin a la llamada que acabamos de hacer. Cuando ve que estamos tratando de invocar a un miembro esttico, reemplaza la variable de instancia que estamos usando por la clase en la que est dicho miembro. Esto quiere decir que reemplazara esta llamada:
Oveja ov = new Oveja(); ov.contadorOveja;
Por esta otra:
Oveja ov = new Oveja(); Oveja.contadorOveja;
As que como podemos ver, finalmente quedamos con una llamada al miembro esttico, a travs de la clase a la que pertenece dicho miembro.
La siguiente imagen describe el efecto del modificador static en mtodos y variables:
Finalmente, recuerden que los mtodos estticos no pueden ser sobrescritos. Pero esto no significa que no puedan ser redefinidos en una subclase. Redefinir y sobrescribir no son la misma cosa. A continuacin mostramos un ejemplo sobre redefinir (no sobrescribir) un mtodo marcado como static:
class Animal { static void hacerAlgo() { System.out.println("a"); } }
class Perro extends Animal { static void hacerAlgo() { System.out.println("b"); //Esto es redefinir, no sobrescribir }
public static void main(String... args) { Animal[] a = {new Animal(), new Perro(), new Animal()};
for(int i = 0; i < a.length; i++ ) { a[i].hacerAlgo(); //Invoca al mtodo esttico } } }
Ejecutando el cdigo anterior se produce la siguiente salida:
a a a
Recuerden, la sintaxis "a[i].hacerAlgo()" es solo un atajo (un truco de sintaxis). El compilador lo sustituye con algo como "Animal.hacerAlgo()", como vimos hace un momento.
Cohesin y acoplamiento (Coupling and Cohesion): Estos dos temas, la cohesin y el acoplamiento, tienen que ver con la calidad de un diseo orientado a objetos. En general un buen diseo pide un acoplamiento bajo y evita un acoplamiento estrecho y un buen diseo orientado a objetos pide una alta cohesin y evita la baja cohesin. Como con la mayora de las discusiones sobre diseo de orientacin a objetos, las metas de una aplicacin son: Facilidad de creacin. Facilidad de mantenimiento. Facilidad de mejora.
Acoplamiento Vamos a empezar haciendo un intento sobre la definicin de acoplamiento. Acoplamiento es el grado en el cual una clase sabe acerca de otra clase. Si el nico conocimiento que la clase "A" tiene acerca de la clase "B", es lo que la clase "B" ha puesto de manifiesto a travs de su interface, se dice que la clase "A" y "B" estn dbilmente acopladas, lo cual es algo bueno. Si por otro lado, la clase "A" se basa en una parte de la clase "B", que no es parte de la interface de la clase "B", entonces el acoplamiento entre estas dos clases es ms estrecho, y esto no es algo bueno. En otras palabras, si la clase "A" sabe ms de lo que debera de la forma en que se implement "B", entonces "A" y "B" estn estrechamente acopladas.
Usando este segundo escenario, imaginemos lo que sucede cuando la clase "B" sea mejorada. Es muy posible que el desarrollador que mejore a "B" no tenga conocimiento acerca de "A" Y porque debera de tenerlo? El desarrollador de la clase "B" debe pensar (y es correcto) que todas las mejoras que no quiebren o rompan la interfaz de la clase deben ser seguras, por lo que podra cambiar alguna parte que no tenga que ver con la interface en la clase. Si las dos clases estn estrechamente acopladas, este cambio provocara que la clase "A" se quiebre.
Veamos un ejemplo obvio de acoplamiento fuerte, que ha sido posible gracias a una pobre encapsulacin:
class Impuestos { float ratio;
float calculaImpuestoVentaPeru() { RatioImpuestoVentas riv = new RatioImpuestoVentas(); ratio = riv.ratioVentas; /*Mal, esto debera ser una llamada a un mtodo get para obtener la variable, por ejemplo: ratio = riv.getRatioVentas("PE"); */ } }
class RatiosImpuestoVentas { public float ratioVentas; //Debera ser privado public float ajusteRatioVentas; //Debera ser privado
public float getRatioVentas(String pais) { ratioVentas = new Impuestos().calculaImpuestoVentaPeru(); //Mal otra vez hacer basado en los clculos de la regin.
return ajusteRatioVentas; } }
Todas las aplicaciones orientadas a objetos que no son triviales son una mezcla de muchas clases e interfaces trabajando juntas. Idealmente, todas las interacciones entre los objetos en un sistema orientado a objetos deben utilizar sus APIs, en otras palabras, los contratos de las clases de los objetos respectivos. Tericamente, si todas las clases en una aplicacin tienen bien diseada sus APIs, entonces debera ser posible para todas las interacciones entre las clases el uso de las APIs de forma exclusiva. Como hemos comentado anteriormente en este captulo, un aspecto de buen diseo de una clase y el diseo de la API es que las clases deben estar bien encapsuladas.
Cohesin Mientras que el acoplamiento tiene que ver con cmo las clases interactan con las otras clases, la cohesin es acerca de cmo una simple clase es diseada. El termino cohesin es usado para indicar el grado para el cual una clase tiene un propsito simple bien enfocado . Hay que mantener en mente que la cohesin es un concepto subjetivo al igual que el acoplamiento. Cuanto ms enfocada sea la clase en una sola tarea, mayor su cohesin, lo cual es bueno. El beneficio clave de la alta cohesin, es que estas clases son mucho ms fciles de mantener (y menos frecuentes a ser cambiadas) que las clases con baja cohesin. Otro beneficio de la alta cohesin es que las clases con un propsito bien enfocado tienden a ser ms reutilizables que otras clases. Veamos un ejemplo:
Ahora imaginen que su jefe llega y dice, "Eh, sabes de la aplicacin de contabilidad que estamos trabajando? Los clientes decidieron que tambin van a querer generar un informe de proyeccin de ingresos, y que quieren hacer algunos informes de inventario tambin".
Tambin les dice que se aseguren de que todos estos informes les permitan elegir una base de datos, seleccione una impresora, y guardar los informes generados a ficheros de datos ... Ouch!
En lugar de poner todo el cdigo de impresin en una clase de informe, probablemente hubiera sido mejor usar el siguiente diseo desde el principio:
class ReportePresupuesto { Opciones getOpcionesReporte() {} void generarReportePresupuesto(Opciones o){} }
class ConexionBaseDatos { ConexionBD getConexion(){} }
class Impresion { OpcionesImpresion getOpcionesImpresion(){} }
class AlmacenArchivos { OpcionesGuardado getOpcionesGuardado(){} }
Este diseo es mucho ms cohesivo. En lugar de una clase que hace todo, hemos roto el sistema en cuatro clases principales, cada uno con un rol muy especfico (cohesin).
Como hemos construido estas clases especializadas, reutilizables, y va a ser mucho ms fcil escribir un nuevo informe, dado que ya tenemos la clase de conexin de bases de datos, la clase de impresin, y la clase para guardar archivos, y eso significa pueden ser reutilizados por otras clases pueden desear imprimir un informe.
Este es el fin del segundo tutorial, donde hemos aprendido un poco sobre algunos de los conceptos ms importantes de la orientacin a objetos que son necesarios para el examen de certificacin, y para nuestra vida como programadores en general.El la prxima entrega seguiremos aprendiendo ms temas importantes para nosotros como programadores Java y en particular para los que deseen certificarse como programadores Java.
Muchas gracias a Alan Cabrera Avanto, de Trujillo Per por este tutorial.
Identificar Que Estrategias Didácticas Podemos Utilizar para Facilitar La Enseñanza Del Idioma Inglés en Los Niños de Preescolar Del Colegio Americano de Barranquilla