Wpis z mikrobloga

Podczas porannej sesji przeglądania gorących w mirko natrafiłem na [wpis](http://www.wykop.pl/wpis/13143545/#!$%@?-nie-wierze-co-debuguje-if-this-getid-that-ge/) @LeftShift (i komentarze doń). W pierwszej chwili mocno zastanawiałem się - co tu może nie grać? Kod wygląda jak standardowy kod dla equals() wygenerowany przez IDE, więc takie kwiatki to standard.
Ale! Oprócz zabawnego zwracania wartości boolean, lub porównywania obiektów za pomocą == moze tam sie kryć takze coś ciekawego jesli ID jest typu java.lang.Integer (lub innym autoboxowanym typem). Spójrz na te 3 przykłady:

Integer a = 0;
Integer b = 0;
System.out.println(a == b);

Wyświetli:


Integer c = 100;
Integer d = 100;
System.out.println(c == d);

Wyświetli:


Integer e = 200;
Integer f = 200;
System.out.println(e == f);

Wyświetli:


Czemu tak się dzieje?
"Problemem" jest tutaj sposób w jaki java traktuje wartości prymitywne automatycznie spakowane jako obiekt (tzw. autoboxing). Zauważ, że w powyższym kodzie deklarujemy obiekt typu java.lang.Integer i przypisujemy do niego wartość typu int. Kluczem do zrozumienia powyższego dziwnego zachowania jest to w jaki sposób moze w ogóle takie przypisanie zajść. Otóż po skompilowaniu w miejsce "Integer a = 100;" automatycznie wstawiane jest "Integer a = Integer.valueOf(100);". Sprawdźmy to badając poniższy kod:

public class AutoboxingTest {
public static void main(String []args) {
Integer a = 3;
int b = a;
}
}

Po skompilowaniu i wygenerowaniu bytecode'u przez:

javac AutoboxingTest.java

javap -c AutoboxingTest


Otrzymujemy:

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



public static void main(java.lang.String[]);
Code:
0: iconst_3
1: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: aload_1
6: invokevirtual #3; //Method java/lang/Integer.intValue:()I
9: istore_2
10: return
}

Gdzie jasno widzimy, że po kompilacji w/w magiczny autoboxing to po prostu wywołanie Integer.valueOf() podczas przypisania wartości int do Integer. Ale! To dalej nie tłumaczy dziwnego zachowania pokazanego na pierwszym przykładzie. Spójrzmy zatem co się kryje w kodzie źródłowym metody Integer.valueOf():


public static Integer valueOf(int i) {
final int offset = 128;
if (i >= -128 && i <= 127) { // must cache
return IntegerCache.cache[i + offset];
}
return new Integer(i);
}

Kopiąc głębiej - kod IntegerCache:

private static class IntegerCache {
private IntegerCache(){}
static final Integer cache[] = new Integer[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Integer(i - 128);
}
}

Znaczy to tyle, że wartości Integer z zakresu -128 do 127 są utworzone podczas ładowania klasy IntegerCache do pamięci JVM i przetrzymane w postaci tablicy. Co za tym idzie - porównywanie obiektów Integer mieszczących się w tym zakresie będzie działać złudnie podobnie jak porównywanie prymitywnych typów int, ale to dalej jest wywoływanie == na obiekcie, co oznacza porównywanie referencji, a nie wartości obiektu co mamy w zamyśle. Wartość obiektu zawsze należy porównywać przez wywoływanie equals na danym obiecie.

PS: W Javie 1.6 i wyżej możemy z góry ustalić zakres cache'owanych zmiennych przez ustawienie zmiennej java.lang.Integer.IntegerCache.high


#programowanie #ciekawostkigrizwolda #naukaprogramowania #java
  • Odpowiedz