N IW ERSYTETKONOM IC ZNYWRAKOW IE 7. Typy generyczne
Transkrypt
N IW ERSYTETKONOM IC ZNYWRAKOW IE 7. Typy generyczne
Algorytmy i Struktury Danych 7. Typy generyczne Typy generyczne pozwalają parametryzować definicję klas i interfejsów typami, które będą określone później (np. podczas użycia klasy). Takie rozwiązane pozwala na bardziej elastyczne korzystanie ze zdefiniowanych klas. Programista chcą przechować różne typy danych nie musi już dokonywać niewygodnego rzutowania. Prosty przykład wykorzystania typów generycznych UNIWERSYTET EKONOMICZNY W KRAKOWIE Załóżmy, że mamy napisać klasę Pudelko przechowującą obiekty dowolnego typu. Bez zastosowania typów generycznych nasza klasa mogłaby wyglądać tak: class Pudelko { private Object obiekt; public Pudelko(Object obiekt) { this.obiekt = obiekt; } public Object pobierz() { return obiekt; } } Dodajmy jeszcze przykładową klasę Student przechowujące dane studenta. class Student { private String imie, nazwisko; public Student (String imie, String nazwisko) { this.imie = imie; this.nazwisko = nazwisko; } public String toString() { return ("[" + imie + " " + nazwisko + "]"); } } Przykładowe wykorzystanie klasy Pudelko bez typów generycznych. public class TestPudelka { public static void main (String[] args) { Pudelko a = new Pudelko(new Student("Jan", "Kowalski")); Pudelko b = new Pudelko(new Integer(123)); Pudelko c = new Pudelko(new String("Informatyka")); // Student testowy = a.pobierz(); niepoprawnie! (pobierz zwraca Object) Student testowy = (Student) a.pobierz(); // musimy dokonać rzutowania 32 Typy generyczne System.out.println(testowy); // int liczba = b.pobierz(); niepoprawnie! (pobierz zwraca Object) int liczba = (Integer) b.pobierz(); // musimy dokonać rzutowania System.out.println("Liczba: " + liczba); // System.out.println(c.pobierz().charAt(0)); niepoprawnie! (Object) System.out.println(((String) c.pobierz()).charAt(0)); // rzutowanie } } UNIWERSYTET EKONOMICZNY W KRAKOWIE Zastosowanie typów generycznych umożliwia nam sparametryzowanie klasy Pudelko przez co nie musimy dokonywać rzutowania obiektów. class Pudelko <T> { private T obiekt; public Pudelko(T obiekt) { this.obiekt = obiekt; } public T pobierz() { return obiekt; } } Tworząc obiekty klasy Pudelko, musimy podać jakiego typu dane będzie ona przechowywać (określamy to zarówno w deklaracji zmiennej referencyjnej jak i podczas wywołania konstruktora). Typy generyczne przeznaczone są wyłącznie dla typów obiektowych (nie można używać ich dla typów prymitywnych). public class Test { public static void main (String[] args) { Pudelko<Student> a = new Pudelko<Student>(new Student("Jan", "Kowalski")); Pudelko<Integer> b = new Pudelko<Integer>(new Integer(123)); Pudelko<String> c = new Pudelko<String>(new String("Informatyka")); Student testowy = a.pobierz(); // rzutowanie nie jest potrzebne System.out.println(ostatni); int liczba = b.pobierz(); // rzutowanie nie jest potrzebne System.out.println("Liczba: " + liczba); System.out.println(c.pobierz().charAt(0)); // rzutowanie nie jest potrzebne } } 33 Algorytmy i Struktury Danych Konwencja nazw Wykorzystując typy generyczne przyjmuje się określanie ich nazw za pomocą pojedyńczej dużej litery ujętej w nawiasy ostre. Przykładowe nazwy: E - Element (używane np. w Java Collections Framework), K - Klucz, N - Liczba, T - Typ, V - Wartość, S, U, V itd. - drugi, trzeci, czwarty parametr. UNIWERSYTET EKONOMICZNY W KRAKOWIE Typy generyczne mają duże znaczenie dla funkcjonowania kolekcji. Pozwalają one definiować typ elementów przechowywanych w poszczególnych implementacjach oraz uniemożliwiają dodawanie do kolekcji elementów innych typów niż zdefiniowane (sprawdzenie odbywa się już na etapie kompilacji programu). Lista jednostronna jednokierunkowa z wykorzystaniem typów generycznych W poprzednich rozdziałach tworzyliśmy struktury danych przechowujące elementy tylko jednego typu (String, char, int, Klient, Student, PlytaCD). Poniżej przykład listy jednostronnej jednokierunkowej wykorzystującej typy generyczne. Najpierw tworzymy klasę Wezel. class Wezel<T> { private T obiekt; private Wezel<T> nastepny; // pola klasy public Wezel() { this(null, null); } // konstruktor domyślny public Wezel(T obiekt, Wezel<T> nastepny) { this.obiekt = obiekt; this.nastepny = nastepny; } // konstruktor dwuparametrowy public Wezel<T> pobierzNastepny() { return nastepny; } // metoda dostępowa public void ustawNastepny(Wezel<T> nastepny) { // metoda modyfikująca this.nastepny = nastepny; } public T pobierzObiekt() { return obiekt; } } 34 // metoda dostępowa Typy generyczne Przykładowa klasa implementująca listę jednostronną jednokierunkową. class Lista<T> { private Wezel<T> poczatek; private int rozmiar; public Lista () { poczatek = null; rozmiar = 0; } // referencja do początku listy // rozmiar listy // konstruktor domyślny UNIWERSYTET EKONOMICZNY W KRAKOWIE public void wstawNaPoczatek(T dane) { // wstawia element na poczatek listy poczatek = new Wezel<T>(dane, poczatek); rozmiar++; } public Wezel<T> usunZPoczatku() { // usuwa element z początku listy Wezel<T> temp = poczatek; poczatek = poczatek.pobierzNastepny(); rozmiar--; return temp; } public boolean czyPusta() { // sprawdza czy lista jest pusta return (poczatek == null); } public void wyczysc() { poczatek = null; rozmiar = 0; } // usuwa listę public int pobierzRozmiar() { return rozmiar; } // zwraca rozmiar listy public void wyswietl() { // wyświetla listę Wezel<T> temp = poczatek; while( temp != null ) { System.out.print(temp.pobierzObiekt() + " "); temp = temp.pobierzNastepny(); } } } Sprawdźmy działanie naszej listy. public class TestListy { public static void main (String[] args) { Lista<String> listaPierwsza = new Lista<String>(); // lista łańcuchów tekst. listaPierwsza.wstawNaPoczatek("Adam"); listaPierwsza.wstawNaPoczatek("Marek"); listaPierwsza.wstawNaPoczatek("Kasia"); listaPierwsza.wyswietl(); System.out.println ("Rozmiar listy: " + listaPierwsza.pobierzRozmiar()); listaPierwsza.usunZPoczatku(); 35 Algorytmy i Struktury Danych listaPierwsza.wyswietl(); System.out.println ("Czy pusta? " + listaPierwsza.czyPusta()); Lista<Integer> listaDruga = new Lista<Integer>(); // lista liczb całk. listaDruga.wstawNaPoczatek(10); listaDruga.wstawNaPoczatek(45); listaDruga.wstawNaPoczatek(83); listaDruga.wyswietl(); } } Wynik działania aplikacji UNIWERSYTET EKONOMICZNY W KRAKOWIE Kasia Marek Adam Rozmiar listy: 3 Marek Adam Czy pusta? false 83 45 10 Standardowe klasy biblioteczne Typy generyczne zostały wprowadzone do Javy dopiero od wersji 5.0. Większość klas występujących w kolekcjach (tzw. Java Collection Framework) obsługuje w pełni parametyzowanie. Do najczęściej wykorzystywanych, podczas tworzenia struktur danych, klas bibliotecznych należą: LinkedList<E> - implementacja listy dwukierunkowej z wykorzystaniem listy powiązanej ArrayList<E>- implementacja listy z wykorzystaniem tablicy Vector<E> - implementacja listy z wykorzystaniem tablicy Stack<E> - implementacja stosu Queue<E> - implementacja kolejki Przykładowe metody zawarte w klasie LinkedList<E>: boolean add(E e) void addFirst(E e) void addLast(E e) E getFirst() E getLast() E remove() E removeFirst() E removeLast() int size() void clear() boolean contains(Object o) // // // // // // // // // // // dodaje element na koniec listy dodaje element na początek strony dodaje element na koniec listy zwraca pierwszy element z listy zwraca ostatni element z listy zwraca i usuwa pierwszy element listy zwraca i usuwa pierwszy element listy zwraca i usuwa ostatni element listy zwraca rozmiar listy usuwa wszystkie elementy z listy sprawdza czy dany element jest na liście Zadania do wykonania 1. Napisz klasę Para przechowującą pary obiektów. Dopisz odpowiednie metody umożliwiające dostęp do tych elementów oraz ich zmianę. Wykorzystaj typy generyczne. Przykładowe utworzenie obiektów: 36 Typy generyczne Para<String, Student> p1 = new Para<String, Student> ("Ala", new Student("Adam","Nowak")); Para<Integer, Float> p2 = new Para<Integer, Float> (new Integer(123), new Float(3.14)); 2. W oparciu o przedstawiony poniżej interfejs IStos<E> napisz klasę Stos<E> implementującą ten interfejs. UNIWERSYTET EKONOMICZNY W KRAKOWIE interface IStos<E> { public void odloz(E element); public E sciagnij(); public boolean czyPusty(); public E zobacz(); public int rozmiar(); public void wyswietl(); // // // // // // // odkłada na stosie element zwraca ściągniętą ze stosu element sprawdza czy stos jest pusty zwraca element znajdujący się na szczycie stosu (nie ściąga go) zwraca rozmiar stosu (liczbę jego elementów) wyświetla zawartość stosu } class Stos<E> implements IStos<E> { ... // metody zgodnie z interfejsem IStos<E> } Wykorzystaj klasę LinkedList<E>. 3. Zaprojektuj interfejs IKolejka<E> oraz napisz klasę Kolejka<E> implementującą ten interfejs. Sprawdź działanie programu tworząc kolejkę obiektów klasy Student, Klient, PlytaCD oraz liczb całkowitych (klasa Integer). 37