24 czerwca 2008

SCJP - Przeciążanie metod raz jeszcze

Dzisiejszy artykuł zatytułowałem "SCJP - Przeciążanie metod raz jeszcze", jako że o przeciążaniu (ang. overloading) już pisałem, w artykule "SCJP - Przeciążanie metod". Tym razem mam nadzieję wyczerpać temat.

Jak napisałem w poprzednim artykule – przeciążanie metod to definiowanie wielu metod o tej samej nazwie a różniących się listą argumentów. No dobrze, ale skoro możemy mieć kilka metod o tej samej nazwie, to skąd wiadomo, która z nich zostanie wywołana? O tym właśnie jest dzisiejszy artykuł. Zacznijmy od prostego przykładu.

class Test {
public static void main(String[] args) {
Test test = new Test();

char c = 1;
byte b = 1;
float f = 1;

test.someOp(c);
test.someOp(b);
test.someOp(f);
}

void someOp(short x) {
System.out.println("someOp(short x)");
}

void someOp(long x) {
System.out.println("someOp(long x)");
}

void someOp(double x) {
System.out.println("someOp(double x)");
}
}

Dla typów prostych zasady są następujące: jeśli nie ma metody odpowiadającej idealnie typom parametrów wywołania, to wybierana jest ta metoda, której argumenty są jak najmniej "większe", przy czym "najmniejszy" jest typ ‘byte’ i kolejno ‘short’, ‘int’, ‘long’, ‘float’, ‘double’. Parametry typu ‘char’ traktowane są jakby były typu ‘int’. Wynikiem uruchomienia programu będzie więc:

someOp(long x)
someOp(short x)
someOp(double x)

Co będzie jednak, gdy damy kompilatorowi wybór między "większym" typem prostym a idealnie odpowiadającym typem opakowującym, tak jak to pokazano w kolejnym przykładzie?

class Test {
public static void main(String[] args) {
Test test = new Test();

int i = 1;

test.someOp(i);
}

void someOp(double x) {
System.out.println("someOp(double x)");
}

void someOp(Integer x) {
System.out.println("someOp(Integer x)");
}
}

Kompilator wybierze "większy" typ prosty. Taka jest zasada: konwersja do bardziej "pojemnego" typu prostego ma pierwszeństwo przed opakowywaniem (ang. in-boxing). Tak samo jest z argumentami wielo-arnymi (ang. var-args, variable-arity arguments). Jeśli tylko jest taka możliwość, to kompilator wybierze metodę nie zawierającą wielo-arnych argumentów. A co, jeśli kompilator ma do wyboru tylko opakowanie typu prostego w instancję odpowiadającej mu klasy albo wywołanie metody z argumentem wielo-arnym? Zerknijmy na kolejny przykład.

class Test {
public static void main(String[] args) {
Test test = new Test();

test.someOp(1L);
}

void someOp(long... x) {
System.out.println("someOp(long... x)");
}

void someOp(Long x) {
System.out.println("someOp(Long x)");
}
}

Kompilator zawsze preferuje opakowanie (ang. in-boxing). Uruchomienie powyższego programu spowoduje wyświetlenie napisu "someOp(Long x)". Podsumowując: jeśli tylko się da, kompilator wybiera konwersję typów prostych do innych, "pojemniejszych" typów prostych. W dalszej kolejności, kompilator próbuje znaleźć metodę, która wymaga opakowania w obiekty a dopiero, gdy to zawiedzie, rozważa metody używające argumentów wielo-arnych.

1 komentarz:

Anonimowy pisze...

Dlaczego więc przykład z komentarza poprzedniego artykułu da w wyniku błąd kompilacji?

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

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

static void someOp(Long x) {
}
}

Czy nie powinno być tak, że kompilator zorientuje się, że można opakować zmienną b w typ Long, jeśli nie znajdzie funkcji odpowiadającej idealnie typowi byte?