(c) Krzysztof Barteczko 2014 Interfejsy (c) Krzysztof Barteczko 2014
Metody i klasy abstrakcyjne Metody abstrakcyjne - nie wiemy jeszcze jaka może być ich konkretna implementacja, ale wiemy, że powinny wystąpić w zestawie metod każdej konkretnej klasy dziedziczącej klasę abstrakcyjną. Konkretna implementacja (definicja kodu metody) może być bardzo różna, w zależności od konkretnego rodzaju obiektów, które opisuje dana klasa. Klasa abstrakcyjna nie musi mieć metod abstrakcyjnych. Wystarczy zadeklarować ją ze specyfikatorem abstract. Nie wolno tworzyć obiektów klas abstrakcyjnych. (c) Krzysztof Barteczko 2014
Przykład – abstrakcyjne metody Polimorficzne odwołania do nieistniejących być może metod (c) Krzysztof Barteczko 2014
Implementacja metod abstrakcyjnych (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Pojęcie interfejsu (c) Krzysztof Barteczko 2014
Problem wielodziedziczenia (1) Nie wszystkie zwierzęta wydają głos. Zatem umieszczanie (abstrakcyjnej) metody getVoice() oraz metody speak() w klasie Zwierz nie jest "czystym rozwiązaniem". Co więcej nie tylko zwierzęta mówią. Chciałoby się więc mieć klasę obiektów wydających głos, którą mógłby dziedziczyć np. Wodospad i Pies. Ale Pies jest Zwierzem (dziedziczy Zwierza) i nie może odziedziczyć klasy obiektów "wydających głos". W Javie nie ma bowiem wielodziedziczenia klas: każda klasa może dziedziczyć bezpośrednio tylko jedną klasę. (c) Krzysztof Barteczko 2014
Dlaczego w Javie nie ma wielodziedziczenia klas abstract class Human { public abstract String getSex(); } class Father extends Human { public String getSex() { return "male"; } } class Mother extends Human { public String getSex() { return "female"; } } // hipotetyczne wielodziedziczenie class Child extends Mother, Father { // klasa Child nie przedefiniowuje metody getSex() ! } class A { public int a; } class B extends A { // ma pole a } class C extends A { // ma pole a } // hipotetyczne wielodziedziczenie class D extends B i C { } jaki wynik dla obiektu-dziecka (klasy Child) zwróci wywołanie metody getSex() ? Obiekt d z klasy D ma element definiowany przez pole a. Jeden czy dwa? Jeśli jeden, to który? Poczynając od Java wersja 8 mamy możliwość wielodziedziczenia konkretnych implementacji metod (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Interfejsy na pomoc (c) Krzysztof Barteczko 2014
Interfejsy – definicja i implementacja (c) Krzysztof Barteczko 2014
Można implementować wiele interfejsów (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Nowy Zwierz i nowy Pies public class Pies extends Zwierz implements Speakable, Moveable { public Pies() {} public Pies(String s) { super(s); } public String getTyp() { return "Pies"; public String getVoice(int voice) { if (voice == LOUD) return "HAU... HAU... HAU... "; else return "Hau... Hau..."; public Pies start() { System.out.println("Pies " + getName() + " biegnie"); return this; public Pies stop() { System.out.println("Pies " + getName() + " stanął"); public Pies merda() { System.out.println("Merda ogonem"); Zmieniając nieco definicję klasy Zwierz (nie wszystkie zwierzęta wydają głos, więc usunęliśmy metody getVoice() i speak()): public abstract class Zwierz { private String name = "bez imienia"; public Zwierz() {} public Zwierz(String s) { name = s; } public abstract String getTyp(); public String getName() { return name; Możemy teraz tak zdefiniować klasę Pies Używana dalej klasa Kot jest zdefiniowana podobnie. (c) Krzysztof Barteczko 2014
Interfejsy też wyznaczają typy (c) Krzysztof Barteczko 2014
Jeśli interfejsy wyznaczają typy, to ... Możemy: wykonywać konwersje w górę do typu wyznaczanego przez implementowany interfejs, używać operatora instanceof, by stwierdzić czy obiekt jest obiektem klasy implementującej dany interfejs. Implementacja interfejsów działa podobnie jak przedefiniowanie metod: zauważmy, że metody start() i stop() interfejsu Moveable mają typ wyniku Moveable. W klasie Pies implementowaliśmy je z typem wyniku Pies, czyli przy implementacji interfejsów dopuszczalna jest kowariancja typów wyniku implementowanych metod. (c) Krzysztof Barteczko 2014
Polimorfizm przy użyciu interfejsów public class Vehicle implements Moveable { // ... public Vehicle start() { setState(MOVING); return this; } public Vehicle stop() { setState(STOPPED); Dzięki interfejsom polimorficzne odwołania są możliwe nie tylko "wzdłuż" hierarchii dziedziczenia klas, ale również "w poprzek" tych hierarchii. Klasy Pies i Kot należą do innej hierarchii dziedziczenia niż klasa Car i Rower. Dzięki temu, że wszystkie te klasy implementują interfejs Moveable dla wszystkich tych klas uzyskujemy możliwość polimorficznych odwołań do metody start(). public class Wyscig { static void wyscig(Moveable ... moveables) { for (Moveable m : moveables) { m.start(); if (m instanceof Vehicle) System.out.println(m); } public static void main(String[] args) { wyscig(new Pies("Kuba"), new Car("WB4545", new Person("Janek", "9012102567"),100, 100, 100, 100, 100).fill(10), new Kot("Mruczek"), new Bicycle(new Person("Ala", "7011122347"),100, 100, 100, 100) ); Pies Kuba biegnie Samochód nr rej WB4545 - JEDZIE Kot Mruczek się skrada Pojazd 2, właścicielem którego jest Ala jest w stanie JEDZIE (c) Krzysztof Barteczko 2014
Mechanizm konwersji zawężających Konwersje zawężające Przypomnienie: jeśli mamy referencję do obiektu typu Zwierz na którą podstawiono odniesienie do obiektu typu Pies, to możemy zrobić konwersję "w dół" hierarchii dziedziczenia. Pies p = new Pies(); Zwierz z = p; Pies p1 = (Pies) z; // Konwersja z typu Zwierz do typu Pies Mówiąc obrazowo (ale pamiętając o tym, że mamy do czynienia z konwersjami referencyjnymi i tak naprawdę jest tu tylko jeden obiekt, który po prostu, w kolejnych przekształceniach referencyjnych, traktujemy inaczej): Pies pochodzi od Zwierza, możemy więc z Psa uzyskać Zwierza, a później z tego Zwierza z powrotem Psa. Mechanizm konwersji zawężających w równym stopniu dotyczy interfejsów. (c) Krzysztof Barteczko 2014
Konwersje referencyjne - przykład (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 ... przykład c.d. dla obiektu hipotetycznej klasy Ryba - info( new Ryba()) - możemy dostać w wyniku tylko: public class Ryba extends Zwierz implements Moveable { public Ryba() { super(""); } public Ryba(String s) { super(s); @Override public String getTyp() { return "Ryba"; public Moveable start() { System.out.println("płynie"); return this; public Moveable stop() { System.out.println("śpi"); Ryba Bo wartość wyrażenia z instanceof Speakable jest false (Ryba nie implementuje interfejsu Speakable) i oczywiście nie jest też Psem. Natomiast dla Psa kuby (kuba = new Pies("Kuba")) po info(kuba) dostaniemy pewnie: Pies Kuba HAU... HAU... HAU... Merda ogonem (c) Krzysztof Barteczko 2014
Konwersje referencyjne – przykład 2 Przywróćmy w klasie Zwierz metodę speak() public void speak(int ... v) { int vol = Speakable.QUIET; if (v.length == 1) vol = v[0]; String voice; if (this instanceof Speakable) voice = ((Speakable) this).getVoice(vol); else voice = "... (cisza) ..."; System.out.println(getTyp()+" "+getName()+ " mówi " + voice); } static void animalDialog(Zwierz z1, Zwierz z2) { z1.speak(); z2.speak(); System.out.println("--------------------------------------"); } // ... Pies kuba = new Pies("Kuba"), reksio = new Pies("Reksio"); Kot kot = new Kot("Mruczek"); Ryba ryba = new Ryba(); animalDialog(kuba, reksio); animalDialog(kuba, kot); animalDialog(reksio, ryba); Pies Kuba mówi Hau... Hau... Pies Reksio mówi Hau... Hau... -------------------------------------- Kot Mruczek mówi Miau... Ryba bez imienia mówi ... (cisza) ... (c) Krzysztof Barteczko 2014
Implementacje metod w interfejsach W Javie 8 wprowadzono możliwość dostarczania gotowych definicji metod w interfejsach (publicznych metod statycznych oraz publicznych niestatycznych metod z użyciem słowa kluczowego default). Istotą tych zmian jest umożliwienie rozbudowy już istniejących interfejsów w taki sposób, by nie zakłócić zgodności z klasami, które już implementują te interfejsy. Rzeczywiście, do interfejsów, które już zostały zaimplementowane przez jakieś klasy nie powinniśmy dodawać metod abstrakcyjnych, bowiem w takim przypadku klasy te przestaną działać i będą wymagać zmian w kodzie - implementacji nowych metod interfejsu. Z drugiej strony czasem dodanie do istniejącego interfejsu nowych metod byłoby wskazane. Oczywiście, to nie mogą być metody abstrakcyjne. (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Przykład (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Mixiny (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Mixiny w użyciu (c) Krzysztof Barteczko 2014
Co teraz z rombem wielodziedziczenia ? (c) Krzysztof Barteczko 2014
Metody domyślne można przedefiniowywać i przeciążać class Child implements Mother, Father { public String getSex() { return "?"; } public String fatherName() { return "Tata: Jan"; } public String motherName() { return "Mama: Ala"; } public String fatherName(int age) { return "Jan, lat " + age; } public String toString() { return "Rodzice - " + fatherName() + " " + motherName(); } // ... Child child = new Child(); System.out.println(child); System.out.println(child.fatherName(30)); Rodzice - Tata: Jan Mama: Ala Jan, lat 30 (c) Krzysztof Barteczko 2014
Domyślne metody są polimorficzne interface BaseIf { default String get() { return this.getClass().getSimpleName(); } //brak abstrakcyjnych metod do implementacji! class A implements BaseIf {} class B implements BaseIf {} public class WhatIsThis { public static void main(String[] args) { BaseIf a = new A(); System.out.println(a.get()); BaseIf b = new B(); System.out.println(b.get()); A B (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 A jak użyć super ? class Child implements Mother, Father { public String getSex() { return "?"; } public String fatherName() { return "Tata: " + Father.super.fatherName(); } public String motherName() { return "Mama: " + Mother.super.motherName(); } public String toString() { return "Rodzice - " + fatherName() + " " + motherName(); } // ... Child child = new Child(); System.out.println(child); Rodzice - Tata: Jan Mama: Ala (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Jeszcze raz: kolizje Gdy te same domyślne metody w kilku implementowanych interfajsach – wystąpi błąd w kompilacji (romb wielodziedziczenia). (c) Krzysztof Barteczko 2014
Różne implementacje tych samych metod Jeśli ta sama metoda domyślna jest implementowana na różnych poziomach hierarchii dziedziczenia, to: priorytet ma metoda przedefiniowana w klasie obiektu, jeśli jej nie ma, to metoda implementowana w nadklasie obiektu, jeśli jej nie ma, to metoda najbliższego w hierarchii interfejsu interface IfA { default void m() { System.out.println("Z IfA"); } } interface IfB extends IfA { default void m() { System.out.println("Z IfB"); } class A implements IfA, IfB { } class B implements IfA, IfB { public void m() { System.out.println("Z B"); class C extends B implements IfA, IfB {} // ... new A().m(); IfA b = new B(); b.m(); IfA c= new C(); c.m(); Z IfB Z B (c) Krzysztof Barteczko 2014
Nie nadużywać metod domyślnych Głównym przeznaczeniem metod domyślnych jest bezkolizyjne modyfikowanie istniejących interfejsów. Inne przypadki uzycia (mixiny) trzeba stosować z umiarem. W przeciwnym razie możemy nadmiernie skomplikować hierarchię dziedziczenia w programie. (c) Krzysztof Barteczko 2014