Typy uogólnione. Programowanie obiektowe. Jacek Sroka na podstawie materiałów Janusza Jabłonowskiego. 8 kwietnia 2013

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

Kurs programowania. Wykład 9. Wojciech Macyna. 28 kwiecień 2016

Klasy abstrakcyjne, interfejsy i polimorfizm

Dawid Gierszewski Adam Hanasko

Dziedziczenie. Tomasz Borzyszkowski

Kurs programowania. Wykład 9. Wojciech Macyna

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

Programowanie obiektowe

Polimorfizm a klasy generyczne w języku Java. Zdzisław Spławski 1

Programowanie obiektowe

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

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

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

Programowanie obiektowe

Typy sparametryzowane

Kolekcje w Javie cz. 1

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

Programowanie obiektowe - 1.

Klasy abstrakcyjne i interfejsy

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

Polimorfizm, metody wirtualne i klasy abstrakcyjne

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

Laboratorium 03: Podstawowe konstrukcje w języku Java [2h]

Aplikacje w środowisku Java

Dziedziczenie. dr Jarosław Skaruz

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

Polimorfizm. dr Jarosław Skaruz

Szablony funkcji i klas (templates)

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

Programowanie obiektowe i zdarzeniowe

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Wykład 4: Klasy i Metody

Dokumentacja do API Javy.

PARADYGMATY PROGRAMOWANIA Wykład 4

Projektowanie obiektowe. Roman Simiński Polimorfizm

Aplikacje Internetowe. Najprostsza aplikacja. Komponenty Javy. Podstawy języka Java

Programowanie w Javie - wykład 3

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

Kurs WWW. Paweł Rajba.

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

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

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

Programowanie obiektowe

Programowanie obiektowe

10. Programowanie obiektowe w PHP5

Programowanie w Javie 1 Wykład i Ćwiczenia 3 Programowanie obiektowe w Javie cd. Płock, 16 października 2013 r.

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

TYPY GENERYCZNE (GENERICS)

Programowanie obiektowe. Wykład 5. C++: szablony

Rozdział 4 KLASY, OBIEKTY, METODY

Scala. Obiektowo-funkcyjny język programowania. Zbyszek Skowron

Interfejsy i klasy wewnętrzne

Programowanie w Internecie. Java

Programowanie obiektowe

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

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

Technologie i usługi internetowe cz. 2

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

Wykład 5: Klasy cz. 3

Programowanie obiektowe

Wykład 9: Polimorfizm i klasy wirtualne

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

Wykład 6: Dziedziczenie

Wyjątki. Streszczenie Celem wykładu jest omówienie tematyki wyjątków w Javie. Czas wykładu 45 minut.

Stos liczb całkowitych

Enkapsulacja, dziedziczenie, polimorfizm

Podstawy Programowania Obiektowego

Wykład 8: klasy cz. 4

Programowanie obiektowe

Szablony. Szablony funkcji

Szablony klas, zastosowanie szablonów w programach

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

Techniki programowania INP001002Wl rok akademicki 2018/19 semestr letni. Wykład 3. Karol Tarnowski A-1 p.

Języki Programowania II Wykład 3. Java podstawy. Przypomnienie

Definiowanie własnych klas

Java: interfejsy i klasy wewnętrzne

Programowanie obiektowe

java.util.* :Kolekcje Tomasz Borzyszkowski

Podstawy otwartych języków programowania Przechowywanie danych

Java - tablice, konstruktory, dziedziczenie i hermetyzacja

WSNHiD, Programowanie 2 Lab. 2 Język Java struktura programu, dziedziczenie, abstrakcja, polimorfizm, interfejsy

Programowanie obiektowe

Java Język programowania

Platformy Programistyczne Podstawy języka Java

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

Paradygmaty programowania

Programowanie Obiektowe Ćwiczenie 4

Język JAVA podstawy. Wykład 3, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Szablony funkcji i szablony klas

Programowanie obiektowe. Literatura: Autor: dr inŝ. Zofia Kruczkiewicz

