Java niezbędnik programisty spotkanie nr 10 Typy wyliczeniowe, auto. opakowywanie/odpakowywanie,... 1
Jak nie należy implementować typu wyliczeniowego Opracował class Przedmiot1 { public static final int BD = 1; public static final int IO = 2; public static final int NIEZBĘDNIK = 3; public static final int XML = 4; public static final int J2EE = 5; //przykładowa metoda String dajnazwę(int i) { switch (i) { case 1: return "Bazy danych"; case 2: return "Inżynieria oprogramowania"; case 3: return "Java niezbędnik programisty"; case 4: return "XML i metody zarządzania treścią"; case 5: return "Java 2 Platform, Enterprise Edition"; default: String s = "Nie ma takiego przedmiotu: " + i; assert (true) : s; return s; 2
Wady/Zalety Zalety: Prostota! Kodowanie informacji przy pomocy masek bitowych (np. użycie kolejnych potęg 2). Jeżeli nie stworzyliśmy dziur w numeracji to łatwo iterować. Wady: Nie ma kontroli typów. Przedmioty przekazujemy jako wartości całkowite. Niestaranny programista może przez pomyłkę przekazać jako parametr inną stałą całkowitą, np. PowodyNaZawalenieNocy.DEBUGOWANIE_NIESTARANNEGO_KODU Nie zabraniamy przekazywania wprost wartości z pominięciem zdefiniowanych stałych, np. dajnazwe(3). Zawsze trzeba być przygotowanym, że ktoś przekaże wartość z poza zakresu, np. dajnazwe(13). Nie można łatwo zmienić numeracji. Wniosek: Nie wbijajmy gwoździ ręką. Zbudujemy sobie młotek! 3
Typy wyliczeniowe zrób to sam //żeby nam nikt nie zrobił dziwnych przedmiotów w podklasie final class Przedmiot2 { private int numer;//przyda się jak będziemy switchować public int getnumer() { return numer; private String pełnanazwa;//jedynie do odczytu String dajnazwę(przedmiot2 p) { return p.pełnanazwa; private Przedmiot2(int nr, String pn) {//ograniczona liczba egzemplarzy this.numer = nr; this.pełnanazwa = pn; public static final Przedmiot2 BD = new Przedmiot2(1, "Bazy danych"), IO = new Przedmiot2(2, "Inżynieria oprogramowania ); NIEZBĘDNIK = new Przedmiot2(3, "Java niezbędnik programisty ); XML = new Przedmiot2(4, "XML i metody zarządzania treścią ); J2EE = new Przedmiot2(5, "Java 2 Platform, Enterprise Edition ); 4
Wady/Zalety Zalety: Pełna kontrola typów. Posługujemy się egzemplarzami naszej klasy. Istnieją jedynie wyliczone przez nas egzemplarza. Nadal można switchować na opierając się na zaszytych wewnątrz wartościach całkowitych. Można wygodnie wprowadzać zmiany, np. w numeracji. Wady: Trzeba się najpierw dużo narobić. Ponieważ nie powinniśmy polegać na zaszytych wewnątrz wartościach całkowitych nie można kodować informacji przy pomocy masek bitowych. Chociaż przy odrobinie wysiłku można sobie poradzić przy pomocy zbiorów. Żeby iterować po możliwych wartościach trzeba się jeszcze bardziej narobić. 5
Typy wyliczeniowe w J2SE 5.0 Od wersji 5.0 młotek jest dołączony do Javy. Jest to wersja deluxe: ma wbudowaną poziomnicę, miarkę i latarkę, co więcej łatwo daje się integrować z pozostałymi narzędziami, np. obcęgami, wiertarką, wkręcarką, a nawet ekspresem do parzenia kawy, na szczęście w 90% przypadków do wbijania gwoździ wystarczy 10% funkcjonalności młotka. Mimo to lepiej przebrnąć przez instrukcję obsługi super młotka niż za każdym razem konstruować swój dopasowany do sytuacji zwłaszcza jeśli chcemy tworzyć zgrany zespół z innymi (jest o wiele bardziej prawdopodobne, że naszymi młotkami dadzą sobie po palcach) Dostarczanie młotków przez twórców języka jest zgodne ogólnie panującymi tendencjami, patrz: kolekcje, graficzny interfejs użytkownika, parsowanie XML,..., specyfikacje składające się na J2EE. 6
Przykład Już taki prosty kod chroni nas przed większością problemów, które mogły powstać przy wyliczaniu przy pomocy liczb całkowitych: enum Przedmiot { BD, IO, NIEZBĘDNIK, XML, J2EE //tutaj można dodać ; Co dostajemy za darmo: Przedmiot rozszerza java.lang.enum (która sama z siebie nie jest typem wyl.) Przedmiot to typ wyliczeniowy, a BD, IO, NIEZBĘDNIK, XML, J2EE to jego jedyne wartości (egzemplarze) nie można stworzyć innych egzemplarzy przedmiotu niż te wyliczone (brak publicznego konstruktora) typy wyliczeniowe są zawsze final (nie można użyć tego modyfikatora, ani abstract) wartości są zawsze public static final (nie można stosować żadnych modyfikatorów, nawet dostępu) 7
Co jeszcze dostajemy za darmo wartości można porównywać za pomocą == oraz equals() (wygodna praca z kolekcjami) typy wyliczeniowe implementują java.lang.comparable (wymuszone przez java.lang.enum) tostring() zwraca napis z nazwą wartości, a valueof() wartość na podstawie napisu z nazwą (np. Przedmiot.valueOf("BD") == Przedmiot.BD) ordinal() zwraca numer pozycji, w oparciu o kolejność deklaracji i poczynając od 0, ale jej używanie nie jest zalecane values() zwraca tablicę z wszystkimi wartościami 8
Przykład użycia System.out.print("W ofercie jest "+Przedmiot.values().length+" przedmiotów: "); for (Przedmiot p : Przedmiot.values()) System.out.print(p + ", "); W ofercie jest 5 przedmiotów: BD, IO, NIEZBĘDNIK, XML, J2EE, 9
switch static String pełnanazwa(przedmiot p ) { switch (p) { case BD: return "Bazy danych"; case IO: return "Inżynieria oprogramowania"; case NIEZBĘDNIK: return "Java niezbędnik programisty"; case XML: return "XML i metody zarządzania treścią"; case J2EE: return "Java 2 Platform, Enterprise Edition"; default: String s = "przedmiot "+p.tostring()+" nie był przewidziany"; assert (true) : s; return s; Wartości po case nie poprzedzamy (nie musimy i nie możemy) nazwą typu. Jak się da to wynik kompilacji takiego kodu będzie oparty na tablicy wartości funkcji ordinal(). Często typ wyliczeniowy i switch mogą być w różnych jednostkach kompilacji, więc wygenerowany kod może przypominać ciąg if/else. Warto pamiętać o rozsądnej klauzuli domyślnej, bo ktoś może zmienić definicję typu wyliczeniowego już po napisaniu switcha. 10
Wyliczenia wewnętrzne public class Test { enum Przedmiot {//można użyć modyfikatora static, ale jest nadmiarowy //tak jak abstract przy interfejsach BD, IO, NIEZBĘDNIK, XML, J2EE 11
Mapy //bardzo wydajna implementacja oparta na tablicy EnumMap<Przedmiot, String> pełnenazwy = new EnumMap<Przedmiot, String>(Przedmiot.class); pełnenazwy.put(przedmiot.bd, "Bazy danych"); pełnenazwy.put(przedmiot.io, "Inżynieria oprogramowania"); pełnenazwy.put(przedmiot.niezbędnik, "Java niezbędnik programisty"); pełnenazwy.put(przedmiot.xml, "XML i metody zarządzania treścią"); pełnenazwy.put(przedmiot.j2ee, "Java 2 Platform, Enterprise Edition"); System.out.print("Oto pełne nazwy wszystkich "+Przedmiot.values().length+" przedmiotów z oferty: "); for (Przedmiot p : Przedmiot.values()) System.out.println(p.toString()+" - "+pełnenazwy.get(p)); 12
Zbiory java.util.enumset (poniższe metody są statyczne i zwracają EnumSet) allof(class typ) zbiór z wszystkimi wartościami typu complementof(collection c) dopełnienie noneof(class typ) pusty zbiór o określonym typie elementów of(e e[, E e2, E e3, E e4, E e5]) zbiór z podanymi elementami of(e... e) wersja o zmiennej liczbie argumentów range(e from, E to) el. pewnego zakresu (na podstawie deklaracji typu) clone() kopia aktualnego (nie statyczna) zbioru oczywiście wszystkie normalne operacje na zbiorach są również dostępne 13
Integracja z ekspresem do kawy enum Przedmiot3 { BD("Bazy danych"), IO("Inżynieria oprogramowania"), NIEZBĘDNIK("Java niezbędnik programisty"), XML("XML i metody zarządzania treścią"), J2EE("Java 2 Platform, Enterprise Edition");//ten średnik jest potrzebny //konstruktor i tak jest prywatny, ale użycie modyfikatora jest dozwolone Przedmiot3(String pn) { this.pełnanazwa = pn; private String pełnanazwa; public String dajnazwę() { return pełnanazwa; 14
I inne kwiatki Typy wyliczeniowe mogą implementować interfejsy (jedną klasę już rozszerzają, mianowicie java.lang.enum). Dla zabezpieczenia się przed możliwością stworzenia jakiś dodatkowych egzemplarzy typy wyliczeniowe są oznaczone jako final. Można jednak trochę oszukać (chociaż więcej egzemplarzy się nadal nie da zrobić): num Przedmiot3 { BD("Bazy danych") {//zmusiliśmy typ wyliczeniowy do nie bycia final public String dajnazwę() { return "Teoria baz danych";, IO("Inżynieria oprogramowania"),... Mimo, że klasa Enum jest rozszerzana przez wszystkie typy wyliczeniowe samemu jej nie można rozszerzać: //class MójLepszyTypWyliczeniowy extends Enum { 15
Auto. opakowywanie/odpakowywanie Integer i = null; int j = i; //zgłosi NullPointerException Inkrementacja i dekrementacja działa, ale tworzony jest nowy obiekt Integer i = 1; Integer j = i; i++; System.out.println(i+" - "+(i == j)); //2 - false Należy uważać na porównywanie dwóch obiektów Integer i = 256; Integer j = 256; System.out.println(i == j); //false Ale uwaga nie zawsze tak będzie! JVM może zoptymalizować ten kod i stworzyć tylko jeden egzemplarz (bo przecież to i jego stan się nie zmienia). Dla wartość od -127 do 127 istnieje tylko po jednym egzemplarzu klasy opakowującej: Integer i = 13; Integer j = 13; System.out.println(i == j); //true Auto. opakowywanie/odpakowywanie działa również dla typu logicznego. 16
Operator ternarny wartośćlogiczna? wynikdlaprawdy : wynikdlafałszu Dotychczas wynikdlaprawdy i wynikdlafałszu musiały być tego samego typu lub jedna musiała się dawać przypisać na drugą. Od J2SE 5.0 wynik można zrzutować na wszystkie wspólne nadtypy obu wartości (zawsze wspólny będzie Object): true? new Float(1) : new Double(2) //w J2SE można zrzutować na Number Co więcej działa też auto. opakowywanie/odpakowywanie. np. jeżeli jedna wartość będzie typu Integer, a druga Float, to zostaną odpakowane i wynikiem będzie wartość typu float. 17
Uwaga Pomimo wygody auto. opakowywania/odpakowywani nie należy tracić czujności. Nie wolno zapominać, że wartość obiektów opakowujących się nie zmienia. To jest nieskończona pętla: Integer i = 0; int max = 10; while (i < max) { if (i < max-1) i++; System.out.println(i); A to nie: Integer i = 0; int max = 10; while (i < max) { if (i < max+1) i++; System.out.println(i); 18
Zagadka class ACoZPrzeciążaniem { public void coś(double x) { System.out.println("double"); public void coś(integer x) { System.out.println("Integer"); public static void test() { ACoZPrzeciążaniem a = new ACoZPrzeciążaniem(); int x = 1; a.coś(x); 19
Reguły dopasowywania metody Ze względu na zachowanie kompatybilności najpierw jest dobierana metoda zgodnie ze starymi regułami (J2SE 1.4). Jak się nie uda to próbowane jest auto. opakowywanie/odpakowywanie. Jak się nie uda to brane są pod uwagę metody ze zmienną ilością argumentów (dotychczas były ignorowane). 20
Metody ze zmienną liczbą argumentów Opracował Kompilator może za nas skonstruować tablicę z argumentów metody. class VarargsTest { public static void cześć(string...imiona) { System.out.print("Witaj "); for (String imie : imiona) System.out.print(imie+", "); VarargsTest.cześć("Alo", "Olu", "Ulu"); VarargsTest.cześć(new String[] {"Alo", "Olu", "Ulu"); VarargsTest.cześć(); //VarargsTest.cześć(null);//oczywiście NullPointerException W danej metodzie tą sztuczkę można użyć tylko raz, na końcu listy argumentów. Jeżeli pusta lista argumentów jest niedozwolona należy zgłaszać IllegalArgumentException 21
Metoda z dowolnymi parametrami public static void wypiszwszystko(object...dowypisania) { for (Object o : dowypisania) System.out.print(o+", "); wypiszwszystko(1, true, "ala", 1.); //jeżeli chcemy mieć taką wygodę trzeba użyć... public static void tablicojad(object[] dowypisania) { for (Object o : dowypisania) System.out.print(o+", "); //tablicojad(1, true, "ala", 1.); 22
Ale czy na pewno z dowolnymi? public static void wypiszwszystko(object...dowypisania) { for (Object o : dowypisania) System.out.print(o+", "); 23
Ale czy na pewno z dowolnymi? public static void wypiszwszystko(object...dowypisania) { for (Object o : dowypisania) System.out.print(o+", "); Tablica będąca jedynym argumentem zawsze będzie rozpakowana! Dlatego jak chcemy takową przekazać można ją opakować w tablicę:) albo zrzutować na Object 24