Wybrane elementy języka Java 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 • Obsługa błędów semantycznych (runtime) w sposób efektywniejszy niż zwracanie kodu błędu metody • Podnoszenie wyjątku – Wywołanie metody podnoszącej wyjątek (throws) – Explicite: klauzulą throw Obsługa wyjątków • Wyjątki są zgodnie z definicją języka precise – zoptymalizowana implementacja może wykonać część instrukcji naprzód, ale w przypadku zgłoszenia wyjątku musi zadbać o to, żeby w widocznym stanie wykonania (user-visible state) wyliczone były dokładnie te wyrażenia, które wystąpiły przed wyrażeniem podnoszącym wyjątek • Znajdowana jest klauzula catch bloku try-catch dynamicznie zawierającego generujące wyjątek wyrażenie Obsługa wyjątków • Jaki będzie rezultat wykonania try{ try { throw new Exception1(); } catch(Exception1 e){ System.out.println("Wewnetrzny catch: ” +e.toString()); throw new Exception2(); } finally {throw new Exception3();} }catch (Exception e) {System.out.println("Zewnetrzny catch: ” +e.toString());} •Blok try-catch-finally ma jeden atrybut – propagowany dalej wyjątek. Zgodnie z definicją języka, wewnętrzny blok w tym przykładzie będzie propagował wyjątek klasy Exception3, a więc Wewnetrzny catch: mypackage18.Application1$Exception1 Zewnetrzny catch: mypackage18.Application1$Exception3 Przyczyny powstania wyjątku • Błąd wykonania JVM – wyjątek zgłaszany synchronicznie – Operacja niezgodna z semantyką języka (np. java.lang.ArithmeticException ) – Błąd ładowania/linkowania programu ( NoClassDefFound ) – Brak zasobu ( OutOfMemoryError, NoRouteToHostException ) • Jawne użycie klauzuli throw •Wyjątek podnoszony asynchronicznie –Może wystąpić potencjalnie w dowolnym punkcie wykonania bieżącego wątku –Inny wątek wywołuje stop() –Wewnętrzny błąd JVM ( VirtualMachineError ) Hierarchia wyjątków • checked (podklasy Exception nie będące RuntimeException) – muszą być wyspecyfikowane w klauzuli throws metody lub konstruktora; kompilator wymusza implementowanie obsługi takich wyjątków • Unchecked – kompilator nie wymusza na programiście obsługi (ani deklaracji throws) – RuntimeException (np. ArithmeticException, NullPointerException) - kompilator nie ma możliwości posłużenia się zaawansowanymi technikami dowodzenia poprawności programów i nie może przez to stwierdzić, czy dany ciąg instrukcji może wygenerować taki wyjątek lub nie – Error (np. OutOfMemoryError extends VirtualMachineError) – mamy do czynienia z „nienormalnym” (abnormal) stanem JVM Throwable Exception SQLException Error RuntimeException MyException IOException ThreadDeath VirtualMachineError NullPointerException ArithmeticException OutOf MemoryError IllegalArgumentException Error jest osobną, dziedziczącą po Throwable klasą – dzięki temu wszystkie wyjątki, które da się jakoś obsłużyć, można „potraktować” konstrukcją catch (Exception e) • 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 Pakiet java.lang.reflect • Dostęp do obiektów reprezentujących konstrukcje samego języka • Obiekt Class (pakiet java.lang) reprezentuje załadowaną postać binarną klasy; użycie: public static Class forName(String className) throws ClassNotFoundException • Method reprezentuje metodę klasy lub interfejsu Class c = Class.forName(nazwaKlasy); Method m[] = c.getDeclaredMethods(); for (int i = 0; i < m.length; i++) System.out.println(m[i].toString()); • Analogiczne użycie obiektów klas Field, Constructor • Interfejs Member implementowany przez Field, Constructor, Class i Method; udostępnia metodę getModifiers() • 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 Konstruktory • W kodzie konstruktora pierwsza instrukcja może, opcjonalnie, być explicite wywołaniem konstruktora. Wpp domyślnie super() jest pierwszą instrukcją kodu konstruktora • Explicite wywołania konstruktora – this(<lista_argumentow>) – super(<lista_argumentow>) – <wyrazenie>.super() Wyrazenie.super() • Tzw. kwalifikowane wywołanie konstruktora nadklasy. Przykład ze specyfikacji języka: class Outer { class Inner{ } } class ChildOfInner extends Outer.Inner { ChildOfInner() { (new Outer()).super(); } } Jak wykonywana jest konstrukcja wyrazenie.super() ? • Rozważamy konstrukcję class O { class S {} } class C extends O.S { C() { wyrazenie.super(argumenty); ciag_pozostalych_instrukcji_konstruktora } } • wyrazenie jest wyliczane. Jeśli jego wartością jest null, wywołanie konstruktora nadklasy kończy się wyjątkiem NullPointerException • Niech O będzie klasą bezpośrednią otaczającą statycznie (leksykalnie) klasę (C), w której konstruktorze wystąpiła konstrukcja wyrazenie.super • Jeśli wartością wyrazenia nie jest obiekt klasy O ani podklasy klasy O, generowany jest błąd kompilatora • Wyrażenia argumenty są wyliczane, od lewej do prawej • Konstruktor jest wywoływany • 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 GC i finalization • Środowisko uruchomieniowe Javy utrzymuje tablicę referencji do obiektów. – Referencja jest usuwana, gdy obiekt przestaje być widoczny – Jawne przypisanie null do zmiennej • Gdy nie ma więcej referencji do obiektu, – Finalization: użycie System.runFinalization() wywoła finalize() dla wszystkich obiektów czekających na postprzątanie – Garbage collection: użycie System.gc() jawnie uruchamia GC Osiągalność referencji • Wyznaczanie zbioru referencji nieosiągalnych (unreachable) dla żadnego z wątków programu • Jeśli wszystkie referencje do obiektu pochodzą z obiektów nieosiągalnych, sam obiekt jest nieosiągalny • Uwaga: dodatkowe warunki jeśli używamy JNI finalization • finalize() zaimplementowana w Object (jako pusta metoda), programista może ją przesłonić public class ProcessFile { protected void finalize() throws Throwable { try { file.close(); } finally { super.finalize(); }} • • (ciekawostka) w J2ME: brak metod związanych z garbage collection; jawne przypisanie null może zasugerować GC możliwość posprzątania obiektu „Reference Objects and Garbage Collection”, artykuł na stronie http://developer.java.sun.com/developer/technicalArticles/ALT/RefObj/ • • • • • 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 interface Action<E extends Exception> void run() throws E; } class AccessController { public static <E extends Exception> void exec(Action<E> action) throws E action.run(); } } { { public class Main { public static void main(String[] args) { try { AccessController.exec ( new Action<FileNotFoundException>() { public void run() throws FileNotFoundException { someFile.delete(); } } // new Action ... ); // AccessController.exec() }// koniec bloku try catch (FileNotFoundException e) { } } // koniec metody main() } // koniec class Main Typy sparametryzowane i JVM • Programy używające generics są tłumaczone do bytecode’u Javy, wykonywanego przez JVM • Proces nazywany erasure polega na mapowaniu typów generycznych do typów niesparametryzowanych • Niech |T| oznacza erasure typu T, zachodzą wtedy m.in. – |T <T1,...,Tn>| = |T| – |T[]| = |T|[] • Dokładny opis mechanizmu erasure, tłumaczenia metod i wyrażeń w „Adding Generics to the Java Programming Language: Participant Draft Specification”, dostępnym np. ze strony java.sun.com Typy sparametryzowane i tablice • Bez generics tablice mają następującą własność: Tablica referencji nadtypu jest nadtypem tablicy referencji podtypu • Wynika stąd np. to, że Object[] jest nadtypem String[] • Możemy dzięki temu m.in. napisać Object[] objArr = new String[10]; • Brak analogicznej własności w przypadku typów sparametryzowanych LinkedList<Object> objList = new LinkedList<String>(); daje błąd kompilacji Dynamiczna informacja o typie i array store check • Tablice w Javie zawsze posiadają dynamiczną informację o typie przechowywanych w nich obiektów • W czasie wykonywania operacji na tablicach wykonywany jest tzw. array store check, który może spowodować zgłoszenie ArrayStoreException Object objArr = new String[10]; objArr[0] = new Object(); kompiluje się, ale w czasie wykonania zgłaszą wyjątek • Programista może zakładać, że tablica zawiera elementy tego samego typu (albo podtypów tego typu) • Store check nie jest w ogóle wykonywany w przypadku kolekcji typów sparametryzowanych, bo poniższy kod LinkedList<Object> objList = new LinkedList<String>(); w ogóle się nie skompiluje Typy sparametryzowane i tablice • Tablice typów sparametryzowanych nie są dopuszczalne ze względów bezpieczeństwa class Box<T> { final T x; Box(T x) { this.x = x;} } Box<String>[] niebezpiecznaTablica = new Box<String>[3]; Object[] tablicaObiektow = niebezpiecznaTablica; tablicaObiektow[0] = new Box<Integer>(3); /* błąd nie znajdowany przez tzw. array store check */ String s = niebezpiecznaTablica[0].x; // BOOM! Typy sparametryzowane i rzutowanie • Kompilator „nie zna” typu sparametryzowanego przed uruchomieniem programu • zadanie utrzymania poprawności rzutowań do typu parametrycznego należy do programisty static Integer foo() { Double d = new Double(1.0); return (Integer) d; } • Powyższy kod nie kompiluje się z błędem inconvertible types (znaleziono Double, oczekiwano Integer) Typy sparametryzowane i rzutowanie public static Double return } class BadCast { <T> T foo() { d = new Double( 1.0 ); ( T ) d; public static void main( String[] args ) { System.out.println( BadCast.<Integer>foo() ); } } • Powyższy program nie generuje ostrzeżeń kompilatora i wykonuje się dając... 1.0 !!! • Dla przypomnienia class Number extends Object class Integer extends Number class Double extends Number • Kompilator „zauważyłby” błąd przy rzutowaniu, jeśli w kodzie funkcji foo() napisalibyśmy po prostu return d; • Kompilator nie generuje błędu, jeśli napiszemy return (T) d; • • • • • 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 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 System typów i autoboxing • Typy proste (primitives) i referencyjne (obiekty) • Czasem konstrukcja wymaga użycia typu referencyjnego: – Jako typu parametryzującego szablon – Jako obiektów kolekcji np. List • Konieczne rzutowania list.add(new Integer(1)); int i = ((Integer)list.get(0)).intValue(); • Autoboxing / autounboxing robi to automatycznie Integer integer = new Integer(1); integer += 1; • • • • • 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 • Dotychczas class Movement { public static final int UP = 0; public static final int LEFT = 1; public static final int DOWN = 2; public static final int RIGHT = 3; } • Wywołanie metody oczekującej parametru takiego typu wyliczeniowego z parametrem np. 5 nie wygeneruje błędu kompilacji • Nowa konstrukcja „enum” class Movement { public enum Direction {up, left, down, right} } • • • • • 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 Statyczne „import” • Po użyciu import static java.lang.Math.*; możemy pisać po prostu int i = abs(-1); zamiast kwalifikować dodatkowo nazwą pakietu int i = Math.abs(-1); • Potencjalne błędy kompilacji w przypadku powstającej w ten sposób dwuznaczności nazw • • • • • 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 Zmienna liczba argumentów public static int sum(int args...) { int sum = 0; for (int x : args ) /* użycie wcześniej przedstawianej konstrukcji „foreach” */ {sum += x; } return sum; } Literatura i URLs • Wprowadzenie do użycia Generics http://developer.java.sun.com/developer/technicalArticles/releases/generics/ • Artykuł o tablicach typów sparametryzowanych http://www.langer.camelot.de/Articles/Java/JavaGenerics/ArraysInJavaGenerics. htm • The Java Language Specification, dostępna z java.sun.com