Wybrane elementy języka Java – ciąg dalszy
Post on 30-Dec-2015
65 Views
Preview:
DESCRIPTION
Transcript
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ć rzutowaniaList 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.5List<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 Javypublic 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 sparametryzowanypublic 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> TreeTree<Integer> Tree
• Typ parametru jest mapowany do swojego upper boundT (w klasie Tree) Object
aleBush <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ącyclass 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 koduList<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ąclo.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_02class 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
Fruit i 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 typupublic 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));• WypisujeFalse• 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 switchKarta 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 Languagehttp://www.jcp.org/aboutJava/communityprocess/jsr/tiger/enum.html• Effective Java Programming: Substitutes for Missing C Constructshttp://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 enumerationshttp://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
top related