Związek między pojęciami Zasada podstawialności Podklasy muszą realizować kontrakt zawarty przez nadklasy

Podobne dokumenty
Polimorfizm, metody wirtualne i klasy abstrakcyjne

Projektowanie obiektowe. Roman Simiński Polimorfizm

Dziedziczenie. dr Jarosław Skaruz

Kurs programowania. Wstęp - wykład 0. Wojciech Macyna. 22 lutego 2016

Klasy abstrakcyjne, interfejsy i polimorfizm

Enkapsulacja, dziedziczenie, polimorfizm

Wykład 6: Dziedziczenie

Polimorfizm. dr Jarosław Skaruz

Klasa jest nowym typem danych zdefiniowanym przez użytkownika. Najprostsza klasa jest po prostu strukturą, np

Programowanie w Javie wykład 7 Klasy c.d. (przeciążanie metod, polimorfizm) Metody i klasy abstrakcyjne Bloki inicjalizacyjne

Java - tablice, konstruktory, dziedziczenie i hermetyzacja

Java niezbędnik programisty spotkanie nr 3. Modyfikatory, jednostki kompilacji, tworzenie/inicjalizacja, odśmiecanie/ finalizacja...

Dziedziczenie. Tomasz Borzyszkowski

Informatyka I. Dziedziczenie. Nadpisanie metod. Klasy abstrakcyjne. Wskaźnik this. Metody i pola statyczne. dr inż. Andrzej Czerepicki

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Kurs programowania. Wykład 2. Wojciech Macyna. 17 marca 2016

Podstawy Programowania Obiektowego

Programowanie obiektowe w języku

Wykład 7: Pakiety i Interfejsy

Programowanie obiektowe

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Programowanie obiektowe - 1.

PARADYGMATY PROGRAMOWANIA Wykład 4

Programowanie obiektowe

Obszar statyczny dane dostępne w dowolnym momencie podczas pracy programu (wprowadzone słowem kluczowym static),

Definicje klas i obiektów. Tomasz Borzyszkowski

Wykład 8: klasy cz. 4

Programowanie obiektowe

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

JAVA. Java jest wszechstronnym językiem programowania, zorientowanym. apletów oraz samodzielnych aplikacji.

Programowanie w Internecie. Java

Programowanie współbieżne Wykład 8 Podstawy programowania obiektowego. Iwona Kochaoska

Klasy abstrakcyjne i interfejsy

Kurs WWW. Paweł Rajba.

TEMAT : KLASY DZIEDZICZENIE

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

Programowanie Obiektowe i C++

Pakiety i interfejsy. Tomasz Borzyszkowski

1. Które składowe klasa posiada zawsze, niezależnie od tego czy je zdefiniujemy, czy nie?

Podstawa: Bruce Eckel, Thinking in Java, Second Ed., Prentice Hall, 1998 The JavaLanguage Environment, A white Paper, Sun, Oct.

10. Programowanie obiektowe w PHP5

Konstruktory. Streszczenie Celem wykładu jest zaprezentowanie konstruktorów w Javie, syntaktyki oraz zalet ich stosowania. Czas wykładu 45 minut.

Interfejsy. Programowanie obiektowe. Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej

Klasy. dr Anna Łazińska, WMiI UŁ Podstawy języka Java 1 / 13

Operator przypisania. Jest czym innym niż konstruktor kopiujący!

Wykład 5: Więcej o Klasach i Metodach

Programowanie obiektowe Wykład 6. Dariusz Wardowski. dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/14

Wstęp do programowania obiektowego. WYKŁAD 3 Dziedziczenie Pola i funkcje statyczne Funkcje zaprzyjaźnione, this

Dokumentacja do API Javy.

Definiowanie własnych klas

Aplikacje w środowisku Java

Aplikacje w środowisku Java

Programowanie II. Lista 3. Modyfikatory dostępu plik TKLientBanku.h

Programowanie obiektowe

Materiały do zajęć VII

Zaawansowane programowanie w C++ (PCP)

Programowanie 2. Język C++. Wykład 3.

Języki i metody programowania Java. Wykład 2 (część 2)

Programowanie obiektowe

Rozdział 4 KLASY, OBIEKTY, METODY

