Programowanie obiektowe II. Dziedziczenie Małgorzata Prolejko OBI JA16Z03
Plan Klasy bazowe i pochodne. Hierarchia dziedziczenia. Polimorfizm. Wiązanie dynamiczne. Ograniczenie dziedziczenia, klasy i metody finalne. Rzutowanie. Klasy i metody abstrakcyjne. Klasa Object: metody equals, tostring i hashcode. Klasy wyliczeniowe. Szablony klas Kolekcje Adnotacje
Klasy bazowe i pochodne Klasa bazowa nadklasa, klasa rozszerzana, klasa której pola i metody zostaną odziedziczone. Najbardziej ogólna z klas hierarchii Klasa pochodna podklasa, klasa rozszerzająca, klasa zawierająca pola i metody z nadklasy oraz własne pola i metody. Klasa bardziej szczegółowa. public class Baza{... public class Pochodna extends Baza{... Klasa bazowa Klasa pochodna
Hierarchia dziedziczenia Dziedziczenie może odbywać się wielopoziomowo. Klasa bazowa może mieć wiele klas pochodnych, a klasy pochodne mogą posiadać swoje podklasy. Powiązania dziedziczenia pomiędzy klasami nazywają się hierarchią dziedziczenia. public class Czytelnik{ Czytelnik public class Pracownik extends Czytelnik{ public class Student extends Czytelnik{ Public class Absolwent extends Student{ Pracownik Student Absolwent
Polimorfizm Obiekty różnych podklas jednej nadklasy mogą być wspólnie obsługiwane w granicach stosowalności nadklasy. public class Czytelnik{ private String numerkarty; public Czytelnik(String nr){numerkarty = nr; public String getnumerkarty(){return numerkarty; Czytelnik[] czytelnicy = new Czytelnik[3]; czytelnicy[0] = new Student("1"); czytelnicy[1] = new Student("2"); czytelnicy[2] = new Pracownik("3"); for (Czytelnik c: czytelnicy) { System.out.println(c.getNumerKarty()); package test11
Wiązania dynamiczne Wiązania dynamiczne to poszukiwanie metody odpowiedniej do klasy obiektu wywołującego. public class Czytelnik{... public String getnumerkarty(){return numerkarty; public class Pracownik extends Czytelnik{... public String getnumerkarty(){return "p-"+numerkarty; Czytelnik czytelnicy = new Czytelnik[3]; czytelnicy[0] = new Czytelnik("1"); czytelnicy[1] = new Student("2"); czytelnicy[2] = new Pracownik("3"); for (Czytelnik c: czytelnicy) { System.out.println(c.getNumerKarty()); package test12
Polimorfizm Ćwiczenie 10. Zaimplementuj klasy do systemu nagród. Są nagrody główne: za 1-sze, 2-gie i 3-cie miejsce oraz nagrody specjalne. Nagrody główne składają się z części pieniężnej, upominku* oraz pucharu*. Nagrody specjalne składają się z upominku* oraz dyplomu*. Nagrody przyznawane są w 3 kategoriach: Jakość, Szybkość i Cena. * można przyjąć że są typu String
Polimorfizm Ćwiczenie 10. public class Nagroda { private final String upominek; private final String kategoria; public Nagroda(String upo, String kat){ upominek = upo; kategoria = kat.touppercase(); public String gettext(){ return "kategoria:"+kategoria+", upominek:"+upominek;
Polimorfizm Ćwiczenie 10. public class NagrodaSpecjalna extends Nagroda { private String dyplom; public NagrodaSpecjalna(String upo, String kat, String dyp){ super(upo,kat); dyplom = dyp; @Override public String gettext(){ return super.gettext() + ", dyplom:"+dyplom;
Polimorfizm Ćwiczenie 10. public class NagrodaGlowna extends Nagroda { private String puchar; private int wartosc; private int miejsce; public NagrodaGlowna(String upo, String kat, String pu, int w, int mie){ super(upo,kat); puchar = pu; wartosc = w; miejsce = mie; @Override public String gettext(){ return super.gettext() +", miejsce:"+miejsce+ ", puchar:"+puchar + ", wartość:"+wartosc +"zł";
Polimorfizm public class Test { public static void main(string[] args) { Ćwiczenie 10. Nagroda nagrody[] = new Nagroda[12]; nagrody[0] = new NagrodaGlowna("książka", "JAKOSC", "duży puchar", 1000, 1); nagrody[1] = new NagrodaGlowna("książka", "cena", "duży puchar", 1000, 1); nagrody[2] = new NagrodaGlowna("książka", "SZYbkosc", "duży puchar", 1000, 1);... nagrody[11] = new NagrodaSpecjalna("kubeczki", "szybkosc", "Dyplom za innowacje"); for(nagroda n: nagrody){ System.out.println(n.getText());
Ograniczenie dziedziczenia Jeżeli nie chcemy aby dana klasa, bądź metoda była rozszerzana, można dodać do niej modyfikator final. Klasa opatrzona tym modyfikatorem nie może zostać nadklasą. Metoda opatrzona tym modyfikatorem nie może być przedefiniowana w podklasach. Dzięki oznaczaniu metod jako finalnych nie ma potrzeby używania wiązania dynamicznego (czasochłonnego) oraz zabezpieczamy kod przed niekontrolowanym przedefiniowaniem. public class Czytelnik{ private String numerkarty; public final void setnumerkarty(string nr){numerkarty = nr; public final String getnumerkarty(){return numerkarty;
Rzutowanie Rzutowanie jest zmianą typu obiektu na nowy (o ile to ma sens). Rzutowaniu mogą podlegać liczby double pi = 3.14; int p = (int) pi; bądź obiekty z jednej hierarchii dziedziczenia Czytelnik[] c = new Czytelnik[2]; c[0] = new Pracownik(); Pracownik p = (Pracownik) c[0]; package test13
Klasy i metody abstrakcyjne Niektóre klasy nie powinny posiadać obiektów. Jeżeli klasa tylko definiuje podstawowe pola i metody dla klas dziedziczących to jest klasą abstrakcyjną i oznacza się ją słowem kluczowym abstract. Klasa abstrakcyjna posiada metody abstrakcyjne, które są deklaracją metody, ale bez jej implementacji. Klasa pochodna, która ma tworzyć obiekty musi implementować wszystkie abstrakcyjne metody z nadklasy. public abstract class Czytelnik{ private String numerkarty; public abstract void wyswietl(); public class Pracownik extends Czytelnik{ public void wyswietl(){ System.out.println("Pracownik o nr: " + numerkarty); package test14
Klasa Object Klasa Object jest najbardziej ogólną klasą, po której dziedziczą wszystkie klasy Javy. Nie trzeba nawet dodawać tego dziedziczenia w kodzie. class Czytelnik{... class Czytelnik extends Object{... Dzięki wspólnej nadklasie, możliwe jest przechowywanie dowolnych referencji w zmiennych typu Object. Powrót do pierwotnego typu nastąpi wtedy przez rzutowanie. Object o = new Czytelnik(); Czytelnik c = (Czytelnik) o;
Klasa Object equals() Dzięki wspólnej nadklasie, wszystkie klasy implementują metodę equals() dzięki której możliwe jest porównywanie referencji do dwóch obiektów. Jeżeli obie referencje wskazują na to samo miejsce w pamięci, to zwracana jest wartość true. Czytelnik c1 = new Czytelnik(); Czytelnik c2 = new Czytelnik(); Czytelnik c3 = c1; c1.equals(c2); //false c1.equals(c3); //true Czytelnik Czytelnik Aby porównać stany dwóch obiektów należy tą metodę przeładować. c1 c2 c3 package test15
Klasa Object Uniwersalna metoda tostring() zwraca łańcuch znakowy będący reprezentację obiektu. tostring() Czytelnik c = new Czytelnik(); System.out.println(c.toString()); W każdej klasie można przeładować tą metodę uzyskując własną wersję reprezentacji znakowej. class Czytelnik{ public String tostring(){ return Mój czytelnik"; package test16
Klasa Object Zaimplementuj metody equals i tostring dla następującej klasy: public class Uzytkownik{ private String nazwakonta; private String haslo; private Date datarejestracji; public Uzytkownik(String nazwa, String has){ nazwakonta = nazwa; haslo = has; datarejestracji = new Date(); Ćwiczenie 11.
Klasa Object Ćwiczenie 11. public boolean equals(object o){ Uzytkownik1 u; Boolean takiesame = false; try{ u = (Uzytkownik1) o; takiesame = datarejestracji.compareto(u.getdatarejestracji())==0 && nazwakonta.equals(u.getnazwakonta()) && haslo.equals(u.gethaslo()); catch(classcastexception e){ System.out.println(e); return takiesame; public String tostring(){ SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.YYYY"); return "konto:"+ nazwakonta + " zarejestrowane:"+ sdf.format(datarejestracji.gettime());
Klasy wyliczeniowe Oprócz typów liczbowych można zadeklarować typ wyliczeniowy enum, który z góry definiuje wszystkie możliwe wartości zmiennej. Traktowany jest jak klasa. Powinien być umieszczony w osobnym pliku. enum PoraRoku{ WIOSNA, LATO, JESIEN, ZIMA; Klasy enum mogą posiadać konstruktory i własne metody. enum PoraRoku{ WIOSNA("marzec"), LATO("czerwiec"), JESIEN("wrzesien"), ZIMA("grudzien"); private String poczatekporyroku; private PoraRoku(String poczatek){poczatekporyroku = początek; public void wypisz(){ System.out.println(this + " zaczyna się w: " + poczatekporyroku); PoraRoku p = PoraRoku.WIOSNA; p.wypisz(); PoraRoku.LATO.wypisz(); package test17
Szablony klas - lista Istnieje czasem potrzeba, aby klasa obsługiwała różne typy danych zachowując przy tym ten sam zestaw operacji -> np. lista. Lista jest sposobem przechowywania wielu obiektów o tym samym typie. Umożliwia dodanie, wyszukanie bądź usunięcie obiektu. Ma zmienną długość. Można by utworzyć klasę która posiada pole będące obiektem oraz wskaźnik(referencję) na następny element ElementListy Obiekt 1 ElementListy Obiekt 2 ElementListy Obiekt 3 obiekt obiekt obiekt następny następny następny
Szablony klas Aby taki system działał dla dowolnych obiektów, pole obiekt musiałoby być typu Object. Przechowywanie elementów w takiej liście miałoby jednak bardzo dużą wadę: można by umieścić w niej obiekty dowolnych klas i zgubić typ podstawowy class ElementListy { private Object obiekt; private ElementListy nastepny; public ElementListy(Object o){ obiekt = o; public void dodajliste(elementlisty el){ nastepny = el; public Object getobiekt(){return obiekt; String s = bum ; Integer i = 3; ElementListy el1 = new ElementListy(s); ElementListy el2 = new ElementListy(i); el1.dodajliste(el2); package test18
Szablony klas Dostanie podstawowego typu wymaga rzutowania zwężającego musimy wcześniej znać typ obiektu. Jeżeli w liście są wymieszane typy, to jest to pracochłonne zadanie. Z tego powodu wprowadzono szablony klas i typy generyczne (generics). Mechanizm ten pozwala na utworzenie klasy, w której niektóre typy danych dopiero zostaną zdefiniowane podczas konstruowania obiektu. class ElementListy<T> { private T obiekt; private ElementListy<T> nastepny; public ElementListy(T o){ obiekt = o; public void dodajliste(elementlisty<t> el){ nastepny = el; public T getobiekt(){return obiekt; ElementListy<String> = new ElementListy<String>( bum );
Szablony klas Teraz nie będzie możliwości łączenia list z różnymi typami danych. Już podczas kompilacji sprawdzone zostaną typy. class ElementListy<T> { private T obiekt; private ElementListy<T> nastepny; public ElementListy(T o){ obiekt = o; public void dodajliste(elementlisty<t> el){ nastepny = el; public T getobiekt(){return obiekt; String s = bum ; Integer i = 3; ElementListy<String> el1 = new ElementListy<String>(s); ElementListy<Integer> el2 = new ElementListy<Integer>(i); el1.dodajliste(el2); //błąd!!! package test19
Szablony klas Zaproponuj klasę do przechowywania punktu płaszczyzny. Ćwiczenie 12. Dodaj dwa obiekty: - jeden punkt na rastrze(współrzędne opisane tylko przez liczby całkowite) - oraz jeden punkt na ciągłej płaszczyźnie (współrzędne opisane przez liczby rzeczywiste).
Szablony klas public class Punkt<T> { private T x; private T y; Ćwiczenie 12. public Punkt(T nx, T ny){ x = nx; y = ny; public T getx(){return x; public T gety(){return y; public void setpunkt(t nx, T ny){ x = nx; y = ny; Punkt<Integer> p1 = new Punkt<Integer>(1,2); Punkt<Float> p1 = new Punkt<Float>(1.1f,2.2f);
Szablony klas W szablonie może być więcej niż jeden zmienny typ generyczny. class Para<T,S> { private T pierwszyelementpary; private S drugielementpary;... Para<String,String> p1 = new Para<String,String>( aaa, bbb ); Para<String,Integer> p2 = new Para<String,Integer>( aaa,1); Para<Integer,String> p3 = new Para<Integer,String>(2, bbb ); Zawarte między znakami <> typy(np. String,String) nazywane są argumentami typu. Klasa Para<S,T> nazywana jest sparametryzowaną, a klasy Para<String,String> instancjami sparametryzowanej klasy Para<S,T> package test20
Szablony klas typy Niech będzie dana klasa sparametryzowana Box<T> posiadająca element i statyczny numer liczący liczbę instancji tej klasy. class Box<T> { private T element; public static int licznik=0; public Box(T e){ element = e; licznik++; public int getlicznik(){return licznik; Referencja na obiekt typu T Statyczne pole klasy inicjowane 0 Zwiększanie licznika Czy obiekty o różnych argumentach typów będą miały tą samą wartość numeru? package test21
Szablony klas typ surowy class Box<T> { private T element; public static int licznik=0;... public int getlicznik(){return licznik; Box<String> b1 = new Box<String>( aaa ); b1.getlicznik(); //1 Box<Integer> b2 = new Box<Integer>(3); b1.getlicznik(); //2 b2.getlicznik(); //2 Mimo różnych instancji klasy sparametryzowanej Box<T>, przechowywana w pamięci jest tylko jedna klasa Box (już bez parametrów) i jest to typ surowy. Dlatego pole statyczne jest wspólne dla obiektów b1 i b2. package test21
Szablony klas czyszczenie typów class Box<T> { private T element; public static int numer=0;... public T getelement(){return element; public void setelement(t e){element = e; Przechowywane w pamięci metody tracą argumenty typu, który zostaje zastąpiony przez Object. Mechanizm ten nazywa się czyszczeniem typów. Można sprawdzić typy przechowywane za pomocą mechanizmu refleksji. import java.lang.reflect.*; Box<String> b = new Box<String>( aaa ); Class klasa = b.getclass(); System.out.println(klasa); Method[] metody = klasa.getdeclaredmethods(); for (Method m : metody) System.out.println(m); Pakiet refleksji Pobranie klasy danego obiektu Pobranie listy metod z danej klasy package test22
Szablony klas ograniczanie typów W deklaracji klasy sparametryzowanej nie znamy typu, który dopiero podczas tworzenia obiektu zostanie podany. Dlatego nie można wewnątrz takiej klasy używać metod specyficznych dla pewnego typu obiektu. Jedyne które są dozwolone to metody klasy Object, ponieważ wszystkie klasy po niej dziedziczą. Jeżeli do działania potrzebowalibyśmy metody z konkretnej klasy, musimy zapewnić że typ obiektu będzie taką metodę posiadał. Można to zrobić stosując ograniczenie parametrów typu. Po extends dopisujemy klasy, bądź interfejsy, które typ T musi implementować. Dzięki temu można używać metod z nich. class Box<T extends String> { private T element; public static int numer=0;... public String wypisz(){return element.touppercase(); T musi teraz dziedziczyć po klasie String. Dzięki temu można wywołać metodę touppercase specyficzną dla tej klasy. Klasa String jest górnym ograniczeniem typu. package test23
Szablony klas Ćwiczenie 13. Dopisz metodę do klasy Punkt pozwalającą na obliczenie odległości między dwoma punktami.
Szablony klas public class Punkt<T extends Number> { private T x; private T y; Ćwiczenie 13. public T getx(){return x; public T gety(){return y; public double odleglosc(punkt<t> p2){ return Math.sqrt( Math.pow(x.doubleValue() - p2.getx().doublevalue(),2.0)+ Math.pow(y.doubleValue() - p2.gety().doublevalue(),2.0));
Szablony klas public class Test { Ćwiczenie 13. public static void main(string[] args) { Punkt<Integer> p1 = new Punkt<Integer>(1,2); Punkt<Integer> p2 = new Punkt<Integer>(4,5); Punkt<Float> p3 = new Punkt<Float>(1.1f,2.1f); Punkt<Float> p4 = new Punkt<Float>(2.1f,3.1f); System.out.println("Punkt["+p1.getX() +";"+p1.gety()+"]"); System.out.println("Punkt["+p2.getX() +";"+p2.gety()+"]"); System.out.println(p1.odleglosc(p2)); System.out.println(p3.odleglosc(p4));
Szablony klas parametry uniwersalne Czasem wymagane jest aby można było korzystać z możliwości dziedziczenia klas sparametryzowanych. Ponieważ po kompilacji wszystkie typy stają się surowe nie da się zapewnić, że będzie zgodność zarówno klas jak ich parametrów. Box<Integer> extends Box<Number> Wprowadzono zatem parametry uniwersalne? (wildcards). Dzięki temu można - zastosować ograniczenie górne: - dowolny podtyp A <? extends A> - zastosować ograniczenie dolne: - dowolny nadtyp A <? super A> - zastosować pełną dowolność: dowolny typ <?> Box<Integer> extends Box<? extends Number> Box<? super Integer> extends Box<Number> Box<Integer> extends Box<?>
Szablony klas Ćwiczenie 13a. Dopisz metodę do klasy Punkt pozwalającą na obliczenie odległości między dwoma punktami, bez względu na to jakiego typu są.
Szablony klas public class Punkt<T extends Number> { private T x; private T y; Ćwiczenie 13a. public T getx(){return x; public T gety(){return y; public double odleglosc(punkt<?> p2){ return Math.sqrt( Math.pow(x.doubleValue() - p2.getx().doublevalue(),2.0)+ Math.pow(y.doubleValue() - p2.gety().doublevalue(),2.0)); Punkt<Integer> p1 = new Punkt<Integer>(4,5);Punkt<Float> p2 = new Punkt<Float>(1.1f,2.1f); System.out.println(p1.odleglosc(p2));
Szablony klas parametry uniwersalne Czytelnik Lista<Czytelnik> SortLista<Czytelnik> Pracownik Lista<Pracownik> SortLista<Pracownik> Ochroniarz Lista<Ochroniarz> SortLista<Ochroniarz> Lista<T> SortLista<T> Lista<Czytelnik> lczyt1 = new Lista<Czytelnik>(new Czytelnik()); Lista<Czytelnik> lczyt2 = new Lista<Pracownik>(new Pracownik()); //Błąd!!! Lista<? extends Czytelnik> lczyt2 = new Lista<Pracownik>(new Pracownik()); Lista<? super Pracownik> lczyt3 = new Lista<Czytelnik>(p); package test24
Szablony klas Stosowanie typów sparametryzowanych (tu w skrócie ts.) ma pewne ograniczenia. Ts. może być typem pól, argumentów i wyniku metod Ts. nie mogą być typem pola statycznego, bo dla różnych instancji klas sparametryzowanych pola statyczne są wspólne. Można rzutować zmienne do ts., ale i tak po kompilacji zostanie zastosowanie czyszczenia typów. Nie można tworzyć obiektów ts. ponieważ nie wiadomo jakiego konstruktora używać. Można wywoływać metody jedynie z klasy do której nastąpi czyszczenie typów(zwykle Object, chyba że zastosowano ograniczenie typu) Nie można wywoływać metod konkretnej klasy, ponieważ nie wiadomo jaka będzie.
Szablony klas Stosowanie typów sparametryzowanych (tu w skrócie ts.) ma pewne ograniczenia. Ts. może być typem pól, argumentów i wyniku metod Nie można przeciążać metod tylko poprzez zmianę jednego ts. na inny, ponieważ po czyszczeniu typów oba zostaną zastąpione przez Object void set(t t1){ void set(s t2){ -> void set(object t1){ -> void set(object t1){ Można wywoływać metody jedynie z klasy do której nastąpi czyszczenie typów(zwykle Object, chyba że zastosowano ograniczenie typu) Nie można wywoływać metod konkretnej klasy, ponieważ nie wiadomo jaka będzie.
Kolekcje Jednym z najczęstszych zastosowań szablonów są kolekcje. Są to struktury przechowujące dowolne dane: Zbiór java.util.set przechowuje dowolną liczbę obiektów, nie zapamiętuje kolejności Lista java.util.list przechowuje obiekty w kolejności i możemy się odwoływać do każdego z nich po numerze indeksu Kolejka java.util.queue przechowuje obiekty w kolejności, ale dodaje się zawsze elementy na koniec, a obsługuje elementy z końca (kolejka LIFO(First In-Last Out)) albo z początku (FIFO(First In-First Out)) Mapa java.util.map przechowuje pary elementów klucz-wartość. Kolejność jest nieistotna, odwołujemy się do elementu po kluczu.
Kolekcje Aby utworzyć kolekcję trzeba wybrać jedną z implementacji: Zbiór java.util.set implementują HashSet LinkedHashSet TreeSet Lista java.util.list implementują ArrayList LinkedList Kolejkę java.util.queue implementują LinkedList PriorityQueue Mapa java.util.map implementują HashMap LinkedHashMap TreeMap
Adnotacje Adnotacje są fragmentem kodu rozpoczynającym się od @, który pozwala kompilatorowi na interpretację kodu. Jedną z najczęściej stosowanych adnotacji jest @Override, którą oznacza się metody przysłaniające te z nadklasy, bądź implementacji interfejsów. Dzięki temu kompilator może sprawdzić czy na pewno sygnatury się zgadzają i zawczasu poinformować o problemie. Adnotacje używane są podczas obsługi formularzy lub baz danych, dzięki czemu można sprawniej zarządzać kodem.
Praca domowa 2. Do systemu rezerwacji biletów lotniczych dopisz funkcjonalności: 1. Klient może rezerwować więcej niż jeden lot. 2. Rezerwowany bilet można anulować. 3. Za bilet można zapłacić. Nieopłacony bilet nie może zostać odprawiony. 4. Bilet można odprawić wraz z bagażem. 5. Lot posiada listę pasażerów lub biletów. 6. Lot można zrealizować, po czym bilety na ten lot usuwane są z listy u pasażerów. 7. Miejsca wylotu/przylotu są klasami. Lot je wykorzystuje. 8. Do rodzaju bagażu (lub taryfy biletu, rodzaju ulgi) użyte są typy wyliczeniowe. 9. Stan obiektu wypisuje na konsolę jedna metoda tej klasy. 10.Cena biletu wyliczana jest na podstawie długości trasy, taryfy/ulgi i zadeklarowanego bagażu.