Question

Ainsi, en Java, la première ligne de votre constructeur DOIT être un appel à super...que ce soit en appelant implicitement super() ou en appelant explicitement un autre constructeur.Ce que je veux savoir, c'est pourquoi ne puis-je pas mettre un bloc try autour de cela ?

Mon cas spécifique est que j'ai une classe simulée pour un test.Il n'y a pas de constructeur par défaut, mais j'en veux un pour rendre les tests plus simples à lire.Je souhaite également envelopper les exceptions levées par le constructeur dans une RuntimeException.

Donc, ce que je veux faire, c'est effectivement ceci :

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

    // Mocked methods
}

Mais Java se plaint que super n'est pas la première instruction.

Ma solution de contournement :

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
}

Est-ce la meilleure solution de contournement ?Pourquoi Java ne me permet-il pas de faire la première ?


Ma meilleure hypothèse quant au "pourquoi" est que Java ne veut pas me laisser avoir un objet construit dans un état potentiellement incohérent...cependant, en faisant une simulation, cela ne m'importe pas.Il semble que je devrais être capable de faire ce qui précède...ou du moins je sais que ce qui précède est sans danger pour mon cas...ou il semble que cela devrait l'être de toute façon.

Je remplace toutes les méthodes que j'utilise de la classe testée, il n'y a donc aucun risque d'utiliser des variables non initialisées.

Était-ce utile?

La solution

Malheureusement, les compilateurs ne peuvent pas fonctionner sur des principes théoriques, et même si vous savez que c'est sûr dans votre cas, s'ils l'autorisaient, cela devrait être sûr dans tous les cas.

En d’autres termes, le compilateur n’arrête pas seulement vous, il arrête tout le monde, y compris tous ceux qui ne savent pas qu’il n’est pas sûr et nécessite un traitement spécial.Il y a probablement d'autres raisons à cela, car toutes les langues ont généralement des moyens de le faire. peu sûr choses si l'on sait comment les gérer.

En C# .NET, il existe des dispositions similaires, et la seule façon de déclarer un constructeur qui appelle un constructeur de base est la suivante :

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

ce faisant, le constructeur de base sera appelé avant le corps du constructeur, et vous ne pourrez pas modifier cet ordre.

Autres conseils

C'est fait pour empêcher quelqu'un de créer un nouveau SecurityManager objet à partir d’un code non fiable.

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

Je sais que c'est une vieille question, mais je l'ai aimée et, en tant que telle, j'ai décidé d'y répondre moi-même.Peut-être que ma compréhension des raisons pour lesquelles cela ne peut pas être fait contribuera à la discussion et aux futurs lecteurs de votre question intéressante.

Permettez-moi de commencer par un exemple de construction d’objet défaillante.

Définissons une classe A, telle que :

class A {
   private String a = "A";

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

Supposons maintenant que nous souhaitions créer un objet de type A dans un try...catch bloc.

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

Évidemment, le résultat de ce code sera : null.

Pourquoi Java ne renvoie pas une version partiellement construite de A?Après tout, au moment où le constructeur échoue, l'objet name le champ a déjà été initialisé, n'est-ce pas ?

Eh bien, Java ne peut pas renvoyer une version partiellement construite de A car l'objet n'a pas été construit avec succès.L'objet est dans un état incohérent et est donc rejeté par Java.Votre variable A n'est même pas initialisée, elle est conservée nulle.

Maintenant, comme vous le savez, pour construire entièrement un nouvel objet, toutes ses super classes doivent d'abord être initialisées.Si l’exécution de l’une des super classes échouait, quel serait l’état final de l’objet ?Il est impossible de le déterminer.

Regardez cet exemple plus élaboré

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(); }
}

Lorsque le constructeur de C est invoqué si une exception se produit lors de l'initialisation B, quelle serait la valeur de la finale int variable b?

En tant que tel, l’objet C ne peut pas être créé, il est faux, c’est une poubelle, il n’est pas complètement initialisé.

Pour moi, cela explique pourquoi votre code est illégal.

Je ne peux pas prétendre avoir une compréhension approfondie des composants internes de Java, mais je crois comprendre que, lorsqu'un compilateur doit instancier une classe dérivée, il doit d'abord créer la base (et sa base avant cela (...)) puis appliquez les extensions réalisées dans la sous-classe.

Ce n'est donc même pas le danger des variables non initiées ou quoi que ce soit du genre.Lorsque vous essayez de faire quelque chose dans le constructeur de la sous-classe avant la classe de base' constructeur, vous demandez essentiellement au compilateur d'étendre une instance d'objet de base qui n'existe pas encore.

Edit : Dans votre cas, Ma classe devient l'objet de base, et MaClassMock est une sous-classe.

Je ne sais pas comment Java est implémenté en interne, mais si le constructeur de la superclasse lève une exception, alors il n'y a pas d'instance de la classe que vous étendez.Il serait impossible d'appeler le toString() ou equals() par exemple, puisqu’elles sont héritées dans la plupart des cas.

Java peut autoriser un try/catch autour de l'appel super() dans le constructeur si 1.vous remplacez TOUTES les méthodes des superclasses, et 2.vous n'utilisez pas la clause super.XXX(), mais tout cela me semble trop compliqué.

Je sais que cette question a de nombreuses réponses, mais j'aimerais expliquer pourquoi cela ne serait pas autorisé, en particulier pour expliquer pourquoi Java ne vous permet pas de faire cela.Alors voilà...

Maintenant, gardez à l'esprit que super() doit être appelé avant toute autre chose dans le constructeur d'une sous-classe, donc, si vous avez utilisé try et catch des blocs autour de votre super() call, les blocs devraient ressembler à ceci :

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

Si super()fails in theessayerblock, it HAS to be executed first in theattraperblock, so thatsuperruns before anything in your subclasss constructeur.Cela vous laisse avec le même problème qu’au début :si une exception est levée, elle n'est pas interceptée.(Dans ce cas, il est simplement lancé à nouveau dans le bloc catch.)

Désormais, le code ci-dessus n’est en aucun cas autorisé par Java non plus.Ce code peut exécuter la moitié du premier super appel, puis l'appeler à nouveau, ce qui pourrait poser des problèmes avec certaines super classes.

Maintenant, la raison pour laquelle Java ne vous permet pas de lever une exception plutôt d'appeler super() c'est parce que l'exception pourrait être interceptée ailleurs et que le programme continuerait sans appeler super() sur votre objet de sous-classe, et peut-être parce que l'exception pourrait prendre votre objet comme paramètre et tenter de modifier la valeur des variables d'instance héritées, qui n'auraient pas encore été initialisées.

Une façon de contourner ce problème consiste à appeler une fonction statique privée.Le try-catch peut ensuite être placé dans le corps de la fonction.

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);
     }
  }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top