300 likes | 498 Views
Wybrane elementy języka Java – ciąg dalszy. Paweł Zdziarski. Wyjątki Reflection Tworzenie i zarządzanie obiektami Garbage Collector i finalize() Nowe elementy Javy 1.5 Typy sparametryzowane Covariant return types „ autoboxing ” Pętla w stylu foreach Bezpieczne ( type-safe ) enumeracje
E N D
Wybrane elementy języka Java – ciąg dalszy Paweł Zdziarski
Wyjątki • Reflection • Tworzenie i zarządzanie obiektami • Garbage Collector i finalize() • Nowe elementy Javy 1.5 • Typy sparametryzowane • Covariant return types • „autoboxing” • Pętla w stylu foreach • Bezpieczne (type-safe) enumeracje • Statyczne import • Metody ze zmienną liczbą parametrów
Wyjątki • Reflection • Tworzenie i zarządzanie obiektami • Garbage Collector i finalize() • Nowe elementy Javy 1.5 • Typy sparametryzowane • Covariant return types • „autoboxing” • Pętla w stylu foreach • Bezpieczne (type-safe) enumeracje • Statyczne import • Metody ze zmienną liczbą parametrów
Typy sparametryzowane • Obecne w np. C++ • Używane najczęściej w kontekście różnych operacji na kolekcjach • W Javie 1.4 (i wcześniejszych) musimy używać rzutowania List myIntList = new LinkedList(); myIntList.add(new Integer(0)); Integer x = (Integer) myIntList.iterator().next(); aby zapewnić poprawność typów w czasie wykonania. Kompilator wie jedynie, że obiekt typu LinkedList przechowuje elementy typu Object • Użycie generics w Javie 1.5 List<Integer> myIntList = new LinkedList<Integer>(); myIntList.add(new Integer(0)); Integer x = myIntList.iterator().next();
Typy sparametryzowane - składnia • Deklaracja: używamy formalnego parametru typu, np. public interface List<E> { void add(E x); Iterator<E> iterator(); } • Wywołanie: typ sparametryzowany, np. List <Integer> myList; • „intuicyjne” rozumienie wywołania: każde wystąpienie formalnego parametru typu w deklaracji typu sparametryzowanego zastępujemy faktycznym parametrem typu, np. zastępujemy we wszystkich metodach List<E> wystąpnienia E typem Integer • W rzeczywistości, skompilowany kod typu sparametryzowanego istnieje tylko w jednej kopii(jeden plik klasy .class)
Typy sparametryzowane - wildcards • Jak napisać metodę wypisującą wszystkie elementy kolekcji? void printCollection(Collection<Object> c) { for (Object e : c) { System.out.println(e);}} (używamy dodatkowo nowej postaci pętli for) • Skoro Collection<Object> nie jest nadtypem innej sparametryzowanej kolekcji, musielibyśmy stworzyć odpowiednie metody do wypisywania elementów Collection<String> etc. • Collection<?> jest nadtypem wszystkich kolekcji. ? Jest typem wildcard void printCollection(Collection<?> c) { for (Object e : c) { System.out.println(e);}}
Typy sparametryzowane – bounded wildcards • Nawet jeśli Shape jest nadtypem Circle, Rectangle etc., List<Shape> nie jest nadtypem List<Circle>, List<Rectangle> • Możemy jawnie zadeklarować tzw. upper bound parametru typu List <? extends Shape>
Typy sparametryzowane – implementacja, erasure • Problem używania kodu generics z kodem wcześniejszych wersji Javy public String loophole(Integer x) { List<String> ys = new LinkedList<String>; List xs = ys; xs.add(x); ys.iterator().next(); } • Jak we wcześniejszym przykładzie, tworzymy alias xs do zmiennej ys typu spararametryzowanego List<String> • Tutaj kod się kompiluje (kompilator daje jedynie ostrzeżenie), natomiast użycie błędnego typu daje błąd wykonania (ClassCastException) return (String) ys.iterator().next(); • Implementacja generics działa jako przód (front-end) normalnego kompilatora Javy, kod z użyciem generics jest zamieniany na normalny bytecode Javy w trakcie procesu nazwanego erasure
Typy sparametryzowane – implementacja, erasure • Kod sparametryzowany public String loophole(Integer x) { List<String> ys = new LinkedList<String>; List xs = ys; xs.add(x); ys.iterator().next(); } zostanie przetłumaczony do public String loophole(Integer x) { List ys = new LinkedList; List xs = ys; xs.add(x); return (String) ys.iterator().next(); // run time error } • Podwójna kontrola typów: • translator sprawdza poprawność użycia typów sparametryzowanych (np. nie moglibyśmy zadeklarować List<Object> xs) • Do wynikowego kodu „zwykłej” Javy dodawane są rzutowania wszędzie, gdzie może wystąpić niepoprawne (pod wzgl. typów) przypisanie
Typy sparametryzowane - erasure • Mechanizm polega na mapowaniu typów sparametryzowanych do typów niesparametryzowanych • Erasure klasy niesparametryzowanej nie zmienia definicji tej klasy • Typy sparametryzowane tracą typ parametru Tree<T> Tree Tree<Integer> Tree • Typ parametru jest mapowany do swojego upper bound T (w klasie Tree) Object ale Bush <T extends Color> T (w klasie Bush) Color • Wstawiane są odpowiednie rzutowania
Typy sparametryzowane - erasure • Czy skopiluje się class ShoppingCart<T extends DVD>{ // ... } class ShoppingCart<T extends VideoTape>{ // ... } ? • Nie: po erasure, obie klasy miałyby tą samą nazwę
Typy sparametryzowane - erasure • Czy skompiluje się class TwoForOneSpecial<T extends Rentable, W extends Rentable> { public void add(T newRentable) { //... } public void add(W newRentable) { //... } }? • Nie: po erasure, obie metody mają te same sygnatury
Typy sparametryzowane - erasure • Czy skompiluje się class GetAFreeVideoTape<T extends Rentable, W extends VideoTape> { public void add(T anything) { //... } public void add(W videotape) { //... } } ? • Tak: po erasure dostaniemy kod odpowiadający class GetAFreeVideoTape { public void add(Rentable anything) { //... } public void add(Videotape videotape) { //... } }
Typy sparametryzowane – jeden kod klasy • Dlaczego nie jest poprawna konstrukcja class MyClass<T> { static T member; } ? • Tworząc wiele instancji typu sparametryzowanego, np. • MyClass<Integer> myIntClass; • MyClass<String> myStringClass; • mielibyśmy tylko jedną kopię member w pamięci. Jakiego typu byłaby ta zmienna?
Typy sparametryzowane – jeden kod klasy • Jaki wynik da wykonanie List <String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass() == l2.getClass()); ? • Ponieważ kod z użyciem generics i tak jest tłumaczony do kodu bez typów sparametryzowanych, wszystkie instancje klas sparametryzowanych są tej samej klasy • true
Typy sparametryzowane i dziedziczenie • Fragment kodu List<String> ls = new ArrayList<String>(); List<Object> lo = ls; daje błąd kompilacji w linii 2 • Intuicyjne rozumienie List<Object> jako nadtypu List<String> nie jest poprawne! Rozważając lo.add(new Object()); String s = ls.get(0); • Wcześniej stworzyliśmy alias do obiektu lo o nazwie ls • W pierwszej linii wstawiamy do listy element typu Object – nie ma już gwarancji, że lista przechowuje elementy wyłącznie typu String • Jeśli cały kod kompilowałby się, druga linia nie zachowywałaby poprawności typów • Ogólnie, mimo iż A extends B to jednak List<B> nie jest podtypem List<A>
Wyjątki • Reflection • Tworzenie i zarządzanie obiektami • Garbage Collector i finalize() • Nowe elementy Javy 1.5 • Typy sparametryzowane • Covariant return types • „autoboxing” • Pętla w stylu foreach • Bezpieczne (type-safe) enumeracje • Statyczne import • Metody ze zmienną liczbą parametrów
Covariant return types • Do wersji 1.4 języka przesłaniająca implementowaną w nadklasie metoda podklasy musiała mieć identyczną sygnaturę – w szczególności, zwracany typ • Poniższy kod nie kompiluje się w JRE 1.4.1_02 class Fruit implements Cloneable { Fruit copy() throws CloneNotSupportedException { return (Fruit) clone(); }} class Apple extends Fruit implements Cloneable { Apple copy() throws CloneNotSupportedException { return (Apple) clone(); }} • Wywołując clone() na obiekcie Apple dostajemy obiekt nadklasy Fruiti musimy niepotrzebnie rzutować w dół do Apple • Java 1.5 dopuszcza taką konstrukcję
Wyjątki • Reflection • Tworzenie i zarządzanie obiektami • Garbage Collector i finalize() • Nowe elementy Javy 1.5 • Typy sparametryzowane • Covariant return types • „autoboxing” • Pętla w stylu foreach • Bezpieczne (type-safe) enumeracje • Statyczne import • Metody ze zmienną liczbą parametrów
Iterowanie po elementach kolekcji • Dotychczas (Java 1.4) używamy konstrukcji typu public void drawAll (Collection c) { Iterator itr = c.iterator(); while (itr.hasNext()) { ((Shape)itr.next()).draw(); } } • Używając typów parametrycznych, możemy zaoszczędzić sobie kodowania kilku rzutowań • public void drawAll (Collection<Shape> c) { • Iterator<Shape> itr = c.iterator(); • while (itr.hasNext()) { • itr.next().draw(); • } • }
Pętla „foreach” + generics • Nowa dopuszczalna postać pętli „for” public void drawAll(Collection<Shape> c) { for (Shape s:c) s.draw(); } • Rozwijane automatycznie do kodu for (Iterator<Shape> $i = c.iterator(); $i.hasNext();) { Shape s = $i.next(); s.draw(); }
Wyjątki • Reflection • Tworzenie i zarządzanie obiektami • Garbage Collector i finalize() • Nowe elementy Javy 1.5 • Typy sparametryzowane • Covariant return types • „autoboxing” • Pętla w stylu foreach • Bezpieczne (type-safe) enumeracje • Statyczne import • Metody ze zmienną liczbą parametrów
Bezpieczne (type-safe) typy wyliczeniowe • Typ wyliczeniowy – obecny w C, C++, C#, Pascalu • Dotychczas public class Karty { public static final int PIK = 0; public static final int TREFL = 1; public static final int KARO = 2; public static final int KIER = 3; } • Użycie wzorca (pattern) zamiast konstrukcji języka • Potencjalne problemy • Metoda oczekująca Karty skompiluje się nawet, jeśli jako parametr przekażemy literał np. 5 • Optymalizacja przez kompilator – inlining
Typy wyliczeniowe - inlining • Kompilator optymalizuje kod binarny włączając wartości stałych bezpośrednio do każdej klasy, która ich używa • Zgodnie ze specyfikacją języka, narzędzia mogą, ale nie muszą wspierać model rozproszonej pracy nad aplikacją i automatycznie rekompilować klasy używane przez program public class Test { public Test() { } static public void main(String[] args) { System.out.println(Karty.KARO); } } • Po zrekompilowaniu klasy Karty i zmianie wartości stałej KARO uruchomienie klasy Test (poza IDE) da rezutat identyczny jak przed zmianą stałej
Typy wyliczeniowe – wzorzec typesafe enum • Zdefiniuj klasę reprezentującą pojedynczy element „typu wyliczeniowego” (UWAGA: system typów Javy 1.4 nie obejmuje typu wyliczeniowego) • Nie udostępniaj żadnych publicznych konstruktorów • Jedynie pola public static final • Nigdy nie stworzymy obiektów tego typu poza tymi udostępnianymi przez pola public static final public class KartyPattern { public final String nazwa; private KartyPattern(String nazwa) {this.nazwa = nazwa;} public String toString() {return nazwa;} public static final KartyPattern PIK = new KartyPattern("pik"); public static final KartyPattern TREFL = new KartyPattern("trefl"); public static final KartyPattern KARO = new KartyPattern("karo"); public static final KartyPattern KIER = new KartyPattern("kier"); }
Typy wyliczeniowe – wzorzec typesafe enum • Użycie wzorca typesafe enum gwarantuje poprawność wykonania (runtime) • Jeśli zdefiniujemy metodę oczekującą parametru typu KartyPattern mamy pewność, że każda niezerowa (non-null) referencja przekazana do tej metody reprezentuje poprawny kolor karty • Stałe mogą być dodane do takiej klasy bez rekompilowania klas-klientów • Stałe nigdy nie są wkompilowane do klas ich używających (klientów) • Możemy zmienić metodę toString() aby uzyskać sensowną reprezentację stałych na ekranie • Porównywanie stałych odbywa się przez dziedziczoną z Object metodę equals wykonującą porównanie referencji, a nie np. kosztowne porównywanie String’ów • Wady: • Klasy J2SE i API’s produktów opartych na Javie rzadko używają wzorca typesafe enum, jest on generalnie mało popularny • Kod znacznie się rozrasta, jeśli chcemy dodać serializację klasy, uporządkowanie wartości etc.
Wzorzec typesafe enum – problemy z serializacją • Dodajemy public class KartyPattern implements Serializable • Poniższy kod ByteArrayOutputStream bout = new ByteArrayOutputStream (); ObjectOutputStream out = new ObjectOutputStream (bout); KartyPattern kp = KartyPattern.KARO; out.writeObject (kp); out.flush (); ByteArrayInputStream bin = new ByteArrayInputStream (bout.toByteArray ()); ObjectInputStream in = new ObjectInputStream (bin); KartyPattern kp2 = (KartyPattern) in.readObject (); System.out.println ((kp2 == KartyPattern.KARO || kp2 == KartyPattern.KIER || kp2 == KartyPattern.PIK || kp2 == KartyPattern.TREFL)); • Wypisuje False • W klasie implementującej Serializable musielibyśmy przesłonić metodę readResolve(), żeby upewnić się, że podczas deserializacji nie zostanie stworzona nowa instancja klasy, a użyte będzie stworzone wcześniej statyczne pole..
Typy wyliczeniowy w Java 1.5 • Wbudowany w specyfikację języka nowy typ: wyliczeniowy (enum) enum Karta (pik, trefl, karo, kier); • W odróżnieniu od klas wzorca typesafe enum, zmienne takiego typu mogą być używane w klauzuli switch Karta karta; switch karta{ case Karta.pik ... } • Nie są konieczne zmiany JVM • Implementowane przez kompilator • Nowa klasa java.lang.Enum, po której dziedziczą wszystkie zmienne typu wyliczeniowego • Deklaracja jest nowym rodzajem deklaracji klasy • W szczególności, nie jest legalne jawne tworzenie instancji enumeracji przy użyciu new()
Typy wyliczeniowy w Java 1.5 • A Typesafe Enum Facility for the Javatm Programming Language http://www.jcp.org/aboutJava/communityprocess/jsr/tiger/enum.html • Effective Java Programming: Substitutes for Missing C Constructs http://java.sun.com/developer/Books/shiftintojava/page1.html#replaceenums • Typesafe Enum: Using enum in J2SE 1.5 (Tiger) http://www.langrsoft.com/articles/enum.html • Beware of Java typesafe enumerations http://www.javaworld.com/javaworld/javatips/jw-javatip122.html
Literatura i URLs • Wprowadzenie do użycia Generics http://developer.java.sun.com/developer/technicalArticles/releases/generics/ • The Java Language Specification, dostępna z java.sun.com