Obviando que los constructores declarados en
VehiculoConRuedas
sirven para aprovechar constructores de Vehiculo
, lo correcto es que las clases abstractas contengan todo el código relacionado con sus miembros.Es normal que el código que implementemos para inicializar los atributos
modelo
y color
, pueda heredarlo para no tener que implementarlo en cada subtipo (mejorando el mantenimiento como ya hemos repetido varias veces)No obstante, hay un caso muy importante para tener también constructores incluso en clases abstractas: instanciar con clases anónimas.
Una clase anónima nos va a permitir declarar una clase e instanciarla en la pequeña parte de código donde nos haga falta, haciendo nuestro código más legible y económico. Como podemos deducir de su definición, es una clase que no tiene nombre, no está contenida en su propio archivo
.java
, ni es interna (esto último todavía no lo hemos visto)
Una clase anónima nos va a permitir declarar una clase e instanciarla brevemente, haciendo nuestro código más legible y económico
Hasta ahora hemos visto que cada clase tenía que tener su propio archivo
.java
con el mismo nombre o Eclipse nos daba un error (MiClase
tiene el código en el archivo MiClase.java
).Sin embargo, vamos a encontrarnos infinidad de situaciones en las que necesitaremos un objeto que podría usar un tipo prácticamente igual a otro ya creado, salvo por un pequeñísimo cambio que además sólo vamos a utilizar en ese objeto en un punto concreto de nuestro código y nunca más nos interesará.
Es demasiado repetitivo y excesivo tener que crear un archivo para una nueva clase que herede de un tipo sólo para instanciar un objeto en una línea de código (imaginad crear un botón en un menú donde lo único que cambia con otro botón es qué tiene que pasar al hacer click... ¿nos creamos una clase para cada botón?). Para estos casos, Java tiene las clases anónimas que son una herramienta poderosísima de personalización de un tipo.
Una clase anónima se crea al vuelo a la hora de instanciar un objeto que necesitamos y no encaja exactamente con ningún tipo. Se define llamando al constructor de un tipo conocido y añadiéndole un cuerpo con el comportamiento personalizado.
Vamos a ver un ejemplo con nuestro código sobre
Vehiculo
creándonos un triciclo. No nos vamos a crear una nueva clase Triciclo
, pues sólo queremos imprimir nuestro objeto triciclo, pero no encaja con ningún tipo que tengamos: tiene tres ruedas (no es una Moto
) y no tiene matrícula (no es un Coche
).
NOTA: No es una buena práctica utilizar tipos que tengan todo lo que necesitamos y hacer
null
el resto de atributos. Los objetos deben ser del tipo con el que se construyen, el resto son apaños que aumentan la deuda técnica.Lo primero es seleccionar la clase que más se acerca a lo que queremos:
VehiculoConRuedas
. Éste sería el código para crearme un triciclo:VehiculoConRuedas triciclo = new VehiculoConRuedas("Fisher-Price", "Multicolor") {
@Override
public int getNumeroDeRuedas() {
return 3;
}
};
Vemos que declaramos una variable del tipo VehiculoConRuedas
(pues hereda de éste tipo), pero al ser una clase abstracta y no poderse instanciar (está incompleta) directamente nos añadirá lo que le falta por implementar: getNumeroDeRuedas()
.Si imprimimos nuestro triciclo veremos que el método
toString()
funciona igual que en las clases NO anónimas Coche
y Moto
que heredaban de VehiculoConRuedas
. Tenemos la siguiente salida.
Fisher-Price (Multicolor), 3 ruedas
¿Sólo puedo modificar métodos que ya existan?
No. Al serVehiculoConRuedas
una clase abstracta debemos implementar los métodos no implementados, pero podemos añadir más miembros o modificar (incluso reutilizar) los que ya estén implementados.En nuestros ejemplos anteriores nos hemos creado una
Moto
. Vamos a crearnos otra que sea una Harley-Davidson
. Ésta marca de moto sólo permite dos colores: negro y rojo. Vamos a cambiar el método setColor(String)
para que no acepte ningún color que no sea uno de esos dos:Moto harley = new Moto("Harley-Davidson", "Rosa") {
@Override
public void setColor(String color) {
if(!(color.equals("Rojo") || color.equals("Negro"))) {
System.out.println("No se permite ese color para " + modelo);
} else {
super.setColor(color);
}
}
};
System.out.println(harley);
Con este código tenemos la salida:
No se permite ese color para Harley-Davidson
Moto: Harley-Davidson (null), 2 ruedas
Moto: Harley-Davidson (null), 2 ruedas
Nos avisa que el color no está permitido y de hecho vemos que nos imprime
null
porque no se ha asignado. Si le asignamos (reutilizando con super
el método que hereda) un color válido si que funcionará:harley.setColor("Negro");
System.out.println(harley);
Moto: Harley-Davidson (Negro), 2 ruedas
¿Necesito una clase abstracta para crear una clase anónima?
No. Vamos a crearnos un barco usando la claseVehiculo
que no es abstracta:Vehiculo barco = new Vehiculo("CMB Yachts", "Blanco") {
double eslora = 47.8;
private double getEslora() {
return eslora;
}
@Override
public String toString() {
return super.toString() + " con " + getEslora() + "m de eslora";
}
};
System.out.println(barco);
Vemos que podemos tanto sobrescribir, como crear nuevos miembros (incluyendo atributos como eslora
) y no afecta si el tipo del que heredamos es abstracto o no. Incluso podemos crear clases anónimas usando una Interface como veremos cuando las conozcamos. El resultado por consola es:
CMB Yachts (Blanco) con 47.8m de eslora
Las clases anónimas y el concepto scope
Un aspecto importante a destacar es que las clases anónimas se crean en el ámbito (scope) de otra y tienen acceso a los miembros tanto de su clase como del tipo donde se crean incluso con el modificador de accesoprivate
. Esto nos permite referenciar valores que permanecen privados pero que pueden ser usados en la implementación de nuestra clase anónima (por ejemplo un botón puede desencadenar un cambio en un componente privado de un formulario).
Las clases anónimas tienen acceso a todos sus miembros y a los del tipo donde se crean
Vamos a ver un ejemplo. Declaro un atributo privado:
private static int miCodigoSecreto = 12345; // ¿En serio?
Pero si me instancio una clase anónima dentro de su ámbito voy a tener acceso:Coche cocheConPin = new Coche() {
@Override
public String toString() {
return "El código secreto es: " + miCodigoSecreto;
}
};
System.out.println(cocheConPin);
Si imprimo mi nuevo objeto tendré la salida:
El código secreto es: 12345
Siendo anónimas ¿Cómo nos referimos a su class
?
Las clases van a tomar por defecto el nombre del tipo donde se instancian más un autonumérico precedido de un dolar ($
). Podemos verlo con el siguiente código:System.out.println(triciclo.getClass());
System.out.println(harley.getClass());
System.out.println(barco.getClass());
Como vemos nos salen 3 clases distintas (instanciadas en la clase Igualdad
) y lo único que cambie es el autonumérico:
class Igualdad$1
class Igualdad$2
class Igualdad$3
class Igualdad$2
class Igualdad$3
Conclusión
Puede que todavía no veas las posibilidades que aportan las clases anónimas, pero cuando conozcas losListener
, las lambdas, los stream, las Interfaces Funcionales y otras capacidades de Java, no querrás seguir programando sin ellas.
No hay comentarios:
Publicar un comentario