De ce nu pot folosi un bloc de încercare în jurul apelului meu super ()?

Deci, în Java, prima linie a constructorului dvs. trebuie să fie un apel către super ... fie implicit apelând super (), fie apelând în mod explicit un alt constructor. Ce vreau să știu este, de ce nu pot pune un blocaj în jurul valorii de asta?

Cazul meu specific este că am o clasă machetă pentru un test. Nu există constructor implicit, dar vreau să faci testele mai simple de citit. De asemenea, vreau să împachetez excepțiile aruncate de la constructor într-o RuntimeException.

Deci, ceea ce vreau să fac este în mod efectiv acest lucru:

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

    // Mocked methods
}

Dar Java se plânge că super nu este prima declarație.

Mea solutie:

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
}

Este aceasta cea mai bună soluție? De ce nu mă lasă Java să fac prima?


Cea mai bună estimare cu privire la "de ce" este că Java nu dorește să-mi lase un obiect construit într-o stare care ar putea fi inconsistentă ... totuși, în a face o falsă, nu-mi pasă de asta. Se pare că ar trebui să fie capabil de a face mai sus ... sau cel puțin știu că cele de mai sus este sigur pentru cazul meu ... sau pare ca ar trebui să fie oricum.

Îmi suprascriu orice metodă pe care o folosesc din clasa testată, deci nu există niciun risc că folosesc variabile neinitializate.

0
fr hi bn
Ești sigur că octetul este încă valabil? Îmi amintesc că a fost invalidată după ce cineva a exploatat gaura de securitate rezultată pe care am demonstrat-o mai jos.
adăugat autor Joshua, sursa
O notă interesantă este că aceasta este o limitare a limbajului Java. Echivalentul echivalent este perfect valabil.
adăugat autor Antimony, sursa
Deoarece regulile nu o permit. Citiți JDK spec . Chiar dacă treceți de compilator, verificatorul îl va respinge.
adăugat autor Hot Licks, sursa

7 răspunsuri

Nu pot sa presupun ca am o intelegere profunda a interfetelor Java, dar intelegeti ca atunci cand un compilator trebuie sa instantizeze o clasa derivata, trebuie sa creeze mai intai baza (si baza ei inainte de aceasta ...) și apoi să faceți clic pe extensiile făcute în subclasă.

Deci, nici măcar pericolul variabilelor neintenționate sau altceva de genul acesta nu există. Când încercați să faceți ceva în constructorul subclasa înainte de clasa de bază constructor , solicitați în principiu compilatorului să extindă o instanță de obiect de bază care încă nu există .

Editare: În cazul tău, MyClass devine obiectul de bază și MyClassMock este o subclasă.

0
adăugat

Nu știu cum se implementează Java intern, dar dacă constructorul superclasei aruncă o excepție, atunci nu există o instanță a clasei pe care o extindeți. Ar fi imposibil să apelați metodele toString() sau equals() , de exemplu, deoarece acestea sunt moștenite în majoritatea cazurilor.

Java poate permite o încercare / învârtire în jurul apelului super() în constructor dacă 1. suprascrieți toate metodele de la superclase și 2. nu folosiți clauza super.XXX (), dar toate sunetele sunt prea complicate pe mine.

0
adăugat

Din nefericire, compilatorii nu pot lucra pe principii teoretice și chiar dacă știți că este sigur în cazul dvs., dacă ar permite acest lucru, ar trebui să fie sigur pentru toate cazurile.

Cu alte cuvinte, compilatorul nu se oprește doar pe tine, oprește pe toată lumea, inclusiv pe toți cei care nu știu că este nesigur și are nevoie de manevrare specială. Există probabil și alte motive pentru acest lucru, deoarece toate limbile au de obicei modalități de a face nesigure lucrurile dacă știm cum să le rezolvăm.

În C# .NET există prevederi similare, iar singura modalitate de a declara un constructor care apelează un constructor de bază este acesta:

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

în acest mod, constructorul de bază va fi apelat în fața corpului constructorului și nu puteți modifica această ordine.