C++ - polimorfizm. C++ - polimorfizm. C++ - polimorfizm. C++ - polimorfizm. C++ - polimorfizm POLIMORFIZM

Typy sparametryzowane

Java: kilka brakujących szczegółów i uniwersalna nadklasa Object

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Dziedziczenie jednobazowe, poliformizm

Programowanie obiektowe

Programowanie obiektowe

Kurs programowania. Wykład 3. Wojciech Macyna. 22 marca 2019

Programowanie obiektowe

Wykład 5: Klasy cz. 3

Wykład 4: Klasy i Metody

Dziedziczenie. Streszczenie Celem wykładu jest omówienie tematyki dziedziczenia klas. Czas wykładu 45 minut.

Aplikacje w Javie wykład 5 Klasy c.d. (przeciążanie metod, polimorfizm) Metody i klasy abstrakcyjne Interfejsy

Interfejsy i klasy wewnętrzne

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Programowanie w języku Java - Wyjątki, obsługa wyjątków, generowanie wyjątków

Wykład 9: Polimorfizm i klasy wirtualne

Programowanie, część I

Programowanie Obiektowe i C++

W2 Wprowadzenie do klas C++ Klasa najważniejsze pojęcie C++. To jest mechanizm do tworzenia obiektów. Deklaracje klasy :

Programowanie obiektowe w C++ Wykład 12

Składnia C++ Programowanie Obiektowe Mateusz Cicheński

Programowanie obiektowe, wykład nr 6. Klasy i obiekty

IMIĘ i NAZWISKO: Pytania i (przykładowe) Odpowiedzi

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

C++ - dziedziczenie. C++ - dziedziczenie. C++ - dziedziczenie. C++ - dziedziczenie. C++ - dziedziczenie C++ - DZIEDZICZENIE.

Programowanie, część I

Programowanie obiektowe

Programowanie obiektowe

Zaawansowane programowanie w C++ (PCP)

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Kompozycja i dziedziczenie klas

1 Atrybuty i metody klasowe

Platformy Programistyczne Podstawy języka Java

Marcin Luckner Politechnika Warszawska Wydział Matematyki i Nauk Informacyjnych

Szablony funkcji i klas (templates)

Java Język programowania

Zaawansowane programowanie w języku C++ Programowanie obiektowe

Projektowanie obiektowe. Roman Simiński Wzorce projektowe Wybrane wzorce strukturalne

Języki i techniki programowania Ćwiczenia 3 Dziedziczenie

Transkrypt:

Janusz Jabłonowski

Związek między pojęciami Zasada podstawialności Podklasy muszą realizować kontrakt zawarty przez nadklasy

Przedefiniowywanie lub podmienianie (ang. overriding, czasami błędnie tłumaczone jako nadpisywanie) Umożliwia realizację polimorfizmu Realizacja kontraktu określonego w nadklasie Zasadniczo powinna być zachowana sygnatura (w sensie nagłówek metody ), ale

Rozważmy: class C{ private void priv() {} void pack() {} protected void prot() {} public void publ() {} }

Czy można zmienić zakresy widoczności? class subc extends C {? void priv() {}? void pack() {}? void prot() {}? void publ() {} }

Mamy zachować kontrakt Poszczególne zakresy widoczności wg rosnącego zasięgu: private (pakietowy) protected public Zaoferowanie wyższego zakresu widoczności nie narusza kontraktu Subtelny problem z private (czyli wręcz przeciwnie)

class PrivSubC extends C { private void priv() {} private void pack() {} private void prot() {} private void publ() {} } I dlaczego?

class PrivSubC extends C { private void priv() {} private void pack() {} private void prot() {} private void publ() {} } priv() się kompiluje, ale oznacza co innego metodę nie związaną z priv() z klasy C. Po dodaniu @Override priv też się nie kompiluje.

class PackSubC extends C { void priv() {} void pack() {} void prot() {} void publ() {} } I dlaczego?

class PackSubC extends C { void priv() {} void pack() {} void prot() {} void publ() {} } priv() jak poprzednio.

class ProtSubC extends C { protected void priv() {} protected void pack() {} protected void prot() {} protected void publ() {} } I dlaczego?

