¿Es posible hacer cálculos antes de super () en el constructor?

81

Dado que tengo una clase Base que tiene un constructor de argumento único con un objeto TextBox como argumento. Si tengo una clase Simple de la siguiente forma:

public class Simple extends Base {
  public Simple(){
    TextBox t = new TextBox();
    super(t);
    //wouldn't it be nice if I could do things with t down here?
  }
}

Recibiré un error que me dice que la llamada a super debe ser la primera llamada en un constructor. Sin embargo, por extraño que parezca, puedo hacer esto.

public class Simple extends Base {
  public Simple(){
    super(new TextBox());
  }
}

¿Por qué se permite esto, pero no el primer ejemplo? Puedo entender la necesidad de configurar la subclase primero, y quizás no permitir que se creen instancias de variables de objeto antes de llamar al superconstructor. Pero es claramente una variable de método (local), entonces, ¿por qué no permitirlo?

¿Hay alguna forma de sortear esta limitación? ¿Existe una forma buena y segura de mantener variables para las cosas que podría construir ANTES de llamar a super pero DESPUÉS de haber ingresado al constructor? O, de forma más genérica, ¿permitir que el cálculo se realice antes de que se llame realmente a super, pero dentro del constructor?

Gracias.

Stephen Cagle
fuente
3
¿Por qué posible razón se ha etiquetado esto como gwt? Porque lo estabas probando en gwt ??
2
TextBox era una clase GWT, pero no, supongo que no es relevante.
Stephen Cagle

Respuestas:

89

Sí, existe una solución para su caso simple. Puede crear un constructor privado que tome TextBoxcomo argumento y llamarlo desde su constructor público.

public class Simple extends Base {
    private Simple(TextBox t) {
        super(t);
        // continue doing stuff with t here
    }

    public Simple() {
        this(new TextBox());
    }
}

Para cosas más complicadas, necesita usar un método de fábrica o un método de fábrica estático.

ala de cera
fuente
1
Esta parece ser una buena respuesta a la pregunta sobre cómo usaría las referencias a los objetos creados después de la construcción pero antes de super.
Stephen Cagle
3
Este es un patrón muy útil y prácticamente le permite eludir por completo el requisito de que super()no aparezca después de nada más en el constructor. En casos extremos, incluso puede encadenar varios constructores privados juntos, aunque no puedo pensar en ninguna situación en la que necesite hacer eso, y si lo hiciera probablemente comenzaría a preguntarme si hay una mejor manera de lograr lo que estaba haciendo.
Laurence Gonsalves
Sip. Otros ya habían respondido por qué funciona así en Java, solo quería señalar una solución en particular. Creo que para la mayoría de los otros casos válidos que puede encontrar, también es posible encontrar una solución alternativa aceptable. Ahora, para contradecirme, hay un caso en el que esto me mordió: estaba extendiendo una clase cuyo constructor tenía un parámetro de clase. Quería llamar super(this.getClass()), pero como no se le permite hacer referencia this, ese código era ilegal, aunque parece perfectamente razonable que pueda conocer la clase real en este momento.
Waxwing
@waxwing - puede parecer "perfectamente razonable", pero implicaría tratar el getClass()método como un caso especial en el JLS. Úselo en su <classname>.classlugar.
Stephen C
Usar <classname>.classno era una opción en este caso, porque estaba escribiendo una clase abstracta que la gente podía extender y necesitaba la clase real de la instancia.
Waxwing
33

Tuve el mismo problema con el cálculo antes de la super llamada. A veces, desea verificar algunas condiciones antes de llamar super(). Por ejemplo, tienes una clase que usa muchos recursos cuando se crea. la subclase quiere algunos datos adicionales y es posible que desee verificarlos primero, antes de llamar al superconstructor. Hay una forma sencilla de solucionar este problema. puede parecer un poco extraño, pero funciona bien:

Use un método estático privado dentro de su clase que devuelva el argumento del superconstructor y haga sus comprobaciones dentro:

public class Simple extends Base {
  public Simple(){
    super(createTextBox());
  }

  private static TextBox createTextBox() {
    TextBox t = new TextBox();
    t.doSomething();
    // ... or more
    return t;
  }
}
Ben
fuente
8

Es requerido por el lenguaje para asegurar que la superclase se construya primero de manera confiable . En particular, "Si un constructor no invoca explícitamente a un constructor de superclase, el compilador de Java inserta automáticamente una llamada al constructor sin argumentos de la superclase".

En su ejemplo, la superclase puede depender del estado de ten el momento de la construcción. Siempre puedes pedir una copia más tarde.

Hay una discusión extensa aquí y aquí .

basurero
fuente
2
Curiosamente, parece que Java se esfuerza por evitar uno de los casos más importantes en los que sería útil envolver el código alrededor de una llamada de constructor de superclase: asegurarse de que si un constructor lanza una excepción, las cosas se pueden limpiar. Por ejemplo, si una de las sobrecargas del constructor de un objeto lee datos de un archivo pasado, un constructor de clase derivada podría querer tener una sobrecarga que acepte un nombre de archivo, pase al constructor de superclase un archivo recién abierto y luego cierre el archivo. después. Si el constructor de la superclase arroja, ¿cómo se puede cerrar el archivo?
supercat
@supercat: Tienes que diseñar para la herencia o excluirla [Bloch, EffectiveJava , §17]; vea también estas preguntas y respuestas .
trashgod
Si la construcción de un objeto de subclase requiere un patrón "adquirir recurso; construir objeto de superclase; liberar recurso", ¿cómo debería implementarse? ¿Pasar al constructor interno un objeto que implemente un método de interfaz única para usarlo en caso de falla? Vagamente viable, quizás, a expensas de encadenar algunos niveles adicionales de llamadas al constructor.
supercat
Me he dado el lujo de arreglar la superclase o usar la composición; escríbeme si haces esto como una pregunta.
trashgod
1

Puede definir una lambda de proveedor estática que puede contener una lógica más complicada.

public class MyClass {

    private static Supplier<MyType> myTypeSupplier = () -> {
        return new MyType();
    };

    public MyClass() {
        super(clientConfig, myTypeSupplier.get());
    }
}
loweryjk
fuente
0

La razón por la que se permite el segundo ejemplo pero no el primero es más probable que mantenga el lenguaje ordenado y no introduzca reglas extrañas.

Permitir que se ejecute cualquier código antes de que se haya llamado a super sería peligroso, ya que podría meterse con cosas que deberían haberse inicializado pero aún no lo han sido. Básicamente, supongo que puedes hacer bastantes cosas en la llamada al super sí mismo (por ejemplo, llamar a un método estático para calcular algunas cosas que deben ir al constructor), pero nunca podrás usar nada del no -objeto aún completamente construido, lo cual es bueno.

CodificaciónInsomnio
fuente
0

Así es como funciona Java :-) Hay razones técnicas por las que se eligió de esta manera. De hecho, podría ser extraño que no pueda hacer cálculos en locales antes de llamar a super, pero en Java el objeto primero debe asignarse y, por lo tanto, debe ir hasta Object para que todos los campos se inicialicen correctamente antes de que pueda modificar accidentalmente ellos.

En su caso, la mayoría de las veces existe un getter que le permite acceder al parámetro que le dio a super (). Entonces usarías esto:

super (nuevo TextBox ());
cuadro de texto final = getWidget ();
... Haz tus cosas...
David Nouls
fuente
2
Que "todos los campos están inicializados correctamente" no está realmente garantizado en Java (a diferencia de IIUC, C ++) ya que un constructor de superclase podría llamar a un finalmétodo que no sea de instancia y que una subclase anule. La anulación no verá los campos inicializados de la subclase (¡incluso finalaquellos con inicializadores de instancia!). Algunas herramientas de análisis estático advertirán acertadamente sobre esta situación.
Jesse Glick
0

Esta es mi solución que permite crear un objeto adicional, modificarlo sin crear clases, campos, métodos adicionales, etc.

class TextBox {
    
}

class Base {

    public Base(TextBox textBox) {
        
    }
}

public class Simple extends Base {

    public Simple() {
        super(((Supplier<TextBox>) () -> {
            var textBox = new TextBox();
            //some logic with text box
            return textBox;        
        }).get());
    }
}
Pavel_K
fuente