Pregunta

Entonces, en Java, la primera línea de su constructor TIENE que ser una llamada a super...ya sea llamando implícitamente a super() o llamando explícitamente a otro constructor.Lo que quiero saber es, ¿por qué no puedo bloquear esto?

Mi caso concreto es que tengo una clase simulada para un examen.No existe un constructor predeterminado, pero quiero uno para que las pruebas sean más sencillas de leer.También quiero envolver las excepciones lanzadas desde el constructor en una RuntimeException.

Entonces, lo que quiero hacer es efectivamente esto:

public class MyClassMock extends MyClass {
    public MyClassMock() {
        try {
            super(0);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // Mocked methods
}

Pero Java se queja de que super no es la primera afirmación.

Mi solución:

public class MyClassMock extends MyClass {
    public static MyClassMock construct() {
        try {
            return new MyClassMock();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public MyClassMock() throws Exception {
        super(0);
    }

    // Mocked methods
}

¿Es esta la mejor solución?¿Por qué Java no me deja hacer lo primero?


Mi mejor suposición sobre el "por qué" es que Java no quiere permitirme tener un objeto construido en un estado potencialmente inconsistente...sin embargo, al hacer una burla, eso no me importa.Parece que debería poder hacer lo anterior...o al menos sé que lo anterior es seguro para mi caso...o parece que debería serlo de todos modos.

Estoy anulando cualquier método que uso de la clase probada, por lo que no hay riesgo de que esté usando variables no inicializadas.

¿Fue útil?

Solución

Desafortunadamente, los compiladores no pueden trabajar con principios teóricos y, aunque sepa que es seguro en su caso, si lo permitieran, tendría que ser seguro para todos los casos.

En otras palabras, el compilador no solo lo detiene a usted, sino a todos, incluidos aquellos que no saben que no es seguro y necesita un manejo especial.Probablemente también haya otras razones para esto, ya que todos los idiomas suelen tener formas de hacerlo. inseguro cosas si uno sabe cómo afrontarlas.

En C# .NET existen disposiciones similares, y la única forma de declarar un constructor que llama a un constructor base es esta:

public ClassName(...) : base(...)

al hacerlo, se llamará al constructor base antes que el cuerpo del constructor y no se puede cambiar este orden.

Otros consejos

Se hace para evitar que alguien cree una nueva SecurityManager objeto de código que no es de confianza.

public class Evil : SecurityManager {
  Evil()
  {
      try {
         super();
      } catch { Throwable t }
      {
      }
   }
}

Sé que esta es una vieja pregunta, pero me gustó y, como tal, decidí darle mi propia respuesta.Quizás mi comprensión de por qué no se puede hacer esto contribuya a la discusión y a los futuros lectores de su interesante pregunta.

Permítanme comenzar con un ejemplo de construcción de objeto fallida.

Definamos una clase A, tal que:

class A {
   private String a = "A";

   public A() throws Exception {
        throw new Exception();
   }
}

Ahora, supongamos que nos gustaría crear un objeto de tipo A en un try...catch bloquear.

A a = null;
try{
  a = new A();
}catch(Exception e) {
  //...
}
System.out.println(a);

Evidentemente, el resultado de este código será: null.

¿Por qué Java no devuelve una versión parcialmente construida de A?Después de todo, cuando el constructor falla, el objeto name El campo ya ha sido inicializado, ¿verdad?

Bueno, Java no puede devolver una versión parcialmente construida de A porque el objeto no se construyó correctamente.El objeto está en un estado inconsistente y, por lo tanto, Java lo descarta.Su variable A ni siquiera está inicializada, se mantiene como nula.

Ahora, como sabes, para construir completamente un nuevo objeto, primero se deben inicializar todas sus superclases.Si una de las superclases no se ejecutara, ¿cuál sería el estado final del objeto?Es imposible determinar eso.

Mira este ejemplo más elaborado.

class A {
   private final int a;
   public A() throws Exception { 
      a = 10;
   }
}

class B extends A {
   private final int b;
   public B() throws Exception {
       methodThatThrowsException(); 
       b = 20;
   }
}

class C extends B {
   public C() throws Exception { super(); }
}

Cuando el constructor de C se invoca, si se produce una excepción durante la inicialización B, ¿Cuál sería el valor del final? int variable b?

Como tal, el objeto C no se puede crear, es falso, es basura y no está completamente inicializado.

Para mí, esto explica por qué su código es ilegal.

No puedo presumir de tener un conocimiento profundo de los aspectos internos de Java, pero tengo entendido que, cuando un compilador necesita crear una instancia de una clase derivada, primero debe crear la base (y su base antes de eso (...)) y luego aplique las extensiones realizadas en la subclase.

Por lo tanto, ni siquiera existe el peligro de variables no iniciadas ni nada por el estilo.Cuando intentas hacer algo en el constructor de la subclase antes la clase base' constructor, básicamente le estás pidiendo al compilador que extienda una instancia de objeto base que aún no existe.

Editar: En tu caso, Mi clase se convierte en el objeto base, y Mi clase simulada es una subclase.

No sé cómo se implementa Java internamente, pero si el constructor de la superclase genera una excepción, entonces no hay una instancia de la clase que extiendes.Sería imposible llamar a toString() o equals() métodos, por ejemplo, ya que se heredan en la mayoría de los casos.

Java puede permitir un try/catch alrededor de la llamada super() en el constructor si 1.anula TODOS los métodos de las superclases, y 2.no usas la cláusula super.XXX(), pero todo eso me parece demasiado complicado.

Sé que esta pregunta tiene numerosas respuestas, pero me gustaría dar un pequeño dato sobre por qué esto no estaría permitido, específicamente para responder por qué Java no permite hacer esto.Así que aquí tienes...

Ahora, ten en cuenta que super() tiene que ser llamado antes que cualquier otra cosa en el constructor de una subclase, por lo tanto, si usó try y catch bloques alrededor de tu super() llamada, los bloques tendrían que verse así:

try {
   super();
   ...
} catch (Exception e) {
   super(); //This line will throw the same error...
   ...
}

Si súper()fails in theintentarblock, it HAS to be executed first in theatraparblock, so thatsúperruns before anything in your subclasss constructor.Esto te deja con el mismo problema que tenías al principio:si se lanza una excepción, no se detecta.(En este caso, simplemente se vuelve a arrojar al bloque de captura).

Ahora bien, Java tampoco permite el código anterior de ninguna manera.Este código puede ejecutar la mitad de la primera superllamada y luego volver a llamarla, lo que podría causar algunos problemas con algunas superclases.

Ahora, la razón por la que Java no te permite lanzar una excepción. en cambio de llamar super() es porque la excepción podría detectarse en otro lugar y el programa continuaría sin llamar super() en su objeto de subclase, y posiblemente porque la excepción podría tomar su objeto como parámetro e intentar cambiar el valor de las variables de instancia heredadas, que aún no se habrían inicializado.

Una forma de evitarlo es llamando a una función estática privada.Luego, el try-catch se puede colocar en el cuerpo de la función.

public class Test  {
  public Test()  {
     this(Test.getObjectThatMightThrowException());
  }
  public Test(Object o)  {
     //...
  }
  private static final Object getObjectThatMightThrowException()  {
     try  {
        return  new ObjectThatMightThrowAnException();
     }  catch(RuntimeException rtx)  {
        throw  new RuntimeException("It threw an exception!!!", rtx);
     }
  }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top