Un pequeño "bug" con el que me he tropezado el otro día. Supón una clase padre abstracta en la que desde el constructor se llama al método abstracto.
public abstract ClasePadre {
public ClasePadre() {
…
inicializa();
…
}public abstract void inicializa() {
}
}
Ahora imagina que hacemos una clase hija, mal hecha, tal que así
public class ClaseHija extends ClasePadre {
private UnAtributo atributo = null;@Override
public void inicializa() {
…
atributo = new UnAtributo();
…
}
}
}
Simplemente hemos sobreescrito el método inicializa() que nos obliga el padre y lo aprovechamos para inicializar un atributo que inicialmente es null. A partir de aquí, nuestro código se fia de que ese atributo esté inicializado. Pues bien, está mal. Veamos el orden de construcción cuando hacemos new ClaseHija()
- Primero java llama al constructor del padre. Este llama a inicializar() y se crea el atributo de la clase hija.
- Luego java asigna a los atributos de la clase hija los valores definidos al declararlos, o sea, pone atributo a null.
- Finalmente java llama al constructor de la clase hija.
El punto 2 es el que nos da los problemas, resulta que en el punto 1 se inicializa atributo dándole un valor y en el paso 2 se vuelve a poner a null. ¡¡ El atributo queda sin inicializar a pesar de que le hemos hecho un new !!. Nos costó un buen rato dilucidar por qué algo de lo que se hacía el new, un rato después era null.
Pues bien, esto nos ha pasado, y nos ha pasado por pasar de las métricas. Hay una que dice ConstructorCallOverridableMethod, en la que salta un error si un constructor llama a un método que no es final, es decir, que las clases hijas podrían sobreescribir y hacer que la clase padre no quedara bien inicializada. Esto no es exactamente así en este ejemplo, pero está claro que no es buena idea que un constructor llama a métodos que se pueda o, como en este ejemplo, se deban sobreescribir.
Ahí el principal problema que veo yo es que Java está realmente mal diseñado en este aspecto. En c++, por ejemplo, no puedes inicializar ningún miembro de una clase (salvo los estáticos constantes) si no es en el propio constructor. Y en lenguajes dinámicos, eso funcionaría como la lógica dice que debería funcionar.
Yo evitaría siempre asignar valores en las definiciones de miembros de una clase, ese es el propósito del constructor, inicializar el estado del objeto.
Yo no creo que java esté mal diseñado en este aspecto. Si no se inicializa el atributo a null en la declaración este no se resetea, por tanto el problema no está en que el método inicializar() se pueda sobreescribir o no. Por otro lado ese atributo pertenece a la clase hija y es responsabilidad de esta incializarlo correctamente, y para eso hay que tener en cuenta al padre.
esta clase de cosas suelen pasar en lenguajes mutantes estaticos con herencia por clases 😛 Buen apunte a ver si me acuerdo la proxima vez que se me vaya la olla creando clases abstractas
Por eso es que en el futuro no habrá ni constructores ni existirá el new. Una curiosidad, por que llamas métricas a las reglas del PMD? me imagino que es alguna diferencia entre mi español sudamericano y el tuyo, pero me desconcertó un poco
Hola ilcavero:
Nunca he sido demasiado estricto con las palabras. Seguramente lo de de PMD sean reglas y no métricas, pero yo suelo meterlo todo en el mismo cajón.
Se bueno.
hola.
ey esto esta mal
public abstract void inicializa() {
}
un metodo con el modificador abstract tiene q terminar en punto y coma «;».
—————————–
si cambias esto:
private UnAtributo atributo = null;
por:
private UnAtributo atributo;
todo funciona bien. se le asigna el valor por defecto al atributo (null en el caso de que sea una variable de referencia), y despues de asignado el valor en la llamada al metodo que se hace en el constructor en la ClasePadre no lo perdera de nuevo.
😉