23 czerwca 2008

SCJP - In-boxing i out-boxing

W artykule "SCJP - Klasy opakowujące typów prostych" opisałem klasy opakowujące dla typów prostych. Wspomniałem tam też, że począwszy od Javy 5 nie musimy owego pakowania i rozpakowywania wykonywać samodzielnie, gdyż zrobi to za nas kompilator. Dziś będzie właśnie o tym, o in-boxingu i out-boxingu.

Co to jest in-boxing i out-boxing wyjaśnię na przykładzie. Poniżej pokazana klasa implementuje dwie metody równoważne pod względem wykonywanych operacji, z tą różnicą, że druga z nich używa in- oraz out- (auto-) boxingu a pierwsza nie.

public class Test {
private Integer counter;

public void noAutoBoxing() {
if(counter == null)
counter = new Integer(0);

counter = new Integer(counter.intValue() + 1);
}

public void useAutoBoxing() {
if(counter == null)
counter = 0; // in-boxing

counter++; // out-boxing, inkrementacja, in-boxing
}
}

Zaobserwujmy przy okazji jedną ciekawą rzecz – dzięki zastosowaniu typu obiektowego dla zmiennej ‘counter’, tj. ‘Integer’ a nie ‘int’ mamy możliwość odróżnienia zmiennej nie zainicjalizowanej od zmiennej posiadającej wartość ‘0’, czy inną wybraną wartość. Czasem taka możliwość jest wygodna.

Metoda ‘equals(…)’ dla typów opakowujących działa zgodnie z oczekiwaniami, tj. obiekty uważane są za równe, jeśli są tego samego typu i reprezentują tą samą wartość, ale za to operatory równości (==) i różności (!=) zadziwiają. Zerknijmy na poniższy przykład.

public class Test {
public static void main(String[] args) {
Integer a = 1024; // odpowiada 'Integer a = new Integer(1024);'
Integer b = 1024;

if(a != b)
System.out.println("a i b to różne obiekty");

Integer c = 16;
Integer d = 16;

if(c == d)
System.out.println("c i d to ten sam obiekt");
}
}

O dziwo, zostaną wypisane obydwie sentencje, tj. "a i b to różne obiekty", ale "c i d to ten sam obiekt". Hmmm? Zgadza się, jest to zgodne ze specyfikacją języka, a powodem jest jak można się domyślać chęć optymalizacji zużycia pamięci. Zasada jest taka: jeśli opakowywana (przez automatyczny in-boxing) jest wartość typu ‘boolean’, ‘byte’ lub ‘short’ czy ‘integer’ z przedziału -128 do 127, albo ‘char’ z przedziału \u0000 do \u007f to używana jest dla danego typu i danej wartości zawsze ta sama instancja obiektu – to wyjaśnia, dlaczego "c i d to ten sam obiekt". Co do tego, że "a i b to różne obiekty" to sprawa nie jest jasna. W specyfikacji języka Java wcale nie jest powiedziane, że ‘a’ i ‘b’ muszą odwoływać się do różnych obiektów – przeciwnie, powiedziane jest, że dobrze byłoby gdyby te same wartości zawsze dawały tą samą instancję. To tyle, jeśli chodzi o SCJP a w życiu jak to w życiu; bywa różnie. Powyższy kod uruchomiony na JVM od Sun’a w wersji 1.5.0_11-b03 działa prawidłowo, tj. zgodnie ze specyfikacją, ale już uruchomiony na BEA JRockit w wersji 1.5.0_10-b03 nie, tj. zmienne ‘c’ i ‘d’ wskazują na różne obiekty, co jest wbrew specyfikacji. Czyżby błąd? Dla zainteresowanych – zagadnienia te są omówione w specyfikacji w rozdziale 5.1.7. Poniżej stosowny wyjątek:

"If the value p being boxed is true, false, a byte, a char in the range \u0000 to \u007f, or an int or short number between -128 and 127, then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2."

1 komentarz:

Mariusz Lipiński pisze...

Małe uzupełnienie - czy kompilator jest w stanie wykonać in-boxing do innego, "pojemniejszego" typu? np. z byte na Long? Nie! Poniższy kod się nie skompiluje.

class Test {
public static void main(String[] args) {
byte b = 5;

someOp(b); // błąd!
}

static void someOp(Long x) {
}
}