0
adăugat
De ce nu vă împiedicați să folosiți acest lucru în blocul de captură? Aceasta se referă la cazul comun al excepțiilor de împachetare.
adăugat autor Antimony, sursa

Sa făcut pentru a împiedica pe cineva să creeze un nou obiect SecurityManager din codul nesigur.

public class Evil : SecurityManager {
  Evil()
  {
      try {
         super();
      } catch { Throwable t }
      {
      }
   }
}
0
adăugat

O modalitate de a ajunge în jurul ei este prin apelarea unei funcții statice private. Captura de încercare poate fi apoi plasată în corpul funcției.

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);
     }
  }
}
0
adăugat
Am elaborat un pic. Destul?
adăugat autor aliteralmind, sursa
Ai grijă să explici de ce?
adăugat autor Unheilig, sursa
De ce apelați aici o funcție statică privată? Și ce este în neregulă cu codul de către OP care credeți că nu funcționează?
adăugat autor Unheilig, sursa
Întrebarea despre moștenire, iar în acest cod există o compoziție
adăugat autor Daniel Pinyol, sursa

Știu că este o întrebare veche, dar mi-a plăcut și, ca atare, am decis să-i dau un răspuns al meu. Poate că înțelegerea mea de ce nu se poate face acest lucru va contribui la discuția și la viitorii cititori a întrebării dvs. interesante.

Permiteți-mi să încep cu un exemplu de construcție defectuoasă a obiectelor.

Să definim o clasă A, astfel încât:

class A {
   private String a = "A";

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

Acum, să presupunem că dorim să creăm un obiect de tip A într-un bloc try ... catch .

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

Evident, rezultatul acestui cod va fi: null .

De ce Java nu returnează o versiune parțial construită a A ? La urma urmei, până la punctul în care constructorul nu reușește, câmpul name al obiectului a fost deja inițializat, nu?

Ei bine, Java nu poate returna o versiune parțial construită a A deoarece obiectul nu a fost construit cu succes. Obiectul este într-o stare inconsistentă și, prin urmare, este aruncat de Java. Variabila dvs. A nu este inițializată, este păstrată ca nulă.

Acum, după cum știți, pentru a construi complet un obiect nou, toate clasele super trebuie să fie inițializate mai întâi. Dacă una dintre super-clase nu a reușit să execute, care ar fi starea finală a obiectului? Este imposibil să se determine acest lucru.

Uitați-vă la acest exemplu mai elaborat

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

Atunci când se invocă constructorul C , dacă se face o excepție în timpul inițializării B , care ar fi valoarea variabilei finale int b ?

Ca atare, obiectul C nu poate fi creat, este fals, este gunoi, nu este complet inițializat.

Pentru mine, acest lucru explică de ce codul dvs. este ilegal.

0
adăugat

Știu că această întrebare are numeroase răspunsuri, dar aș vrea să îmi dau puțină atenție de ce nu ar fi permis acest lucru, în special pentru a răspunde de ce Java nu vă permite să faceți acest lucru. Deci aici te duci ...

Acum, rețineți că super() trebuie apelat înainte de orice altceva în constructorul unei subclase, deci, dacă ați folosit try și catch blochează în jurul apelului super() , blocurile ar trebui să arate astfel:

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

Dacă super() eșuează în blocul try , trebuie executat mai întâi în blocul catch , astfel încât super nimic din constructorul dvs. din subclasa s. Acest lucru vă lasă cu aceeași problemă pe care ați avut-o la început: dacă o excepție este aruncată, nu este prinsă. (În acest caz, este doar aruncat din nou în blocul de captură.)

Acum, nici codul de mai sus nu este permis în nici un fel de Java. Acest cod poate executa jumătate din primul apel în plus și apoi îl sună din nou, ceea ce ar putea cauza unele probleme cu unele super-clase.

Acum, motivul pentru care Java nu vă permite să aruncați o excepție în loc de de a apela super() se datorează faptului că excepția ar putea fi capturată în altă parte și programul va continua < strong> fără a apela super() în obiectul dvs. de subclasă, și probabil că excepția ar putea lua obiectul ca parametru și să încerce să modifice valoarea variabilelor de instanță moștenite au fost inițializate.

0
adăugat