Przesłanianie metod
W hierarchii klas metoda klasy pochodnej może mieć taki sam typ zwracany i sygnaturę jak metoda w klasie bazowej. Mówimy wtedy o przesłanianiu metody klasy bazowej. Wywołanie przesłoniętej metody przez klasę pochodną spowoduje zawsze wywołanie wersji metody zdefiniowanej w klasie pochodnej. Wersja metody zdefiniowana w klasie bazowej jest niewidoczna w klasie pochodnej. Przeanalizujmy działanie programu przedstawionego na listingu 7.14.
Listing 7.14. Override.java
class A {
int i, j;
A(int a, int b) {
i = a;
j = b;
}
// wyświetla składowe i i j
void show() {
System.out.println("i i j: " + i + " " + j);
}
}
class B extends A {
int k;
B(int a, int b, int c) {
super(a, b);
k = c;
}
// wyświetla składową k — przesłania metodę show() klasy A
void show() { Ta metoda show() należąca do klasy B przesłania metodę zdefiniowaną w klasie A.
System.out.println("k: " + k);
}
}
class Override {
public static void main(String args[]) {
B subOb = new B(1, 2, 3);
subOb.show(); // wywołanie metody show() klasy B
}
}
Wynik działania programu przedstawiłem poniżej:
Wywołanie metody show() dla obiektu typu B spowoduje wykonanie wersji metody show() zdefiniowanej w klasie B. Zatem wersja metody show() zdefiniowana w klasie B przesłania metodę show() zdefiniowaną w klasie A.
Jeśli potrzebujesz dostępu do przesłoniętej wersji metody zdefiniowanej w klasie bazowej, możesz zastosować wywołanie z użyciem słowa kluczowego super. Na przykład w przedstawionej poniżej wersji klasy B wersja metody show() zdefiniowana w klasie bazowej zostaje wywołana przez wersję tej metody zdefiniowaną w klasie pochodnej. W ten sposób wyświetlone zostaną wszystkie składowe obiektu.
int k;
B(int a, int b, int c) {
super(a, b);
k = c;
}
void show() {
super.show(); // wywołuje show() zdefiniowaną w klasie A
System.out.println("k: " + k);
}
}
Jeśli użyjesz tej wersji metody show() w poprzednim programie, to wyświetli on następujące informacje:
k: 3
Wywołanie super.show() oznacza wersję metody show() zdefiniowaną w klasie bazowej.
Przesłanianie metod zachodzi jedynie, gdy sygnatury dwóch metod są identyczne. W przeciwnym razie dwie metody są jedynie przeciążone. Rozważmy zmodyfikowaną wersję poprzedniego programu przedstawioną na listingu 7.15.
Listing 7.15. Overload.java
są przeciążane, a nie przesłaniane. */
class A {
int i, j;
A(int a, int b) {
i = a;
j = b;
}
// wyświetl i i j
void show() {
System.out.println("i i j: " + i + " " + j);
}
}
// Tworzy klasę pochodną klasy bazowej A.
class B extends A {
int k;
B(int a, int b, int c) {
super(a, b);
k = c;
}
// przeciąża metodę show()
void show(String msg) { // Ponieważ sygnatury metod różnią się, ta metoda show() jedynie przeciąża metodę show() zdefiniowaną w klasie bazowej A.
System.out.println(msg + k);
}
}
class Overload {
public static void main(String args[]) {
B subOb = new B(1, 2, 3);
subOb.show("Składowa k: "); // wywołanie metody show() klasy B
subOb.show(); // wywołanie metody show() klasy A
}
}
Wykonanie tej wersji programu spowoduje wyświetlenie następujących informacji:
i i j: 1 2
Wersja metody show() zdefiniowana w klasie pochodnej B ma parametr będący łańcuchem znaków. W ten sposób różni się sygnaturą od wersji metody show() zdefiniowanej w klasie bazowej A, która nie ma żadnego parametru. Zatem w tej sytuacji przesłanianie nie ma miejsca.
Przesłanianie metod i polimorfizm
Poprzednie przykłady stanowiły ilustrację działania mechanizmu przesłaniania metod, ale nie demonstrowały jego zastosowań. Gdyby przesłanianie metod było tylko konwencją związaną z nazwami metod, to byłoby jedynie ciekawostką niezbyt przydatną w praktyce. Jednak przesłanianie metod tworzy fundament jednej z najpotężniejszych koncepcji języka Java: dynamicznego wyboru metod. Mechanizm ten polega na wyborze przesłoniętej metody podczas działania programu, a nie w czasie jego kompilacji. Dynamiczny wybór metody jest ważnym mechanizmem, ponieważ umożliwia implementację polimorfizmu w Javie podczas działania programów.
Omówienie tego mechanizmu rozpocznijmy od przypomnienia ważnej zasady: zmienna referencyjna klasy bazowej może odwoływać się do obiektu klasy pochodnej. Zasadę tę wykorzystano w Javie do wyboru przesłanianych metod podczas wykonywania programu. Jeśli przesłaniana metoda zostanie wywołana za pomocą referencji klasy bazowej, Java wybiera wersję metody na podstawie rzeczywistego typu obiektu, dla którego wywołano metodę. Wybór ten odbywa się dopiero podczas działania programu. Jeśli referencja używana jest dla obiektów różnych typów, to wywoływane są różne wersje przesłoniętej metody. Innymi słowy, o wyborze wersji przesłanianej metody decyduje typ obiektu, a nie typ zmiennej referencyjnej. Jeśli zatem klasa bazowa zawiera metodę przesłoniętą w klasach pochodnych, a zmienna referencyjna klasy bazowej jest używana do odwołań do obiektów różnych typów, to wykonywane będą różne wersje metody.
Działanie dynamicznego wyboru metody ilustruje program przedstawiony na listingu 7.16.
Listing 7.16. DynDispDemo.java
class Sup {
void who() {
System.out.println("who() klasy Sup");
}
}
class Sub1 extends Sup {
void who() {
System.out.println("who() klasy Sub1");
}
}
class Sub2 extends Sup {
void who() {
System.out.println("who() klasy Sub2");
}
}
class DynDispDemo {
public static void main(String args[]) {
Sup superOb = new Sup();
Sub1 subOb1 = new Sub1();
Sub2 subOb2 = new Sub2();
Sup supRef;
supRef = superOb;
supRef.who();
supRef = subOb1;
supRef.who();
supRef = subOb2;
supRef.who();
}
}
W każdym przypadku wersja metody who(), którą należy wywołać, ustalana jest na podstawie typu obiektu podczas działania programu.
Wykonanie tego programu spowoduje wyświetlenie następujących komunikatów:
who() klasy Sub1
who() klasy Sub2
W tym programie zdefiniowano klasę bazową Sup oraz dwie jej klasy pochodne, Sub1 i Sub2. W klasie bazowej Sup została zdefiniowana metoda who(), którą przesłaniają klasy bazowe. Wewnątrz metody main() zadeklarowano obiekty typów Sup, Sub1 i Sub2. A także zmienną referencyjną typu Sup o nazwie supRef. Program przypisuje do tej zmiennej referencje każdego z wymienionych typów obiektu i używa tej zmiennej do wywołania metody who(). Komunikaty wyświetlane przez program dowodzą, że wersja metody who() jest ustalana na podstawie typu obiektu w momencie wywołania, a nie na podstawie typu zmiennej supRef.
Ekspert odpowiada
Pytanie: Przesłanianie metod przypomina mechanizm funkcji wirtualnych w języku C+. Czy rzeczywiście oba mechanizmy są podobne?
Odpowiedź: Tak. Czytelnicy znający C++ zorientują się, że przesłanianie metod w Javie działa podobnie i ma takie same zastosowanie jak funkcje wirtualne w C++.
Java. Przewodnik dla początkujących. Wydanie VI, Autor: Herbert Schildt, Wydawnictwo: Helion