2009-11-29

SCJP-6 03. Dvigubas return

Turbūt savaime suprantama, kad Java kompiliatorius neleistų kompiliuoti tokio kodo:

// DoubleReturnWrong.java
class DoubleReturnWrong {
  static int method() {
    return 1000;
    return 2000;
  }
  public static void main(String ... args) {
    System.out.println(method());
  }
}

Kompiliatorius aptiks, kad 5-oje eilutėje esantis return 2000; sakinys yra nepasiekiamas:

% javac DoubleReturnWrong
DoubleReturnWrong.java:5: unreachable statement
    return 2000;
    ^
1 error
Abejonių tikriausiai nekels ir šis kodas:
// DoubleReturn1000
class DoubleReturn1000 {
  static int method() {
    boolean flag = true;

    if (flag) return 1000;

    return 2000;
  }

  public static void main(String ... args) {
    System.out.println(method());
  }
}
Kadangi metodas vykdomas iki pirmojo sutikto return sakinio, ir po to priverstinai nutraukiamas, šiuo atveju method() grąžins reikšmę 1000.

Dabar -- subtilesnė situacija:

// DoubleReturn.java
class DoubleReturn {

  static int method() {
    try {
      return 1000;
    } finally {
      return 3000;
    }
}

  public static void main(String ... args) {
    System.out.println(method());
  }
}

1000 ar 3000? Pagal ankstesnį pavyzdį būtų logiška tikėtis, kad rezultatas -- 1000. T.y. grąžinama pirmo pasiekto return reiškmė. Tačiau šiuo atveju antrasis return yra finally bloke, kuris privalo būti įvykdytas bet kuriuo atveju, išskyrus System.exit() ar panašų visišką programos nutraukimą ankstesnėje try {...} arba catch(...) {...} dalyje, esančioje prieš finally bloką. Taigi, po return 1000 vykdymas pratęsiamas finally dalyje, kurioje tėra kitas return sakinys su reikšme 3000. Kaip elgsis JVM šiuo atveju? Ar šiuo atveju bus kaip su finalize() metodu (prisimenam, kad finalize gali būti JVM automatiškai įvykdytas ne daugiau kaip vieną kartą. T.y. nėra ribojama, kiek kartų jis bus iškviestas iš kodo naudojant tiesioginį kvietinį, pvz. myObject.finalize(), tačiau nepriklausomai nuo to JVM vis tiek gali vieną kartą iškviesti šį metodą prieš objekto "sunaikinimą", bet ne daugiau. Su finalize metodu yra niuansas -- metodas automatiškai iškviečiamas prieš objektą sunaikinant, tačiau šio metodo kodas gali sukurti "gyvą" nuorodą į objektą, ir taip apsaugoti jį nuo sunaikinimo. Tačiau tokiu atveju, kai objektas vėl pataps potencialiai naikinamu, JVM daugiau finalize metodo jam neiškvietinės), t.y. JVM yra "pažymėjus", kad metode jau buvo įvykdytas return sakinys?
Atsakymas -- ne. Rezultatas bus 3000. Kyla klausimas -- o kodėl? Patikrinkim "išdizassemblintą" kodą:

% javap DoubleReturn
Compiled from "DoubleReturn.java"

public class DoubleReturn extends java.lang.Object{
public DoubleReturn();
   Code:
    0: aload_0
    1: invokespecial #1; //Method java/lang/Object."":()V
    4: return

static int method();
   Code:
    0: sipush 1000
    3: istore_0
    4: sipush 3000
    7: ireturn
    8: astore_1
    9: sipush 3000
    12: ireturn

   Exception table:
    from   to  target type
     0     4     8    any
     8     9     8    any

public static void main(java.lang.String[]);
   Code:
     0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
     3: invokestatic #3; //Method method:()I
     6: invokevirtual #4; //Method java/io/PrintStream.println:(I)V
     9: return
}
Kol kas nesu Java bytecode specialistas, tačiau iš aukščiau pateikto listingo aiškėja seka, kuria vykdomas metodas method:
1. į steką įdedama 1000 (0-is metodo kodo adresas, sipush 1000)
2. šokama į 4-ą adresą pagal Exception Table (šioje vietoje spėju, kad taip vyksta)
3. į steką įdedama 3000 (4-as kodo adresas, sipush 3000)
4. grįžtama iš metodo su reikšme steke -- 3000 (7-ame kodo adrese esanti komanda ireturn)

Taigi kaip matome iš sukompiliuoto bytecode, return reikšmė yra talpinama steke, ir paskutinė iš jų -- grąžinama.
Egzamine neteko matyti šį "kabliuką" turinčio klausimo, bet tai nereiškia, kad negali pakliūti kam nors kitam.

Komentarų nėra: