Polimorfizm
Monika Wrzosek (IM UG) Programowanie obiektowe 137 / 148
Konwersje typów prostych
i n t a = 9 / 2 ; // k o n w e r s j a a u t o m a t y c z n a ; w y n i k : 4 i n t b = (i n t) 9 / 2 // k o n w e r s j a j a w n a ; w y n i k : 4
Poniższy program zakończy się błędem kompilacji
p u b l i c c l a s s Main {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { f l o a t f = 0 ;
i n t i = f ; // b ł ą d ! e r r o r : i n c o m p a t i b l e t y p e s }
}
Jeśli zmiennej, która reprezentuje węższy zakres wartości (np. int),
przypisuje się zmienną mogącą przedstawiać szerszy zakres wartości (np. float), trzeba zawsze dokonać jawnej konwersji typu (o ile jest ona możliwa):
p u b l i c c l a s s Main {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { f l o a t f = 0 ;
i n t i = (i n t) f ; //OK
} }
Monika Wrzosek (IM UG) Programowanie obiektowe 138 / 148
Konwersje typów prostych
Sytuacja odwrotna:
zmiennej typu float (reprezentującej szerszy zakres wartości) próbujemy przypisać wartość zmiennej typu int (reprezentującej węższy zakres wartości).
Wykonana zostanie konwersja automatyczna:
p u b l i c c l a s s Main {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { i n t i = 5 ;
f l o a t f = i ; //OK
} }
Instrukcję
f l o a t f = i ;
kompilator potraktuje tak, jakby miała ona postać:
f l o a t f = (f l o a t) i ;
Monika Wrzosek (IM UG) Programowanie obiektowe 139 / 148
Rzutowanie typów obiektowych
Jeśli oczekiwany jest argument klasy X, a podany zostanie argument klasy Y, która jest klasą potomną dla X, błędu nie ma (konwersja automatyczna typu obiektu).
p u b l i c c l a s s Punkt { p u b l i c i n t x , y ; }
p u b l i c c l a s s Punkt3D e x t e n d s Punkt { p u b l i c i n t z ;
}
p u b l i c c l a s s Main {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { Punkt P ;
P = new Punkt3D ( ) ;
P . x = 2 ; //OK
P . y = 3 ; //OK
P . z = 5 ; //B ł ą d !
} }
Dla kompilatora typem zmiennej P jest Punkt, a w klasie Punkt nie ma pola z.
Monika Wrzosek (IM UG) Programowanie obiektowe 140 / 148
Rzutowanie typów obiektowych
p u b l i c c l a s s Main {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { Punkt P ;
P = new Punkt3D ( ) ;
P . x = 2 ; //OK
P . y = 3 ; //OK
P . z = 5 ; //B ł ą d !
} }
Instrukcja:
P = new Punkt3D ( ) ;
jest przez kompilator rozumiana jako:
P = ( Punkt ) new Punkt3D ( ) ;
Przypomina to konwersje typów prostych, jednak znaczenie jest nieco inne.
Jest to informacja dla kompilatora:
traktuj obiekt klasy Punkt3D tak, jakby był on klasy Punkt.
Obiekt Punkt3D nie zmienia się ani nie traci żadnych informacji.
Monika Wrzosek (IM UG) Programowanie obiektowe 141 / 148
Rzutowania można dokonać również na zmiennych już istniejących, np.:
Punkt3D punkt3D = new Punkt3D ( ) ; Punkt p u n k t = ( Punkt ) punkt3D ;
To również dowód, że dokonuje się tu rzutowania typów, a nie konwersji.
Przypomnijmy konwersję typów prostych:
f l o a t f = 4 . 5 ; i n t i = (i n t) f ;
Część ułamkowa zostanie tu bezpowrotnie utracona i nawet powtórna konwersja na typ float nie przywróci poprzedniej wartości:
p u b l i c c l a s s Main {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { f l o a t f = 4 . 5 ;
i n t i = (i n t) f ; f l o a t f f = (f l o a t) i ;
S y s t e m . o u t . p r i n t l n (" f = " + f ) ; S y s t e m . o u t . p r i n t l n (" i = " + i ) ; S y s t e m . o u t . p r i n t l n (" f f = " + f f ) ; }}
f = 4 . 5
i = 4
f f = 4 . 0
W przypadku zmiennych obiektowych będzie zupełnie inaczej.
Monika Wrzosek (IM UG) Programowanie obiektowe 142 / 148p u b l i c c l a s s Main {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { Punkt3D p3D1 = new Punkt3D ( ) ;
p3D1 . x = 1 ; p3D1 . y = 2 ; p3D1 . z = 3 ; S y s t e m . o u t . p r i n t l n (" p3D1 : ") ;
S y s t e m . o u t . p r i n t l n (" x = " + p3D1 . x ) ; S y s t e m . o u t . p r i n t l n (" y = " + p3D1 . y ) ; S y s t e m . o u t . p r i n t l n (" z = " + p3D1 . z ) ; S y s t e m . o u t . p r i n t l n (" ") ;
Punkt p u n k t = ( Punkt ) p3D1 ; S y s t e m . o u t . p r i n t l n (" p u n k t ") ;
S y s t e m . o u t . p r i n t l n (" x = " + p u n k t . x ) ; S y s t e m . o u t . p r i n t l n (" y = " + p u n k t . y ) ; S y s t e m . o u t . p r i n t l n (" ") ;
Punkt3D p3D2 = ( Punkt3D ) p u n k t ; S y s t e m . o u t . p r i n t l n (" p3D2 ") ;
S y s t e m . o u t . p r i n t l n (" x = " + p3D2 . x ) ; S y s t e m . o u t . p r i n t l n (" y = " + p3D2 . y ) ; S y s t e m . o u t . p r i n t l n (" z = " + p3D2 . z ) ; }}
p3D1 : x = 1 y = 2 z = 3
p u n k t : x = 1 y = 2
p3D2 : x = 1 y = 2 z = 3
Widzimy, że rzutowanie nie zmienia stanu obiektu. Konwersje nie spowodowały utraty części danych.
Monika Wrzosek (IM UG) Programowanie obiektowe 143 / 148
Klasa Object
W Javie wszystkie klasy dziedziczą bezpośrednio lub pośrednio po klasie Object.
Jeśli definicja nowej klasy bazowej wygląda następująco
p u b l i c c l a s s n a z w a _ k l a s y {}
kompilator potraktuje ten fragment jako:
p u b l i c c l a s s n a z w a _ k l a s y e x t e n d s O b j e c t { }
Zatem każda klasa dziedziczy wszystkie metody i pola klasy Object.
Metoda toString zwraca opis obiektu w postaci ciągu znaków (formalnie: w postaci obiektu klasy String).
p u b l i c c l a s s M o j a K l a s a {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { M o j a K l a s a mk = new M o j a K l a s a ( ) ;
S y s t e m . o u t . p r i n t l n (mk ) ; }
}
Przykładowy wynik:
MojaKlasa@15db9742
Monika Wrzosek (IM UG) Programowanie obiektowe 144 / 148
Rzeczywisty typ obiektu
Rzutowanie obiektów jest możliwe w obie strony:
- obiekt klasy potomnej można rzutować na obiekt klasy bazowej (rzutowanie w górę), - obiekt klasy bazowej można rzutować na obiekt klasy potomnej (rzutowanie w dół) - bardziej skomplikowane.
p u b l i c c l a s s Main {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { Punkt p = new Punkt ( ) ;
Punkt3D p3D = ( Punkt3D ) p ;
p3D . z = 5 ; //B ł ą d 1
p3D . x = 5 ; //B ł ą d 2
}}
Błąd 1 - oczywisty, obiekt klasy Punkt nie ma pola z.
Błąd 2 - nieoczywisty, przecież obiekt na który wskazuje zmienna p3D, zawiera pole x.
To jednak nie ma znaczenia, bo sam obiekt jest innej klasy niż Punkt3D.
Maszyna wirtualna sprawdza zgodność klas. Zmienna wskazująca na obiekt musi być tej samej klasy lub klasy nadrzędnej do klasy tego obiektu - nigdy odwrotnie.
Polimorfizm/późne wiązanie/dynamiczne wiązanie (ang. late/dynamic binding ) Sprawdzanie rzeczywistego (a nie deklarowanego) typu obiektu w trakcie działania programu.
W przypadku rzutowania w dół polimorfizm uniemożliwia wykonanie niedozwolonych operacji. O wiele bardziej użyteczny jest jednak przy rzutowaniu w górę.
Monika Wrzosek (IM UG) Programowanie obiektowe 145 / 148
p u b l i c c l a s s M o j a K l a s a { p u b l i c S t r i n g t o S t r i n g ( ) {
r e t u r n " M o j a K l a s a "; }}
p u b l i c c l a s s M o j a D r u g a K l a s a e x t e n d s M o j a K l a s a { p u b l i c S t r i n g t o S t r i n g ( ) {
r e t u r n " M o j a D r u g a K l a s a "; }}
p u b l i c c l a s s M o j a T r z e c i a K l a s a e x t e n d s M o j a K l a s a { p u b l i c S t r i n g t o S t r i n g ( ) {
r e t u r n " M o j a T r z e c i a K l a s a "; }}
p u b l i c c l a s s Main {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { M o j a K l a s a mk1 = new M o j a D r u g a K l a s a ( ) ; M o j a K l a s a mk2 = new M o j a T r z e c i a K l a s a ( ) ; S y s t e m . o u t . p r i n t l n ( mk1 . t o S t r i n g ( ) ) ; S y s t e m . o u t . p r i n t l n ( mk2 . t o S t r i n g ( ) ) ; }}
M o j a D r u g a K l a s a M o j a T r z e c i a K l a s a
Monika Wrzosek (IM UG) Programowanie obiektowe 146 / 148
p u b l i c c l a s s Main {
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { M o j a K l a s a mk1 = new M o j a D r u g a K l a s a ( ) ; M o j a K l a s a mk2 = new M o j a T r z e c i a K l a s a ( ) ; S y s t e m . o u t . p r i n t l n ( mk1 . t o S t r i n g ( ) ) ; S y s t e m . o u t . p r i n t l n ( mk2 . t o S t r i n g ( ) ) ; }
}
W Javie wywołania metod są domyślnie polimorficzne, tzn. skojarzenie obiektu z metodą jest wykonywane w trakcie działania programu. Sprawdzeniu podlega rzeczywisty typ obiektu, którego metoda jest wywoływana. Ponieważ rzeczywistym typem dla mk1 jest MojaDrugaKlasa, a dla mk2 - MojaTrzeciaKlasa, na ekranie pojawi się:
M o j a D r u g a K l a s a M o j a T r z e c i a K l a s a
Gdyby wiązanie (czyli skojarzenie wywołania metody z obiektem) odbywało się już na etapie kompilacji (wczesne wiązanie, ang. early binding ), wynik byłby inny.
W trakcie kompilacji mk1 i mk2 są dla kompilatora obiektami klasy MojaKlasa, więc przypisałby on im kod metody toString z klasy MojaKlasa.
Dzięki wywołaniom polimorficznym wiązanie następuje dopiero w trakcie działania programu i pod uwagę jest brany rzeczywisty typ obiektu. To jedna z podstawowych zasad programowania obiektowego.
Monika Wrzosek (IM UG) Programowanie obiektowe 147 / 148
Polimorfizm a metody prywatne
Przy wykorzystywaniu polimorficznych wywołań metod należy pamiętać, że mają one miejsce, kiedy metoda X z klasy bazowej jest przesłaniana przez metodę X z klasy potomnej.
Problem może wystąpić, kiedy w klasie bazowej zostanie zdefiniowana metoda prywatna. Taka metoda nie może być przesłonięta (bo przecież metody prywatne są dostępne wyłącznie dla klasy, w której zostały zdefiniowane).
Jednak to, że dana metoda została zadeklarowana w klasie bazowej jako prywatna, nie oznacza wcale, że nie można zdefiniować metody o takiej samej nazwie i argumentach w klasie pochodnej. Nie zaleca się jednak stosowania tego typu konstrukcji, gdyż zaciemnia to sposób działania kodu.
Monika Wrzosek (IM UG) Programowanie obiektowe 148 / 148