Java podstawy jęyka. Wykład 2. Klasy abstrakcyjne, Interfejsy, Klasy wewnętrzne, Anonimowe klasy wewnętrzne.

KOTLIN. Język programowania dla Androida

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

Wykład 5 Okna MDI i SDI, dziedziczenie

MATERIAŁY DO ZAJĘĆ II

Język JAVA podstawy. Wykład 4, część 1. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Programowanie w Javie wykład 8 Interfejsy

Programowanie Komputerów

.NET Klasy, obiekty. ciąg dalszy

Wykład 7: Pakiety i Interfejsy

Transkrypt:

Programowanie obiektowe 8 kwietnia 2013

Po co są klasy i metody uogólnione w Javie? Klasy i metody uogólnione (generyki, ang. generics) - klasy i metody sparametryzowane typami. Czy zwiększają siłę wyrazu języka? Oczywiście nie (wszystkie sensowne języki programowania są równoważne maszynie Turinga). Czy wprowadzają możliwość pisania ogólnego kodu? Nie, ogólność w Javie już jest, dzięki zasadzie podstawialności i typowi Object. Po co więc??? Pozwalają na bezpieczne pisanie uogólnionego kodu.

Po co omawiać generyki na wykładzie z programowania obiektowego? Czy wykład z programowania obiektowego musi omawiać generyki? Nie. Nie są one częścią paradygmatu obiektowego. Czy wykład z programowania obiektowego powinien omawiać generyki? Tak, z kilku powodów (oczywiście o ile są dostępne w używanym języku): Są istotnym elementem wielu języków obiektowych. Ich znajomość jest potrzebna do omawiania istotnych hierarchii klas (np. kolekcji). Ich analiza może pozwolić dostrzec ciekawe zjawiska w używanym języku (np. anomalie typowania tablic w Javie). Są ciekawe i niebanalne.

Generyki Programowanie wymaga tworzenia abstrakcji. Klasy i ich hierarchie są bardzo efektywnym narzędziem do tworzenia abstrakcji. Parametryzowanie kodu pozwala abstrahować od pewnych szczegółów konkretnego zastosowania. Znamy już parametryzowanie: wartościami (parametry metod), czynnościami (parametry będące funkcjami - tak jak w C czy Pascalu - lub obiektami w językach obiektowych). Generyki pozwalają parametryzować typami.

Rodzaje polimorfizmu Polimorficzny (gr. polýmorphos od polýs liczny + morphé postać, kształt ciała ) wielokształtny, wielopostaciowy. Termin polimorfizm ma wiele znaczeń w informatyce. Na naszym wykładzie używamy go (poza tym slajdem) wyłącznie w znaczeniu obiektowym. Parametryzowanie kodu typami też jest nazywane polimorfizmem. Co gorsza jest wiele jego rodzajów 1 polimorfizm ad-hoc: sparametryzowany kod zachowuje się inaczej przy różnych typach parametryzujących. Przykład: przeciążanie nazw funkcji. polimorfizm parametryczny: sparametryzowany kod zachowuje się tak samo dla różnych typów parametryzujących. O tym będziemy dziś mówić na przykładzie Javy. polimorfizm podtypów: zastosowanie zasady podstawialności, zaczniemy od niego. 1 Nic dziwnego przy takiej nazwie....

Życie bez generyków Jest możliwe - skorzystajmy z zasady podstawiania. 1 public class PustyStos extends Exception { } 2 3 public class Stos { / / Uproszczona implementacja 4 private EltStosu wierzch ; 5 public boolean pusty ( ) { return wierzch == null ; } 6 public void wstaw ( Object e l t ) { 7 wierzch = new EltStosu ( e l t, wierzch ) ; } 8 public Object pobierz ( ) throws PustyStos { 9 i f ( pusty ( ) ) throw new PustyStos ( ) ; 10 Object wynik = wierzch. e l t ; 11 wierzch = wierzch. nast ; 12 return wynik ; 13 } 14 }

