Pergunta

Então, em Java, a primeira linha do seu construtor TEM que ser uma chamada para super...seja chamando implicitamente super() ou chamando explicitamente outro construtor.O que eu quero saber é: por que não posso colocar um bloco try em torno disso?

Meu caso específico é que tenho uma aula simulada para teste.Não existe um construtor padrão, mas quero tornar os testes mais simples de ler.Também quero agrupar as exceções lançadas do construtor em uma RuntimeException.

Então, o que eu quero fazer é efetivamente isto:

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

    // Mocked methods
}

Mas Java reclama que super não é a primeira afirmação.

Minha solução alternativa:

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
}

Esta é a melhor solução alternativa?Por que Java não me permite fazer o primeiro?


Meu melhor palpite sobre o "porquê" é que Java não quer me permitir ter um objeto construído em um estado potencialmente inconsistente...entretanto, ao fazer uma simulação, não me importo com isso.Parece que eu deveria ser capaz de fazer o acima ...ou pelo menos eu sei que o que foi dito acima é seguro para o meu caso ...ou parece que deveria ser de qualquer maneira.

Estou substituindo todos os métodos que uso da classe testada, portanto não há risco de usar variáveis ​​não inicializadas.

Foi útil?

Solução

Infelizmente, os compiladores não podem trabalhar com princípios teóricos, e mesmo que você saiba que é seguro no seu caso, se eles permitissem, teria que ser seguro para todos os casos.

Em outras palavras, o compilador não está parando apenas você, está parando todos, inclusive todos aqueles que não sabem que ele é inseguro e precisa de tratamento especial.Provavelmente há outras razões para isso também, já que todas as línguas geralmente têm maneiras de fazer isso. inseguro coisas se soubermos como lidar com elas.

Em C# .NET existem disposições semelhantes, e a única maneira de declarar um construtor que chama um construtor base é esta:

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

ao fazer isso, o construtor base será chamado antes do corpo do construtor e você não poderá alterar essa ordem.

Outras dicas

Isso é feito para evitar que alguém crie um novo SecurityManager objeto de código não confiável.

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

Sei que esta é uma pergunta antiga, mas gostei e, como tal, decidi dar-lhe uma resposta própria.Talvez minha compreensão de por que isso não pode ser feito contribua para a discussão e para futuros leitores de sua interessante pergunta.

Deixe-me começar com um exemplo de falha na construção de objetos.

Vamos definir uma classe A, tal que:

class A {
   private String a = "A";

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

Agora, vamos supor que gostaríamos de criar um objeto do tipo A em um try...catch bloquear.

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

Evidentemente, a saída deste código será: null.

Por que Java não retorna uma versão parcialmente construída do A?Afinal, no ponto em que o construtor falha, o objeto name campo já foi inicializado, certo?

Bem, Java não pode retornar uma versão parcialmente construída de A porque o objeto não foi construído com sucesso.O objeto está em um estado inconsistente e, portanto, é descartado pelo Java.Sua variável A nem sequer é inicializada, ela é mantida como nula.

Agora, como você sabe, para construir totalmente um novo objeto, todas as suas superclasses devem ser inicializadas primeiro.Se uma das superclasses não fosse executada, qual seria o estado final do objeto?É impossível determinar isso.

Veja este exemplo mais 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(); }
}

Quando o construtor de C é invocado, se ocorrer uma exceção durante a inicialização B, qual seria o valor do final int variável b?

Como tal, o objeto C não pode ser criado, é falso, é lixo, não está totalmente inicializado.

Para mim, isso explica porque o seu código é ilegal.

Não posso presumir que tenho um conhecimento profundo dos aspectos internos do Java, mas entendo que, quando um compilador precisa instanciar uma classe derivada, ele deve primeiro criar a base (e sua base antes disso (...))) e depois aplique as extensões feitas na subclasse.

Portanto, não há sequer o perigo de variáveis ​​não iniciadas ou algo assim.Quando você tenta fazer algo no construtor da subclasse antes a classe base' construtor, você está basicamente pedindo ao compilador para estender uma instância de objeto base que ainda não existe.

Editar: No seu caso, Minha classe torna-se o objeto base, e MinhaClassMock é uma subclasse.

Não sei como o Java é implementado internamente, mas se o construtor da superclasse lançar uma exceção, não haverá uma instância da classe que você estende.Seria impossível ligar para o toString() ou equals() métodos, por exemplo, uma vez que são herdados na maioria dos casos.

Java pode permitir um try/catch em torno da chamada super() no construtor se 1.você substitui TODOS os métodos das superclasses e 2.você não usa a cláusula super.XXX(), mas tudo isso parece muito complicado para mim.

Eu sei que esta pergunta tem inúmeras respostas, mas gostaria de dar uma pequena explicação sobre por que isso não seria permitido, especificamente para responder por que o Java não permite que você faça isso.Então aqui está...

Agora, tenha em mente que super() deve ser chamado antes de qualquer coisa no construtor de uma subclasse, então, se você usou try e catch blocos ao redor do seu super() call, os blocos teriam que ficar assim:

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

Se super()fails in thetentarblock, it HAS to be executed first in thepegarblock, so thatsuperruns before anything in your subclassé construtor.Isso deixa você com o mesmo problema que você teve no início:se uma exceção for lançada, ela não será detectada.(Neste caso, ele é jogado novamente no bloco catch.)

Agora, o código acima também não é permitido pelo Java.Este código pode executar metade da primeira superchamada e depois chamá-la novamente, o que pode causar alguns problemas com algumas superclasses.

Agora, a razão pela qual Java não permite lançar uma exceção em vez de de ligar super() é porque a exceção poderia ser capturada em outro lugar e o programa continuaria sem ligar super() no seu objeto de subclasse, e possivelmente porque a exceção poderia pegar seu objeto como parâmetro e tentar alterar o valor de variáveis ​​de instância herdadas, que ainda não teriam sido inicializadas.

Uma maneira de contornar isso é chamar uma função estática privada.O try-catch pode então ser colocado no corpo da função.

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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top