Widening - cecha Javy poniżej 5.0, jest to po prostu poszerzanie zakresu argumentu podanego metodzie. Załóżmy, że metoda przyjmuje argumenty typu int, a my podajemy jej byte, w tym momencie kompilator automatycznie poszerza byte, konwertując go do int, aby sprostać wymaganiom metody. Możliwe jest to ze względu na to, iż byte jest pod przedziałem zakresu liczbowego int, w drugą stronę (int na byte) automatyczna konwersja nie będzie mieć miejsca, a jeżeli ją wymusimy (poprzez rzutowanie), stracimy część danych, bo jak wiadomo int zapisywany jest na 4 bajtach, byte na jednym, rzutując int na byte odcinane są 3 najbardziej znaczące bajty i w efekcie zakres liczb ograniczony jest do min -128, max 127:
class Main {
static void test(int a) {
System.out.println("int");
}
public static void main( String[] args ) {
byte b = 12;
test(b); // na ekranie zostanie wypisane: "int"
}
}
A co w przypadku, kiedy będziemy mieć do czynienia z tzw wrapperem (ang. wrap – opakować), czyli typem prostym opakowanym w obiekt? Jak zachowa się kompilator widząc coś takiego?:
class Main {
static void test(Byte a) {
System.out.println("Byte");
}
static void test(int a) {
System.out.println("int");
}
public static void main( String[] args ) {
byte b = 12;
test(b); // na ekranie zostanie wypisane: "int"
}
}
Dlaczego tak się dzieje, skoro mamy dostępną metodę, która jako argument przyjmuje opakowany typ byte? Otóż programiści implementujący nowe funkcjonalności w Javie 5.0, przyjęli za zasadę, że kod napisany w poprzednich wersjach języka, powinien działać tak, jak w pierwotnych założeniach (kiedy nie było jeszcze mechanizmów typu autoboxing i autounboxing). Tak więc kompilator najpierw próbuje poszerzyć zakres liczby (szuka odpowiedniej metody), a gdy jej nie znajdzie skorzysta z metody, która jako argument przyjmuje obudowany typ prosty (w tym konkretnym przypadku Byte, aby to sprawdzić, wystarczy za komentować przeciążoną metodę static void test(int a)).
Dodać trzeba, że widening wygrywa nie tylko z autoboxingiem, ale też z var-arg, ale czy var-arg wygra z autoboxingiem?
class Main {
static void test(Byte a) {
System.out.println("Byte");
}
static void test(int... a) {
System.out.println("int...");
}
public static void main( String[] args ) {
byte b = 12;
test(b); // na ekranie zostanie wypisane: "Byte"
test(b, b); // na ekranie zostanie wypisane: "int..."
}
}
Jak widać, podczas, gdy jako argument podajemy jedną zmienną typu byte, autoboxing wygrywa. Natomiast, gdy podajemy do metody test dwa argumenty typu byte (lub więcej), kompilator szuka metod gdzie mógłby zastosować widening, potem autoboxing, a na końcu var-arg (w takiej kolejności), dopiero ostatnia spełnia wymagania.
Referencje
W przypadku referencji sprawa jest prosta, jeżeli metoda test oczekiwałaby na argument typu Owoc (Owoc jest klasą), a dostałaby argument typu Jabłko (Jabłko jest klasą dziedziczącą po klasie Owoc), to jeżeli zachodzi relacja IS-A (a w tym przypadku zachodzi), kompilator wykona automatycznie widening i wykonana zosanie metora test(Owoc 0).
Różne kombinacje
Nie można przekazać argumentu typu byte metodzie, która oczekuje argumentu typu Integer, gdyż, co prawda kompilator poprawnie przekonwertowałby go na typ Byte (autoboxing), ale widening na Integer nie jest już możliwi, z tego prostego powodu, że typu Integer, Byte, Short, itp., nie są między sobą w relacji IS-A, są że się tak wyrażę w jednym szeregu. Kompilator wyrzuci zatem wyjątek w miejscu, gdzie metoda test(Integer i) dostaje jako argument typ prosty byte.
Aczkolwiek, jezeli naszą metodę test(Integer i) zmienimy lekko na test(Object o), wszystko byłoby w porządku, wiemy przecież, że w Javie wszystkie klasy dziedziczą po głównej klasie Object, kompilator wykona autoboxing na typ Byte, który jest w relacji IS-A z typem Object, kompilacja zostanie przeprowadzona pomyślnie i metoda będzie mogła być wywoływana bez problemów.
Zasady dla metod przeciążonych, używających wideningu, boxingu i var-arg:
- Widening w przypadku typów prostych, zawsze poszerza do najmniejszego możliwego typu prostego,
- Nie można używać wideningu, pomiędzy dwoma typami opakowującymi typy proste (nie są w relacji IS-A),
- Nie można rozszerzać, a potem opakowywać, (int nie może stać się Long`iem),
- Można za to opakowywać, a następnie rozszerzać (int -> Integer -> Object),
- W przypadku metod przeciążonych boxing i var-arg, używane osobno są poprawne,
- Var-arg działa zarówno z boxingiem, jak i z wideningiem.