que programacion paso interfaz guis gui grafico grafica definicion con componentes java oop inheritance constructor override

java - programacion - ¿Qué hay de malo con las llamadas de método reemplazables en los constructores?



programacion en java con interfaz grafica (7)

Sobre la invocación del método anulable de los constructores.

En pocas palabras, esto es incorrecto porque abre innecesariamente posibilidades a MUCHOS errores. Cuando se invoca el @Override , el estado del objeto puede ser inconsistente y / o incompleto.

Una cita de Effective Java 2nd Edition, Artículo 17: Diseño y documento para herencia, o bien prohibirla :

Hay algunas restricciones más que una clase debe obedecer para permitir la herencia. Los constructores no deben invocar métodos reemplazables , directa o indirectamente. Si viola esta regla, se producirá un fallo del programa. El constructor de la superclase se ejecuta antes que el constructor de la subclase, por lo que el método de reemplazo de la subclase se invocará antes de que se haya ejecutado el constructor de la subclase. Si el método de anulación depende de cualquier inicialización realizada por el constructor de la subclase, el método no se comportará como se espera.

Aquí hay un ejemplo para ilustrar:

public class ConstructorCallsOverride { public static void main(String[] args) { abstract class Base { Base() { overrideMe(); } abstract void overrideMe(); } class Child extends Base { final int x; Child(int x) { this.x = x; } @Override void overrideMe() { System.out.println(x); } } new Child(42); // prints "0" } }

Aquí, cuando el constructor de la Base llama a overrideMe , el Child no ha terminado de inicializar el final int x , y el método obtiene el valor incorrecto. Esto casi seguramente dará lugar a errores y errores.

Preguntas relacionadas

Ver también

Sobre construcción de objetos con muchos parámetros.

Los constructores con muchos parámetros pueden llevar a una mala legibilidad y existen mejores alternativas.

Aquí hay una cita de Effective Java 2nd Edition, Item 2: Considere un patrón de constructor cuando se enfrenta con muchos parámetros de constructor :

Tradicionalmente, los programadores han usado el patrón de constructor telescópico , en el que usted proporciona un constructor con solo los parámetros requeridos, otro con un solo parámetro opcional, un tercero con dos parámetros opcionales, y así sucesivamente ...

El patrón constructor telescópico es esencialmente algo como esto:

public class Telescope { final String name; final int levels; final boolean isAdjustable; public Telescope(String name) { this(name, 5); } public Telescope(String name, int levels) { this(name, levels, false); } public Telescope(String name, int levels, boolean isAdjustable) { this.name = name; this.levels = levels; this.isAdjustable = isAdjustable; } }

Y ahora puedes hacer lo siguiente:

new Telescope("X/1999"); new Telescope("X/1999", 13); new Telescope("X/1999", 13, true);

Sin embargo, actualmente no puede establecer solo el name y es isAdjustable , y dejar los levels por defecto. Puede proporcionar más sobrecargas de constructores, pero obviamente el número explotaría a medida que aumentara el número de parámetros, e incluso podría tener múltiples argumentos boolean e int , lo que realmente haría un lío de cosas.

Como puede ver, este no es un patrón agradable de escribir, y aún menos agradable de usar (¿Qué significa "verdadero" aquí? ¿Qué es 13?).

Bloch recomienda usar un patrón de construcción, que le permitiría escribir algo como esto en su lugar:

Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();

Tenga en cuenta que ahora los parámetros tienen nombre, puede establecerlos en el orden que desee y puede omitir los que desea mantener en los valores predeterminados. Esto es ciertamente mucho mejor que los constructores telescópicos, especialmente cuando hay una gran cantidad de parámetros que pertenecen a muchos de los mismos tipos.

Ver también

Preguntas relacionadas

Tengo una clase de página Wicket que establece el título de la página en función del resultado de un método abstracto.

public abstract class BasicPage extends WebPage { public BasicPage() { add(new Label("title", getTitle())); } protected abstract String getTitle(); }

NetBeans me advierte con el mensaje "Llamada de método anulable en constructor", pero ¿qué debería estar mal con eso? La única alternativa que puedo imaginar es pasar los resultados de métodos abstractos al súper constructor en subclases. Pero eso podría ser difícil de leer con muchos parámetros.


Aquí hay un ejemplo que ayuda a entender esto:

public class Main { static abstract class A { abstract void foo(); A() { System.out.println("Constructing A"); foo(); } } static class C extends A { C() { System.out.println("Constructing C"); } void foo() { System.out.println("Using C"); } } public static void main(String[] args) { C c = new C(); } }

Si ejecuta este código, obtendrá el siguiente resultado:

Constructing A Using C Constructing C

¿Lo ves? foo() utiliza C antes de ejecutar el constructor de C. Si foo() requiere que C tenga un estado definido (es decir, el constructor ha finalizado ), entonces se encontrará con un estado indefinido en C y las cosas podrían romperse. Y como no puedes saber en A lo que espera el foo() sobrescrito, obtienes una advertencia.


Aquí hay un ejemplo que revela los problemas lógicos que pueden ocurrir cuando se llama a un método invalidable en el súper constructor.

class A { protected int minWeeklySalary; protected int maxWeeklySalary; protected static final int MIN = 1000; protected static final int MAX = 2000; public A() { setSalaryRange(); } protected void setSalaryRange() { throw new RuntimeException("not implemented"); } public void pr() { System.out.println("minWeeklySalary: " + minWeeklySalary); System.out.println("maxWeeklySalary: " + maxWeeklySalary); } } class B extends A { private int factor = 1; public B(int _factor) { this.factor = _factor; } @Override protected void setSalaryRange() { this.minWeeklySalary = MIN * this.factor; this.maxWeeklySalary = MAX * this.factor; } } public static void main(String[] args) { B b = new B(2); b.pr(); }

El resultado sería en realidad:

minWeeklySalary: 0

maxWeeklySalary: 0

Esto se debe a que el constructor de la clase B primero llama al constructor de la clase A, donde se ejecuta el método reemplazable dentro de B. Pero dentro del método estamos usando el factor de variable de instancia que aún no se ha inicializado (porque el constructor de A aún no ha terminado), por lo tanto, el factor es 0 y no 1 y definitivamente no 2 (lo que el programador podría pensar ser). Imagine lo difícil que sería rastrear un error si la lógica de cálculo estuviera diez veces más distorsionada.

Espero que eso ayude a alguien.


En el caso específico de Wicket: esta es la razón por la que le pedí a los desarrolladores de Wicket que agregaran soporte para un proceso explícito de inicialización de componentes de dos fases en el ciclo de vida del marco de la construcción de un componente, es decir

