Metoda Java z typem return kompiluje bez instrukcji return

Pytanie 1:

Dlaczego poniższy kod kompiluje się bez instrukcji return?

public int a() {
    while(true);
}

Notice: If I add return after the while then I get an Unreachable Code Error.

Pytanie 2:

Z drugiej strony, dlaczego poniższy kod kompiluje się,

public int a() {
    while(0 == 0);
}

Mimo, że poniższe nie.

public int a(int b) {
    while(b == b);
}
Author: Willi Mentzel, 2015-06-25

3 answers

Pytanie 1:

Dlaczego poniższy kod kompiluje się bez instrukcji return?

public int a() 
{
    while(true);
}

To jest objęte JLS§8.4.7:

Jeśli metoda jest zadeklarowana jako typ zwracany (§8.4.5), to błąd w czasie kompilacji występuje, jeśli ciało metody może normalnie działać (§14.1).

Innymi słowy, metoda z typem return musi zwracać tylko za pomocą instrukcji return, która dostarcza wartość return; metoda nie może "upuścić końca ciała". Szczegółowe zasady dotyczące instrukcji return w ciele metody znajdują się w §14.17.

Możliwe jest, że metoda ma typ return i nie zawiera żadnych instrukcji return. Oto jeden przykład:

class DizzyDean {
    int pitch() { throw new RuntimeException("90 mph?!"); }
}

Ponieważ kompilator wie, że pętla nigdy się nie zakończy (true jest oczywiście zawsze prawdziwa), wie, że funkcja nie może "powrócić normalnie" (zrzucić koniec jej ciała), a zatem jest w porządku, że nie ma return.

Pytanie 2:

Z drugiej strony, dlaczego poniższy kod się kompiluje,

public int a() 
{
    while(0 == 0);
}

Mimo, że poniższe nie.

public int a(int b)
{
    while(b == b);
}

W przypadku 0 == 0 kompilator wie, że pętla nigdy się nie zakończy (że 0 == 0 zawsze będzie prawdą). Ale to Nie wie, że dla b == b.

Dlaczego nie?

Kompilator rozumie wyrażenia stałe (§15.28). Cytowanie §15.2-formy wyrażeń (bo o dziwo to zdanie nie jest w §15.28):

Niektóre wyrażenia mają wartość, którą można określić podczas kompilacji. Są to wyrażenia stałe (§15.28).

W twoim przykładzie b == b, ponieważ istnieje zmienna, nie jest ona wyrażeniem stałym i nie jest określona do określenia w czasie kompilacji. My widzimy, że w tym przypadku zawsze będzie to prawdą (chociaż jeśli b były double, Jak zauważył QBrute , możemy łatwo dać się zwieść Double.NaN, czyli , a nie == samym ), ale JLS określa tylko, że stałe wyrażenia są ustalane w czasie kompilacji, nie pozwala kompilatorowi na próbę oceny niestałych wyrażeń. bayou.io poruszyłem słuszną kwestię dlaczego nie: jeśli zaczniesz próbować określić wyrażenia zawierające zmienne w czasie kompilacji, gdzie się zatrzymasz? b == b jest oczywiste (er, dla wartości nie NaN), ale co z a + b == b + a? Albo (a + b) * 2 == a * 2 + b * 2? Rysowanie linii w stałych ma sens.

Ponieważ nie określa wyrażenia, kompilator nie wie, że pętla nigdy się nie zakończy, więc uważa, że metoda może powrócić normalnie - co nie jest dozwolone, ponieważ wymagane jest użycie return. Skarży się więc na brak return.

 272
Author: T.J. Crowder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:43

Interesujące może być myślenie o metodzie zwracającej Typ nie jako obietnicy zwracania wartości określonego typu, ale jako obietnicy nie zwracania wartości, która jest nie określonego typu. W związku z tym, jeśli nigdy nic nie zwracasz, nie łamiesz obietnicy, a więc każda z poniższych rzeczy jest legalna: {]}

  1. Looping forever:

    X foo() {
        for (;;);
    }
    
  2. Recursing forever:

    X foo() {
        return foo();
    }
    
  3. Wyrzucanie wyjątek:

    X foo() {
        throw new Error();
    }
    

(uważam, że rekurencja jest zabawna: kompilator wierzy, że metoda zwróci wartość typu X (cokolwiek to jest), ale nie jest to prawda, ponieważ nie ma kodu, który miałby pomysł, jak utworzyć lub pozyskać X.)

 32
Author: Boann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-30 03:21:08

Patrząc na kod bajtowy, jeśli to, co jest zwracane, nie pasuje do definicji, otrzymasz błąd kompilacji.

Przykład:

for(;;) wyświetli bajtodes:

L0
    LINENUMBER 6 L0
    FRAME SAME
    GOTO L0

Zwróć uwagę na brak jakiegokolwiek return bytecode

To nigdy nie uderza w Zwrot, a tym samym nie zwraca niewłaściwego typu.

Dla porównania metoda taka jak:

public String getBar() { 
    return bar; 
}

Zwróci następujące bajtowe kody:

public java.lang.String getBar();
    Code:
      0:   aload_0
      1:   getfield        #2; //Field bar:Ljava/lang/String;
      4:   areturn

Zwróć uwagę na "areturn", który oznacza "return a reference"

Teraz jeśli zrobimy co następuje:

public String getBar() { 
    return 1; 
}

Zwróci następujące bajtowe kody:

public String getBar();
  Code:
   0:   iconst_1
   1:   ireturn

Teraz widzimy, że typ w definicji nie pasuje do zwracanego typu ireturn, co oznacza return int.

Tak naprawdę sprowadza się to do tego, że jeśli metoda ma ścieżkę zwrotną, to ta ścieżka musi być zgodna z typem zwrotnym. Są jednak instancje w kodzie bajtowym, w których nie jest generowana ścieżka powrotna, a tym samym nie ma łamania reguły.

 8
Author: Philip Devine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-30 22:02:15