Życie bez generyków c.d. 1 class EltStosu { 2 public f i n a l Object e l t ; 3 public f i n a l EltStosu nast ; 4 / / Usprawiedliwić public! 5 public EltStosu ( Object e l t, EltStosu nast ) { 6 this. e l t = e l t ; 7 this. nast = nast ; 8 } 9 }

Życie bez generyków c.d. Mamy gotową maszynerię, zobaczmy jak działa. 1 public class Osoba { 2 private f i n a l S t r i n g i m i ę ; 3 4 public Osoba ( S t r i n g i m i ę ) { 5 this. i m i ę = i m i ę ; 6 } 7 8 public S t r i n g g e t I m i ę ( ) { 9 return i m i ę ; 10 } 11 }

Życie bez generyków c.d. I wreszcie korzystamy z naszego stosu. 1 Osoba os1 = new Osoba ( " Jasio " ) ; 2 Stos s = new Stos ( ) ; 3 4 s. wstaw ( os1 ) ; 5 Osoba os2 = s. pobierz ( ) ; Ups, nie kompiluje się. Słusznie, kompilator nie wie, czy na stosie była Osoba. 2 2 Miło żyć w kraju, w którym jest mnóstwo skojarzeń z byciem w kolejce a nie ma z byciem na stosie.

Życie bez generyków c.d. Teoretycznie możemy ten problem łatwo rozwiązać: 1 Osoba os2 = ( Osoba ) s. pobierz ( ) ; Ale to nie jest dobre rozwiązanie! Właśnie porzuciliśmy bezpieczny ocean języków ze statycznym sprawdzaniem typów i wpłynęliśmy na pełne wirów i raf morze tzw. dynamicznego typowania. 3 3 Może mniej finezyjnie, ale za to krócej: jest źle.

Rozwiązanie problemu Rozwiązaniem jest umożliwienie deklarowania klas sparametryzowanych typami. Pozwala to na: zachowanie ogólności - czyli na definiowanie jednej klasy pasującej do wielu typów. zachowanie bezpieczeństwa - kompilator może sprawdzać zgodność typów. Oprócz klas uogólnionych można też deklarować metody uogólnione.

Generyki w Javie a inne rozwiązania Rozwiązanie z Javy jest podobne do rozwiązań spotykanych w innych językach: C++: mechanizm szablonów - podobne przeznaczenie i składnia, kompletnie inna realizacja i moc wyrazu. Rozwiązanie z C++ pozwala na wykonywanie makroprogramów podczas kompilacji (sic!). C#: bardzo podobne rozwiązanie do tego z Javy, spójniejsze. Języki z dynamicznym typowaniem oczywiście nie mają i nie potrzebują takich mechanizmów Rozwiązanie z Javy musiało zachować zgodność z dotychczasową implementacją maszyny wirtualnej. Udało się, ale ma to swój koszt.

Przykład typu uogólnionego Chcemy stworzyć pojęcie pary 4. Nie chcemy z góry decydować, jakiego typu elementy będą występować w parach. Zatem definiujemy typ uogólniony, sparametryzowany typami elementów pary! 4 W sensie dwu elementów, a nie np. gazowego stanu wody.

Przykład typu uogólnionego cd 1 public class Para<T1, T2> { 2 private T1 pierwszy ; 3 private T2 d r u g i ; 4 public Para ( T1 pierwszy, T2 d r u g i ) { 5 this. pierwszy = pierwszy ; 6 this. d r u g i = d r u g i ; 7 } 8 9 public T1 getpierwszy ( ) { 10 return pierwszy ; 11 } 12 13 public void setpierwszy ( T1 pierwszy ) { 14 this. pierwszy = pierwszy ; 15 } 16 / /... i analogiczne akcesory dla drugiego pola 17 }

Przykład typu uogólnionego cd 1 S t r i n g s = "... " ; 2 Osoba o = new Osoba (... ) ; 3 4 / / nie musimy jawnie rzutować 5 / / kompilator może pilnować że wszystko pasuje 6 Para<Osoba, String > para = new Para<Osoba, String >(o, s ) ; 7 para. setpierwszy ( o ) ; 8 o = para. getpierwszy ( ) ;

