2010-04-09 22 views
11

To nie jest poprawny kod:Wracając w statycznego inicjatora

public class MyClass 
{ 
    private static boolean yesNo = false; 

    static 
    { 
     if (yesNo) 
     { 
      System.out.println("Yes"); 
      return; // The return statement is the problem 
     } 
     System.exit(0); 
    } 
} 

To głupi przykład, ale w statycznym konstruktorze klasy nie możemy return;. Dlaczego? Czy istnieją ku temu dobre powody? Czy ktoś wie coś więcej na ten temat?

Powodem, dla którego powinienem wykonać return, jest zakończenie budowy.

Dzięki

+4

Są to nazywane inicjalizatory statyczne, a nie konstruktory statyczne. Po prostu piszę to, aby pomóc w wyszukiwaniu. – Oak

+0

@Oak: Dzięki (15 znaków) –

Odpowiedz

15

Myślę, że powodem jest, że inicjalizatory są przenoszone razem z inicjalizacjami pól (i z konstruktorami, w przypadku inicjalizatorów). Innymi słowy, JVM rozpoznaje tylko jedno miejsce do inicjalizacji pól statycznych, a zatem wszystkie inicjalizacje - czy to w blokach, czy nie - muszą być tam wykonywane.

Tak więc, na przykład, kiedy piszesz klasę:

class A { 
    static int x = 3; 
    static { 
     y = x * x; 
    } 
    static int z = x * x; 
} 

Wtedy to faktycznie jakby napisałeś:

class A { 
    static int x, y, z; 
    static { 
     x = 3; 
     y = x * x; 
     z = x * x; 
    } 
} 

Potwierdzają to jeśli spojrzeć na demontażu:

static {}; 
    Code: 
    0: iconst_3 
    1: putstatic  #5; //Field x:I 
    4: getstatic  #5; //Field x:I 
    7: getstatic  #5; //Field x:I 
    10: imul 
    11: putstatic  #3; //Field y:I 
    14: getstatic  #5; //Field x:I 
    17: getstatic  #5; //Field x:I 
    20: imul 
    21: putstatic  #6; //Field z:I 
    24: return 

Więc jeśli dodalibyśmy "return" gdzieś w środku statycznego inicjalizatora, to również e uniemożliwiło obliczenie z.

+1

Już miałem to napisać, kiedy to zobaczyłem. Zauważ też, że możesz mieć wiele statycznych bloków inicjalizujących w klasie. –

+0

Naprawdę dobra odpowiedź, Dąb. Pokazuje również, dlaczego nagłe zakończenie jest uważane za tak źle w statycznym inicjalizatorze (jak wskazał Joe w komentarzu), aby dać błąd podczas kompilacji. – jalopaba

2

Do czego powinieneś wrócić? W statycznym inicjalizatorze nie ma wywołującego, więc powrót nie ma sensu, o ile widzę. Inicjatory statyczne są wykonywane, gdy klasa jest ładowana po raz pierwszy.

10
  • przepływ program może zawsze być tak skonstruowane, aby przejść bez potrzeby return. (W przykładzie oddanie System.exit(0) w klauzuli else by osiągnąć pożądany rezultat)

  • w naprawdę potrzebne, można przenieść kod w metodzie statycznej i nazywają go od inicjatora:

.

static { 
    staticInit(); 
} 

private static void staticInit() { 
    if (yesNo) { 
     System.out.println("Yes"); 
     return; 
    } 
    System.exit(0); 
} 

Zauważ, że nie jest to statyczny konstruktor, to statyczny initializer. Nic się nie buduje.

+0

Wiem, że możesz go rozwiązać za pomocą prostego 'else'. Ale powiedziałem, że to bardzo głupi przykład. Ale to jest prawda! –

+0

@Martijn Courteaux tak, zrozumiałem to. Dlatego jest w nawiasach, tylko przykład mojego uogólnienia. – Bozho

1

Od JSL regarding static initializers:

„To jest błąd czasu kompilacji dla statycznego inicjatora, aby móc zakończyć nagle (§14.1, §15.6) o sprawdzonej wyjątku (§11.2) Jest to compile-. błąd czasu, jeśli statyczny inicjator nie może zakończyć się normalnie (§14.21). "

Abrupt completion (między innymi): „powrót bez wartości”, „powrót z danej wartości”, itp

Więc return w statycznego inicjatora jest „nagłe zakończenie” i tworzy compile- błąd czasu.

+1

JSL nie określa jednak przyczyny, o to właśnie chodzi. – Oak

+1

Już miałem opublikować to samo, ale starałem się myśleć: dlaczego "gwałtowne zakończenie" statycznego inicjalizatora jest tak złe, że spowodowało błąd kompilacji? –

+0

@Oak: Masz rację, nie ma powodu. Specyfikacje są czasami jak aksjomaty. – jalopaba

0

Rozumiem, że reguła dla inicjalizatorów statycznych polega na tym, że są one wykonywane tylko raz, po wczytaniu kodu bajtowego klasy i przed wykonaniem dowolnej statycznej metody lub utworzeniu pierwszego obiektu z klasy. JLS gwarantuje, że inicjalizacja zostanie ukończona. Aby zagwarantować, że ta gwarancja jest prawdziwa, JLS określa również, że kod nie mógł zostać nagle zakończony (co wyraźnie podano w innej odpowiedzi).

Należy pamiętać, że możliwe jest ładowanie kodu bajtowego bez jego inicjalizacji; patrz metoda Class.forName(String, boolean, ClassLoader). Jeśli parametr boolean to false, to spowoduje załadowanie klasy, ale nie zainicjowanie jej. Programista może jeszcze trochę przemyśleć, aby odkryć informacje o tej klasie bez jej zainicjowania. Jednak po próbie użycia klasy bezpośrednio przez wywołanie metody statycznej lub instancji instancji JVM rozpocznie ją najpierw.

Jeśli jakikolwiek ze statycznych inicjalizatorów nagle przerwie działanie - co może się zdarzyć przy RuntimeException, klasa pozostanie w niepoprawnym stanie. Po raz pierwszy JVM rzuci ExceptionInInitializeError (zauważ, że jest to Error, co oznacza, że ​​jest uznawana za awarię wewnętrzną). Od tego momentu nie będzie możliwe użycie klasy - próba wywołania statycznej metody lub utworzenie instancji obiektu spowoduje, że otrzymasz numer NoClassDefFoundError.

Jedynym sposobem na odzyskanie tej sytuacji bez restartowania maszyny JVM jest użycie ClassLoader s oraz zastąpienie programu ładującego klasy błędną klasą i przebudowanie klasy lub reinicjalizatora w innym środowisku (prawdopodobnie w różnych właściwościach systemu), ale program musi być dobrze przygotowany do tego rodzaju sytuacji.

0

Chciałbym zmienić kolejność oświadczeń, dzięki czemu będzie prostsze/krótsze. Nigdy nie będzie dobrego przypadku, w którym obie gałęzie if/else potrzebują zwrotu.

static { 
    if (!yesNo) 
     System.exit(0); // silently exiting a program is a bad idea!" 
    System.out.println("Yes"); 
} 
Powiązane problemy