Różnice w automatycznym rozpakowywaniu między Javą 6 a Javą 7

107

Zauważyłem różnicę w zachowaniu automatycznego rozpakowywania między Java SE 6 i Java SE 7. Zastanawiam się, dlaczego tak jest, ponieważ nie mogę znaleźć żadnej dokumentacji dotyczącej zmian w tym zachowaniu między tymi dwiema wersjami.

Oto prosty przykład:

Object[] objs = new Object[2];
objs[0] = new Integer(5);
int myInt = (int)objs[0];

Kompiluje się dobrze z javac z Java SE 7. Jeśli jednak podam kompilatorowi argument „-source 1.6”, w ostatnim wierszu pojawi się błąd:

inconvertible types
found   : java.lang.Object
required: int

Próbowałem pobrać Java SE 6 do kompilacji z natywnym kompilatorem w wersji 6 (bez żadnej opcji -source). Zgadza się i daje ten sam błąd co powyżej.

Więc co daje? Po dalszych eksperymentach wydaje się, że rozpakowanie w Javie 6 może tylko rozpakować wartości, które wyraźnie (w czasie kompilacji) są typu pudełkowego. Na przykład działa to w obu wersjach:

Integer[] objs = new Integer[2];
objs[0] = new Integer(5);
int myInt = (int)objs[0];

Wygląda więc na to, że między Javą 6 a 7 funkcja rozpakowywania została ulepszona, dzięki czemu może rzucać i rozpakowywać typy obiektów za jednym zamachem, bez wiedzy (w czasie kompilacji), że wartość jest odpowiedniego typu pudełkowego. Jednak czytając specyfikację języka Java lub posty na blogu, które zostały napisane w czasie, gdy pojawiła się Java 7, nie widzę żadnej zmiany w tej kwestii, więc zastanawiam się, na czym ta zmiana polega i jak nazywa się ta „funkcja” ?

Ciekawostka: w związku z tą zmianą możliwe jest wywołanie „niewłaściwego” unboxingu:

Object[] objs = new Float[2];
objs[0] = new Float(5);
int myInt = (int)objs[0];

Kompiluje się dobrze, ale daje ClassCastException w czasie wykonywania.

Jakieś wzmianki na ten temat?

Morty
źródło
17
Ciekawy. Nowy składnik bałaganu autoboxingu. Myślę, że twój przykład mógłby być prostszy i bardziej przejrzysty z pojedynczym obiektem zamiast tablicy. Integer obj = new Integer(2); int x = (int)obj;: działa na Javie 7, wyświetla błąd w Javie 6.
leonbloy
1
Którego JDK używasz? Może to mieć również związek z różnymi dostawcami ...
barfuin
1
@leonbloy: Słuszna uwaga dotycząca uproszczenia, nieco uprościłem to (z mojego oryginalnego kodu), ale w jakiś sposób zatrzymałem się zbyt wcześnie!
Morty
@Thomas: To był najnowszy JDK (dla każdej wersji) firmy Oracle, z którego korzystałem.
Morty
2
Kolejny powód, aby nigdy nie używać autoboxingu.
gyorgyabraham

Odpowiedzi:

92

Wygląda na to, że język w sekcji 5.5 Casting Conversion of Java 7 JLS został zaktualizowany w porównaniu z tą samą sekcją w Java 5/6 JLS , prawdopodobnie w celu wyjaśnienia dozwolonych konwersji.

Mówi Java 7 JLS

Wyrażenie typu referencyjnego może zostać poddane konwersji rzutowania na typ pierwotny bez błędów, przez konwersję rozpakowania.

Java 5/6:

Wartość typu referencyjnego można rzutować na typ pierwotny przez konwersję rozpakowania (§5.1.8).

Java 7 JLS zawiera również tabelę (tabela 5.1) dozwolonych konwersji (ta tabela nie jest uwzględniona w Java 5/6 JLS) z typów referencyjnych na prymitywy. To jawnie wymienia rzuty z Object na prymitywy jako zawężającą konwersję referencyjną z rozpakowywaniem.

Powód jest wyjaśniony w tym e-mailu :

Konkluzja: jeśli specyfikacja pozwala (Object) (int), musi również zezwalać (int) (Object).

Mark Rotteveel
źródło
35

Masz rację; mówiąc prościej:

Object o = new Integer(1234);
int x = (int) o;

Działa to w Javie 7, ale powoduje błąd kompilacji w Javie 6 i starszych. Co dziwne, ta funkcja nie jest wyraźnie udokumentowana; na przykład nie ma o tym mowy tutaj . Dyskusyjne jest, jeśli jest to nowa funkcja lub poprawka błędu (lub nowy błąd?). Zobacz kilka powiązanych informacji i dyskusji . Konsensus wydaje się wskazywać na niejednoznaczność w oryginalnej specyfikacji, która doprowadziła do nieco niepoprawnej / niespójnej implementacji w Javie 5/6, która została naprawiona w 7, ponieważ była krytyczna dla implementacji JSR 292 (Dynamically Typed Languages).

Autoboxing w Javie ma teraz więcej pułapek i niespodzianek. Na przykład

Object obj = new Integer(1234);
long x = (long)obj;

będzie się kompilować, ale zakończy się niepowodzeniem (z ClassCastException) w czasie wykonywania. Zamiast tego zadziała:

long x = (long)(int)obj;

leonbloy
źródło
2
Dziękuję za odpowiedź. Jest jednak jedna rzecz, której nie rozumiem. To jest wyjaśnienie JLS i towarzyszących mu implementacji (por. Dyskusja mailowa), ale dlaczego miałoby to być zrobione w celu dostosowania innych języków maszynowych w JVM? W końcu jest to zmiana języka, a nie maszyny wirtualnej: zachowanie rzutowania maszyny wirtualnej działa tak, jak zawsze, kompilator implementuje tę funkcję przy użyciu istniejącego mechanizmu rzutowania na liczbę Integer i wywoływania .intValue (). Jak więc ta zmiana we właściwym języku Java może pomóc w uruchamianiu innych języków na maszynie wirtualnej? Zgadzam się, że twój link to sugeruje, po prostu się zastanawiam.
Morty