Przykład typu uogólnionego cd Przypomnienie uwagi o konwencji nazewniczej akcesorów. Składniowo definiowanie typów uogólnionych polega na podaniu nazw parametrów określających typy w kątowych nawiasach. Zwykle używa się pojedynczych liter T, T1, T2, S, E, ale to może być dowolny identyfikator. Można podać dowolnie wiele tych parametrów oddzielając je przecinkami. W treści klasy używamy ich 5 jak zwykłych typów. Ukonkretniony typ uogólniony jest tak samo dobrym typem jak każdy inny typ. 5 Parametrów, nie przecinków.

Uwagi o typach uogólnionych Oczywiście można by chcieć mieć tylko pary jednorodne, bardzo łatwo to osiągnąć 1 public class ParaJednorodna <T> { / / reszta definicji... Ale można jeszcze łatwiej! 1 public class ParaJednorodna <T> extends Para<T, T> { 2 public ParaJednorodna ( T pierwszy, T d r u g i ) { 3 super ( pierwszy, d r u g i ) ; 4 } 5 }

Uwagi o typach uogólnionych cd Argumenty typów uogólnionych muszą być typami referencyjnymi (więc np. nie można podać jako parametru typu int, ale można za to podać typ Integer). Kompilator generuje automatyczne konwersje typów prostych na odpowiadające im typy referencyjne i w drugą stronę (ang. autoboxing), dzięki czemu to ograniczenie nie będzie specjalnie kłopotliwe. 1 Para<int, int > para = new Para<int, int >(7, 1 3 ) ; / / błąd 2 Para< Integer, Integer > para = 3 new Para< Integer, Integer >(7, 1 3 ) ; / / autoboxing 4 i n t p1 = para. getpierwszy ( ) ; / / autounboxing

Uwagi o typach uogólnionych cd Oczywiści obiekty klasy Para przechowują jedynie referencje do oryginalnych obiektów (a nie ich kopie). Pary przydają się na przykład wtedy, gdy tworzymy metodę dającą jako wynik dwie wartości. Pary z różnymi typami parametrów są oczywiście różne, zatem poniższy fragment programu nie da się skompilować: 1 Para< String, Integer > p1 = null ; 2 Para< Integer, String > p2 = p1 ; / / nie kompiluje się Wynika z tego, że typem nie jest sam szablon Para, lecz dopiero jego ukonkretnienie z podanymi argumentami.

Uwagi o typach uogólnionych cd Co daje sprawdzanie wykonywane przez kompilator? 1 public class Para<T1, T2> { 2 / /... 3 public void b ł ędna ( ) { 4 System. out. p r i n t l n ( pierwszy. g e t I m i ę ( ) ) ; 5 } Kompilator nie domyśla się, że chcemy mieć Osobę jako pierwszy element Pary. I bardzo dobrze! Kompilator przyjmuje, że typ będący parametrem szablonu może być dowolną podklasą klasy Object (czyli, innymi słowy, dowolnym typem referencyjnym).

Co umie każdy obiekt - tostring 1 Para< Integer, Integer > para = 2 new Para< Integer, Integer >(7, 1 3 ) ; 3 System. out. p r i n t l n ( " Para : " + para ) ; Na ekranie wypisze się coś w tym rodzaju: Para: przykładydowykładu.para@61de33 Przedefiniujmy więc tostring w Parze. 1 @Override 2 public S t r i n g t o S t r i n g ( ) { 3 return " Para ( " + pierwszy + ", " + d r u g i + " ) " ; 4 } Teraz przedstawiony poprzednio fragment programu wygeneruje: Para: Para(7, 13)

Co umie każdy obiekt - equals Wbrew pozorom equals nie jest łatwe. W Object oznacza identyczność. 1 System. out. p r i n t l n ( "Równość par : " + 2 new Para< Integer, Integer > ( 0, 0 ). 3 equals (new Para< Integer, Integer > ( 0, 0 ) ) ) ; Na ekranie wypisze się false. Przedefiniujmy więc equals w Parze.

Co umie każdy obiekt - equals 1 @Override 2 public boolean equals ( Object o ) { 3 i f (! ( o instanceof Para <?,? >)) return false ; 4 Para<T1, T2> p = ( Para<T1, T2>)o ; 5 / / Ostrzeżenie kompilatora: unchecked cast 6 i f ( ( pierwszy!= null ) && ( d r u g i!= null ) ) 7 return pierwszy. equals ( p. getpierwszy ( ) ) && 8 d r u g i. equals ( p. getdrugi ( ) ) ; 9 else 10 i f ( ( pierwszy == null ) && ( d r u g i == null ) ) 11 return ( p. getpierwszy ( ) == null ) && 12 ( p. getdrugi ( ) == null ) ; 13 else / / dokładnie jeden z elementów pary jest null 14 i f ( pierwszy == null ) 15 return ( p. getpierwszy== null ) && 16 ( d r u g i. equals ( p. getdrugi ( ) ) ) ; 17 else / / drugi==null 18 return pierwszy. equals ( p. getpierwszy ( ) ) && 19 ( p. getdrugi ()== null ) ; 20 }

Co umie każdy obiekt - equals - komentarz Problem 0: zdecydowanie za długie. Problem 1: w nagłówku jest (słusznie zresztą) Object. Czy równe obiekty muszą mieć ten sam typ? Czyli czy użyć instanceof (nie muszą) czy getclass() (muszą)? Muszą: równość musi być symetryczna! Nie muszą: dwa napisy "Alamakota" zapisane w dwu podklasach klasy Napis, realizujących różne implementacje napisu powinny być równe! Pisząc implementację metody equals() chcemy by definiowała ona relację równoważności oraz wielokrotne wywołanie dawało ten sam wynik.

Co umie każdy obiekt - equals - komentarz Problem 2: Możliwe wartości null. Na szczęście dla null operator instanceof da wynik negatywny. Problem 3: o instanceof Para<?,?>. Czemu nie o instanceof Para<T1,T2>? Bo chcemy móc się porównać z dowolną parą. Bo się nie skompiluje - w czasie wykonania nie ma typów uogólnionych. o instanceof Para<Object,Object>) też nie. Można napisać o instanceof Para.

Co umie każdy obiekt - equals Jak zobaczymy przy kolekcjach zmieniając equals zwykle należy przedefiniować hashcode. Implikacja a.equals(b) a.hashcode() == b.hashcode(). 1 public i n t hashcode ( ) { 2 return pierwszy. hashcode ( ) & d r u g i. hashcode ( ) ; 3 / / a może: return 13;??? 4 / / wielokrotne wywołania muszą dać ten sam wynik, 5 / / więc implementacje nie może zależeć od składowych transient 6 }

a dziedziczenie Można dowolnie 6 mieszać. Zwykłe klasy mogą dziedziczyć po uogólnionych. 1 public class ParaInt extends Para< Integer, Integer > { 2 public ParaInt ( I n t e g e r pierwszy, I n t e g e r d r u g i ) { 3 super ( pierwszy, d r u g i ) ; 4 } 5 } Uogólnione po uogólnionych. 1 public class ParaJednorodna <T> extends Para<T, T> { 2 public ParaJednorodna ( T pierwszy, T d r u g i ) { 3 super ( pierwszy, d r u g i ) ; 4 } 5 } I uogólnione po zwykłych. 6 Prawie, wyjątki omawiamy na koniec.

Argumenty typów uogólnionych Mogą być dowolnie złożone (nie mogą być typami prostymi). Mogą być ukonkretnieniami typów złożonych 1 Para< Para< String, Integer >, String > dużapara = 2 new Para<Para< String, Integer >, String >( 3 new Para< String, Integer >( " Ola ma psa ", 13), 4 " Ala ma kota " ) ; Skrócona składnia od Javy 7.0 1 Para< Para< String, Integer >, String > dużapara = 2 new Para < >( 3 new Para <>( " Ola ma psa ", 13), 4 " Ala ma kota " ) ;

Jeszcze jedna uwaga o equals Zauważmy, że nasza definicja equals daje zgodne z intuicją wyniki 1 ParaJednorodna < Integer > p j = 2 new ParaJednorodna < Integer >(13, 1 3 ) ; 3 ParaInt p i = new ParaInt (13, 1 3 ) ; 4 System. out. p r i n t l n ( "Równość par r óżnych klas : " + 5 p i. equals ( p j ) + " i " + 6 p j. equals ( p i ) ) ; Wynikiem jest true.

Metody uogólnione Są. Przy użyciu (zwykle) nie podaje się już argumentów będących typami. 1 public class TestMetodyUogó l n i o n e j { 2 <T> ParaJednorodna <ParaJednorodna <T>> 3 twórzparępar ( T e l t ) { 4 return new ParaJednorodna <ParaJednorodna <T>> 5 (new ParaJednorodna <T>( e l t, e l t ), 6 new ParaJednorodna <T>( e l t, e l t ) ) ; 7 } 8 9 void t e s t ( ) { 10 System. out. p r i n t l n ( " Para par I n t e g e r " + 11 twórzparępar ( 3 ) ) ; 12 System. out. p r i n t l n ( " Para par S t r i n g " + 13 twórzparępar ( " Ala ma kota " ) ) ; 14 } 15 }

Ograniczenia na parametry typów uogólnionych Dotąd poznany mechanizm nie jest wystarczający. 1 public class ParaPosortowana <T> extends ParaJednorodna <T> 2 public ParaPosortowana ( T pierwszy, T d r u g i ) { 3 super ( pierwszy, d r u g i ) ; 4 5 i f ( pierwszy. compareto ( d r u g i ) > 0 ) { 6 / / nie kompiluje się 7 setpierwszy ( d r u g i ) ; 8 setdrugi ( pierwszy ) ; 9 } 10 } Kompilator ma rację - nie każda klasa potrafi się porównywać.

Dygresja - interfejs Comparable Bardzo prosty i standardowy. 1 public interface Comparable<T>{ 2 public i n t compareto ( T o ) ; 3 }

Typ z ograniczeniami na parametry 1 public class ParaPosortowana <T extends Comparable<T>> 2 extends ParaJednorodna <T>{ 3 4 public ParaPosortowana ( T pierwszy, T d r u g i ) { 5 super ( pierwszy, d r u g i ) ; 6 7 i f ( pierwszy. compareto ( d r u g i ) > 0 ) { 8 setpierwszy ( d r u g i ) ; 9 setdrugi ( pierwszy ) ; 10 } 11 } 12 } Teraz się kompiluje!. Gdy jawnie nie podamy ograniczenia kompilator przyjmie extends Object.

Typ z ograniczeniami na parametry Można podać wiele ograniczeń na jeden parametr. Jeśli wśród ograniczeń jest klasa, to musi być jedna i na pierwszym miejscu (tu dla interfejsów też używamy extends). Stwórzmy parę sparametryzowaną typem elementów obu pól, który musi być klasą: będącą parą jednorodną (lub jej podklasą), implementującą porównywanie, implementującą serializację.

Typ z ograniczeniami na parametry 1 public class ParaWymagająca 2 <T1, T2 extends ParaJednorodna <T1> & 3 S e r i a l i z a b l e & Comparable<T2>> 4 extends ParaJednorodna <T2> { 5 public ParaWymagająca ( T2 pierwszy, T2 d r u g i ) { 6 super ( pierwszy, d r u g i ) ; 7 } 8 } 9 10 / /... 11 ParaPosortowana < String > pp = 12 new ParaPosortowana < String >( " jeden ", "dwa" ) ; 13 ParaWymagająca< String, ParaPosortowana < String >> pw = 14 new ParaWymagająca< String, 15 ParaPosortowana < String >>(pp, pp ) ;

Typ z ograniczeniami na parametry T1 było tylko pomocnicze. Java pozwala na użycie dżokerów?. W niektórych konstrukcjach nawet je preferuje.

Typ z ograniczeniami na parametry 1 public class ParaWymagająca2 2 <T extends ParaJednorodna <?> & 3 S e r i a l i z a b l e & Comparable<T>> 4 extends ParaJednorodna <T> { 5 public ParaWymagająca2 ( T pierwszy, T d r u g i ) { 6 super ( pierwszy, d r u g i ) ; 7 } 8 } 9 / /... 10 ParaWymagająca2<ParaPosortowana < S t r i n g >> pw2 = 11 new ParaWymagająca2<ParaPosortowana < String >>(pp, pp ) ;

Dalsze możliwości w skrócie Można wyrażać zależności między samymi parametrami 1 public <T1, T2 extends T1> 2 void wstaw ( C o l l e c t i o n <T1> a, T2 b ) { /... / } Dżokery pozwalają pomijać szczegóły... 1 public i n t ileelement ów( C o l l e c t i o n <?> c ) { /... / } 2 public <T> i n t ileelement ów( C o l l e c t i o n <T> c ) { /... / }... i opisywać ograniczenia górne 1 public <T> void wstaw ( C o l l e c t i o n <? super T> kol, T e l t ) ; Jeszcze do tego wrócimy po omówieniu kolekcji

Problemy z typami uogólnionymi w Javie Implementacja Javy musiała dopasować się do istniejących bibliotek i maszyn wirtualnych. Stąd pomysł z wymazywaniem typów (ang. type erasure) i wykorzystaniem dziedziczenia przez typy referencyjne po typie Object - kompilator sprawdza zgodność typów, a potem usuwa informacje o typach uogólnionych.

Problemy z typami uogólnionymi w Javie Maszyna wirtualna nie wie o typach uogólnionych więc Nie można tworzyć obiektów (ani tablic) typów będących parametrami typu uogólnionego - kompilator nie wie jakie konstruktory ma ten typ, a podczas wykonywania typ ten nie będzie znany. Rzutowania do typów uogólnionych powodują ostrzeżenia kompilatora, który nie może wygenerować kodu weryfikującego poprawność rzutowania (przykład dalej). Nie można stosować instanceof do typów uogólnionych (tylko z dżokerami lub surowymi) - poza tym jest tu problem z tym jak wygląda hierarchia klas. nie mogą być typami wyjątków - szukanie obsługi dzieje się na podstawie typu podczas wykonywania programu. Problemy z refleksją. No i problem z typami prostymi - nie mogą być argumentami typów uogólnionych.

Przykład 1 Para< String, String > ps = 2 new Para< String, String >( " ala ", " ola " ) ; 3 Para< Integer, Integer > p i = 4 new Para< Integer, Integer >(7, 1 3 ) ; 5 Para p = ps ; / / zastosowanie surowego typu 6 p i = ( Para< Integer, Integer >) p ; 7 / / Ostrzeżenie kompilatora: unchecked cast 8 System. out. p r i n t l n ( " p i = " + p i ) ; 9 / / pi jest parą napisów! pi = Para(ala, ola)

1 public class MyType<E> { 2 class Inner { } 3 s t a t i c class Nested { } 4 5 public s t a t i c void main ( S t r i n g [ ] args ) { 6 MyType mt ; / / warning: MyType is a raw type 7 MyType. Inner inn ; / / warning: MyType.Inner is a raw type 8 9 MyType. Nested nest ; / / no warning: not parameterized type 10 MyType<Object > mt1 ; / / no warning: type parameter given 11 MyType<?> mt2 ; / / no warning: type parameter given (wildcard OK!) 12 } 13 }

Podsumowanie Na pewno typy uogólnione są potrzebne. Realizacja tego mechanizmu w Javie zaspokaja większość potrzeb programistów. Mogłaby być lepsza, gdyby ten mechanizm został wbudowany w język od jego pierwszej wersji. Pełny opis typów uogólnionych wykracza poza ramy tego wykładu.