class ProtSubC extends C { protected void priv() {} protected void pack() {} protected void prot() {} protected void publ() {} } priv() jak poprzednio.

class PublSubC extends C { public void priv() {} public void pack() {} public void prot() {} public void publ() {} } I dlaczego?

class PublSubC extends C { public void priv() {} public void pack() {} public void prot() {} public void publ() {} } priv() jak poprzednio.

class A { public void mpubl() {System.out.println("A");} private void mpriv() {System.out.println("A");} void testuja(a a) { a.mpubl(); a.mpriv(); // Do prywatnych składowych innych obiektów // tej samej klasy można się odwoływać } void testujb(b b) { b.mpubl(); b.mpriv(); // Nie można, bo mpriv jest prywatne w B! // Definicja w A nie ma znaczenia. }}

class B extends A{ public void mpubl() private void mpriv() {System.out.println("B");} {System.out.println("B");} void testuja(a a) { a.mpubl(); a.mpriv(); // Błąd, mpriv jest prywatne w A! } }} void testujb(b b) { b.mpubl(); b.mpriv(); // OK.

A a = new A(); B b = new B(); a.testuja(a); // A A a.testuja(b); // B A <- tu jest najciekawiej, // prywatna metoda nie jest wirtualna a.testujb(b); // B b.testuja(a); // A b.testuja(b); // B b.testujb(b); // B B

Prywatne metody nie są wirtualne Ale można je zadeklarować w podklasie z tą samą sygnaturą i mogą wydawać się wirtualne Zatem należy zawsze używać adnotacji @Override!

Są przeznaczone do wykonywania na rzecz klas, a nie obiektów Można je wprawdzie wywołać na rzecz obiektów Ale nie jest to zalecane

class D { static private void a() { System.out.println("D.a"); } static void b() { System.out.println("D.b"); } static protected void c() { System.out.println("D.c"); } static public void d() { System.out.println("D.d"); } }

class PubSubD extends D { static public void a() { System.out.println("PublSubD.a"); } static public void b() { System.out.println("PublSubD.b"); } static public void c() { System.out.println("PublSubD.c"); } static public void d() { System.out.println("PublSubD.d"); } }

// Dodajmy do klasy D static public void test() { D d = new PubSubD(); }; d.a(); d.b(); d.c(); d.d(); // D.a // D.b // D.c // D.d

Kompilator dla wywołań w rodzaju d2.a() generuje ostrzeżenie o dostępie do metody klasowej Nie można podać @Override w metodach w podklasach W podklasie PrivSubD kompilator przyjmuje tylko prywatną metodę, w PackSubD dwie metody, w ProtSubD trzy (tych klas nie ma na slajdach)

Nie są Można je definiować w podklasach z taką samą sygnaturą i wtedy wydają się być przedefiniowane Dla uniknięcia wątpliwości najlepiej wywoływać je zawsze na rzecz klasy, np. D.a(), a nie obiektu, czyli np. nie d2.a(), this.a(), super.a(), choć składniowo te wszsytkie formy są poprawne.

Przy wołaniu przez obiekt metody klasowej sam obiekt nie ma znaczenia. Zupełnie nie ma znaczenia: D d = null; d.a(); // D.a d.b(); // D.b d.c(); // D.c d.d(); // D.d Wykona się z takim samym efektem jak poprzednio!

Zmienna klasowa jest wspólna dla całej klasy, więc występuje w dokładnie jednym egzemplarzu dla klasy (niezależnie od tego, czy obiektów tej klasy jest 0 czy 20 10 ) Ale co wtedy, gdy klasa ma podklasę czy jest jedna czy dwie zmienne klasowe (czyli czy zmienne klasowe się dziedziczą)? Czy można w podklasie mieć zmienną klasową o tej samej nazwie co w nadklasie?

class A { static int a1 = 1; static int a2 = 1;} class B extends A { static int b1 = 1; static int a1 = 1; // Ostrzeżenie kompilatora o przesłonięciu }

A.a1 += 10; A.a2 += 100; B.b1 += 1000; B.a1 += 10000; B.a2 += 100000; System.out.println(" A.a1=" + A.a1 + " A.a2=" + A.a2 + " B.b1=" + B.b1 + " B.a1=" + B.a1 + " B.a2=" + B.a2); A.a1=11 A.a2=100101 B.b1=1001 B.a1=10001 B.a2=100101

Zmienna klasowa jest jedna na klasę i wszystkie jej podklasy Można w podklasie zdefiniować zmienną o takiej samej nazwie, spowoduje to przesłonięcie zmiennej zadeklarowanej wyżej w hierarchii

Dotąd zawsze tak było Ale wiemy już, że może mieć inny (szerszy) zasięg widoczności Czy może mieć inny typ parametrów? Czy może mieć inny typ wyniku?

class Ea{} class Eb extends Ea{} class Ec extends Eb{} class E{ public Eb m(eb e){ return new Eb(); } }

class ESub1 extends E {. // Czy któraś deklaracja jest poprawna? @Override public Ea m(eb e){ return new Eb(); } @Override public Ec m(eb e){ return new Ec(); } @Override public Eb m(ea e){ return new Eb(); } @Override public Eb m(ec e){ return new Eb(); } Pamiętajmy o wypełnianiu kontraktu!

class ESub1 extends E {. // Czy któraś jest poprawna? @Override public Ea m(eb e){ return new Eb(); } @Override public Ec m(eb e){ return new Ec(); } @Override public Eb m(ea e){ return new Eb(); } @Override public Eb m(ec e){ return new Eb(); } Czemu dwie ostatnie metody się kompilują bez @Override?

Podklasa może nieco zmienić typ wyniku przedefiniowanej metody: zamiast typu klasy może być podany typ podklasy Jest to zgodne z zasadą kontraktu Mówimy wtedy o kowariantnym (typ wyniku zmienia się zgodnie ze zmianą typu klasy od nadklasy do podklasy) typie wyniku

Język Java pozwala też na inne odstępstwa: Lista wyjątków zgłaszanych przez metodę przedefiniowującą może być inna, ważne tylko, żeby każdy zgłaszany wyjątek (lub jego nadklasa) były w liście zgłaszanych wyjątków nadklasy (znów zasada zachowania kontraktu) Można dowolnie stosować modyfikatory synchronized, native, strictfp, bo nie mówią o kontrakcie, tylko o jego implementacji

Język Java pozwala też na inne odstępstwa: Metoda przedefiniowująca może być final (a przedefiniowywana?) Adnotacje nie muszą być zachowane Zarówno przedefiniowywana jak i przedefiniowana metoda mogą być abstrakcyjne (obie, jedna z nich, no i żadna oczywiście też)

Czemu zatem Java nie pozwala na kontrawariantne parametry? I czemu kompilator pozwala usunąć adnotację @Override w dwu ostatnich wariantach przedefiniowania metody m w klasie ESub1 (a z @Override nie chciał kompilować)? Spójrzmy ponownie na wcześniejszy slajd (tym razem z usuniętymi dwoma @Override)

class ESub1 extends E {. // Czy któraś z deklaracji jest poprawna? @Override public Ea m(eb e){ return new Eb(); } @Override public Ec m(eb e){ return new Ec(); } public Eb m(ea e){ return new Eb(); } public Eb m(ec e){ return new Eb(); } Czemu?

Wkroczyliśmy (niechcący) w zupełnie inny świat Wyszliśmy ze świata obiektowości Jesteśmy w świecie dowolnych języków programowania (z typami) Użyliśmy przeciążania (ang. overloading) To pożyteczne ale zarazem niebezpieczne narzędzie Przeciążanie nie ma nic wspólnego z przedefiniowywaniem!

Przeciążanie pozwala zdefiniować rodzinę funkcji (procedur, a w językach obiektowych metod) o tej samej nazwie, ale innej sygnaturze To która zostanie użyta kompilator rozpoznaje analizując wywołanie Zatem wybór wywoływanej funkcji (metody) odbywa się podczas kompilacji

Często wygodnie jest użyć tej samej nazwy do wielu operacji o podanym charakterze ale z innymi parametrami (np. wypisz, dodaj) Czasem jesteśmy zmuszeniu do użycia tego mechanizmu (np. w językach obiektowych konstruktor zwykle musi się nazywać tak jak klasa) Niektóre języki (Java nie) pozwalają przeciążać nazwy operatorów

Metody przeciążane muszą być widoczne w jednym zasięgu (inaczej nie byłoby o czym mówić), ale nie muszą być w nim zdefiniowane (np. jedna metoda może być odziedziczona, druga własna) Metody przeciążane muszą się dostatecznie różnić listą parametrów, by kompilator mógł wybrać właściwą

Wystarczające różnice: liczba parametrów (łatwe, nawet programista, nie tylko kompilator, się od razu zorientuje) typy parametrów (oj, bywa trudne) jedno i drugie

class G{} class GSub1 extends G{} class GSub2 extends G{}

class G1{ public void f(int i){system.out.println("f(int)");} public void f(char c){system.out.println("f(char)");} } class G2 extends G1{ public void f(long l){system.out.println("f(long)");} public void f(short s){system.out.println("f(short)");} public void f(byte b){system.out.println("f(byte)");} public void f(g g){system.out.println("f(g)");} public void f(gsub1 g){system.out.println("f(gsub1)");} public void f(gsub2 g){system.out.println("f(gsub2)");} public void f(g g1, G g2){system.out.println("f(g, G)");} public void f(gsub1 g1, G g2){system.out.println("f(gsub1, G)");} public void f(g g1, GSub2 g2){system.out.println("f(g, GSub2)");}}

G2 o = new G2(); G g = new GSub1(); o.f('a'); o.f(13); o.f(((byte)13)); o.f(256); o.f((short)256); o.f(-2147483648); o.f(1l); o.f(g); o.f(new GSub1()); o.f(new GSub2()); o.f(new G(), new G()); o.f(new GSub1(), new G()); o.f(new GSub1(), new GSub1()); o.f(new G(), new GSub2()); o.f(new GSub1(), new GSub2());

G2 o = new G2(); G g = new GSub1(); o.f('a'); f(char) o.f(13); f(int) o.f(256); f(int) o.f(-2147483648); f(int) o.f(1l); f(long) o.f(g); f(g)!!! o.f(new GSub1()); f(gsub1 o.f(new GSub2()); f(gsub2) o.f(new G(), new G()); f(g, G) o.f(new GSub1(), new G()); f(gsub1, G) o.f(new GSub1(), new GSub1()); f(gsub1, G) o.f(new G(), new GSub2()); f(g, GSub2) o.f(new GSub1(), new GSub2()); niejednoznaczne

Ostatni wariant nie skompilował się ze względu na niejednoznaczność. Do tego wywołania pasuje kilka metod, a dwie równie dobrze: public void f(gsub1 g1, G g2){ } public void f(g g1, GSub2 g2){ }

Żeby wywołały się metody oczekujące wartości typu byte albo short, można napisać tak: o.f(13); o.f((byte)13); o.f(256); o.f((short)256); G1.f(int) G1.f(byte) G1.f(int) G1.f(short)

Przykład o.f(g); G1.f(G)!!! Ten przykład jest bardzo ważny, bo pokazuje, że przeciążanie nie zależy od faktycznego typu obiektu!!!

Przeciążane metody mogą mieć dowolne zakresy widoczności i typy wyników (tzn. mogą mieć różne) Przeciążane metody nie mogą się różnić tylko zakresem widoczności (muszą być widoczne w tym samym miejscu)

Przeciążane metody nie mogą się różnić tylko typem wyniku Dlaczego? Popatrzmy: void m1(int i) {} void m1(string s) {} int m2() {return 1;} String m2() {return "";} m1(m2()); // co ma się policzyć?

Przedefiniowywanie i przeciążanie to zupełnie co innego (choć jest często mylone) Przedefiniowywanie odbywa się dynamicznie Przeciążanie jest rozstrzygane statycznie Przedefiniowywanie dotyczy tylko programowania obiektowego, przeciążanie dowolnych języków z typami

W przykładzie (wiele slajdów temu) po usunięciu @Override pojawiły się dwie przeciążone metody m, dlatego kompilacja się powiodła Morał (ponownie): zawsze piszmy @Override przy przedefiniowywaniu (IDE często samo podpowie i napisze)