  1. Construcción - via constructor
  2. Inicialización: a través de OnInitilize (después de la construcción cuando los métodos virtuales funcionan)

Hubo un debate bastante activo sobre si era necesario o no (es IMHO completamente necesario) ya que este enlace demuestra http://apache-wicket.1842946.n4.nabble.com/VOTE-WICKET-3218-Component-onInitialize-is-broken-for-Pages-td3341090i20.html )

La buena noticia es que los excelentes desarrolladores en Wicket terminaron introduciendo una inicialización de dos fases (¡para hacer que el marco de la interfaz de usuario Java más impresionante sea aún más impresionante!), Por lo que con Wicket puede hacer toda la inicialización posterior a la construcción con el método onInitialize al que llama el marco de trabajo automáticamente si lo reemplaza: en este punto del ciclo de vida de su componente, su constructor ha completado su trabajo, por lo que los métodos virtuales funcionan como se espera.


Invocar un método reemplazable en el constructor permite que las subclases subviertan el código, por lo que ya no puede garantizar que funcione. Por eso recibes una advertencia.

En su ejemplo, ¿qué sucede si una subclase anula getTitle() y devuelve nulo?

Para "arreglar" esto, puede usar un método de fábrica en lugar de un constructor, es un patrón común de instanciación de objetos.


Si llama a los métodos en su constructor que las subclases anulan, significa que es menos probable que haga referencia a variables que aún no existen si divide su inicialización de forma lógica entre el constructor y el método.

Eche un vistazo en este enlace de muestra javapractices.com/topic/TopicAction.do?Id=215


Supongo que para Wicket es mejor llamar al método add en onInitialize() (ver ciclo de vida de los componentes ):

public abstract class BasicPage extends WebPage { public BasicPage() { } @Override public void onInitialize() { add(new Label("title", getTitle())); } protected abstract String getTitle(); }