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