W dzisiejszej lekcji spotkamy się z wrapperami oraz typami generycznymi. Przydadzą nam się między innymi kiedy poznamy kolekcje.
Wrappery (klasy opakowujące)
Dotychczas używając liczb, znaków czy wartości booleanowskich używaliśmy typów podstawowych, na przykład int
, double
, char
czy boolean
. W przeciwieństwie do typów odnośnikowych nie były one obiektami co zwykle nam nie przeszkadzało. Okazuje się jednak, że w wielu przypadkach jak na przykład przy używaniu kolekcji, o których będzie mowa niebawem, konieczne będzie używanie obiektów zamiast typów podstawowych. W takich przypadkach z pomocą przychodzą klasy opakowujące, zwane także wrapperami.
Czym są wrappery?
Klasy opakowujące, jak sama nazwa wskazuje, służą do „opakowania” typów prymitywnych, w ten sposób aby można było ich używać jako obiektów, wraz z wszelkimi dobrodziejstwami wynikającymi z tego faktu. Dzięki nim można na przykład przekazywać do metod odnośniki do obiektów, korzystać ze wspomnianych kolekcji, czy metod udostępnianych przez klasę Object
oraz klasy wrapperów.
W Javie istnieją następujące klasy opakowujące:
Byte
Character
Double
Float
Integer
Long
Short
Jak łatwo zauważyć odpowiadają one typom podstawowym: byte
, char
, double
, float
, int
, long
, short
, nazwy są takie same (poza int
– Integer
oraz char
–Character
) ale jak przystało na klasy piszemy ich nazwy wielką literą.
Typy liczbowe wrapperów dziedziczą po klasie Number
, która pozwala na uzyskanie z obiektów wartości prymitywnych. Ich nazwy mówią same za siebie: byteValue()
, doubleValue()
, floatValue()
, intValue()
, longValue()
i shortValue()
. Na przykład metoda intValue()
zwraca wartość typu int
, dzięki doubleValue()
można pobrać liczbę double
itd. Chociaż wrappery, jak widać nie służą do obsługi wyłącznie typów liczbowych, na nich się skupimy w tej lekcji.
Do utworzenia obiektu odpowiedniego typu można użyć konstruktora, przyjmującego jako argument wartość typu prymitywnego, na przykład:
Integer calkowitaObiektowa = new Integer(2014);
Można także użyć metody statycznej valueOf()
dostępnej w każdej z wymienionych klas:
Integer calkowitaObiektowa = Integer.valueOf(2014);
Można w końcu, wykorzystując technikę „autoboksingu” (ang. autoboxing) zrobić to krócej:
Integer calkowitaObiektowa = 2014;
W drugą stronę działa to analogicznie (tzw. unboxing, ang unboxing):
int calkowitaPodstawowa = calkowitaObiektowa;
W obu przypadkach „automagicznie” dokona się konwersja typu prymitywnego na obiektowy i odwrotnie.
Autoboxing i unboxing
Autoboxing i unboxing znacznie ułatwia pracę z wrapperami, zwłaszcza w przypadku kolekcji. Można je także wykorzystać, kiedy przekazujemy argumenty do metod, jak widać w poniższym przykładzie. Zwróć uwagę na rodzaj przekazywanych argumentów w deklaracjach metod i wtedy gdy są rzeczywiście używane a także na rodzaj zwracanych typów. Zauważ też, że bezpośrednie drukowanie wartości przechowywanych przez wrappery umożliwia to, że posiadają one nadpisaną metodę toString()
zwracającą przechowywaną wartość w formie łańcucha znaków.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package wrappery; public class Wrappery { public static int dodaj(int l1, int l2) { return l1 + l2; } public static Integer odejmij(int l1, int l2) { return l1 - l2; } public static double pomnoz (Double l1, Double l2 ) { return l1*l2; } public static Double podziel (Double l1, Double l2 ) { return l1/l2; } public static void main(String[] args) { int int1Prym = 1; int int2Prym = 2; Integer int1Wrap = 3; Integer int2Wrap = 4; double dou1Prym = 1.0; double dou2Prym = 2.0; Double dou3Wrap = new Double(3.0); Double dou4Wrap = 4.0; System.out.println("Dodawanie: " + dodaj(int1Prym, int2Prym)); System.out.println("Dodawanie: " + dodaj(int1Wrap, int2Prym)); System.out.println("Odejmowanie: " + odejmij(int1Prym, int2Prym)); System.out.println("Odejmowanie: " + odejmij(int1Wrap, int2Wrap)); System.out.println("Pomnoz: " + pomnoz(dou1Prym, dou3Wrap)); System.out.println("Podziel: " + podziel(dou1Prym, dou3Wrap)); } } |
Powyższy przykład pokazuje, że w zasadzie zamiast prymitywnych typów liczbowych, można wykorzystywać typy opakowujące. Nie należy jednak z tym przesadzać, ponieważ używanie wrapperów do obliczeń jest wolniejsze, wymaga bowiem konwersji (nawet gdy jest to niejawne dzięki auto- i un-boksingowi) do typów podstawowych. Korzystaj więc z klas opakowujących, kiedy to potrzebne.
Typy generyczne (uogólnione)
Zanim przejdziemy do kolekcji warto wspomnieć o typach generycznych (ang. generics) inaczej uogólnionych. W skrócie, typy generyczne pozwalają uogólnić typy używane w klasach i metodach. Teraz zaledwie dotkniemy tematy, więcej przykładów pojawi się w dalszej części kursu. Szersze informacje można znaleźć np. w dokumentacji.
Metody generyczne
Jeśli chcielibyśmy na przykład stworzyć metodę wypisujące na ekranie elementy tablicy, moglibyśmy zastosować przeciążanie, czyli stworzyć serię metod, które przyjmowałyby jako argumenty tablice różnych typów argumentów (String[]
, Integer[]
etc.). W tego typu sytuacjach mogą pomóc właśnie typy generyczne. Pokazuje to poniższy przykład:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class TypyGeneryczne { public static void main(String[] args) { String[] imiona = {"Ala", "Ola", "Ela", "Ula"}; Integer[] liczby = {2, 31, 52, 6}; Boolean[] odpowiedzi = {false, true, true, false, true}; drukuj(imiona); drukuj(liczby); drukuj(odpowiedzi); } public static <T> void drukuj(T[] tablica) { System.out.println("\nKlasa: " + tablica.getClass()); for (T element : tablica) { System.out.println("Element: " + element); } } } |
Po uruchomieniu otrzymamy:
Klasa: class [Ljava.lang.String;
Element: Ala
Element: Ola
Element: Ela
Element: Ula
Klasa: class [Ljava.lang.Integer;
Element: 2
Element: 31
Element: 52
Element: 6
Klasa: class [Ljava.lang.Boolean;
Element: false
Element: true
Element: true
Element: false
Element: true
Deklaracja metody powyżej, przed określeniem zwracanej wartości (void
) zawiera sekcję opisującą typ parametru: <T>
. Zauważ, że stosuje się tu parę nawiasów ostrych. Litera T
wg. konwencji oznacza typ. Jest to nazwa pod którą będzie występować typ uogólniony. Kiedy powyżej używamy pętli foreach
, typ elementu pobieranego z tablicy jest właśnie określony literą T
. Kompilator dopiero „dookreśla” jakiego typu użyć w konkretnych przypadkach. Jeśli jest używanych kilka typów uogólnionych, rozdzielamy je przecinkami, np: <K, V>
. Dalej, kiedy deklarujemy rodzaj przyjmowanych przez metodę argumentów, możemy użyć typu generycznego. Tutaj, zadeklarowaliśmy, że metoda będzie przyjmowała tablicę „jakichś” elementów. Nie oznacza to, że panuje tu pełna dowolność. Zwróć uwagę, że w powyższych przypadkach użyłem wyłącznie tablic z elementami obiektowymi. Typy prymitywne w tej sytuacji nie zadziałają.
Skoro wspomniałem o konwencji nazywania typów parametrów (generycznych) to warto je wymienić:
- E – Element (ang. Element)
- K – Klucz (Key)
- N – Liczba (Number)
- T – Typ (Type)
- V – Wartość (Value)
- S,U,V etc. – kolejne typy
Co należy rozumieć pod terminami klucz i wartość, okaże się kiedy będziemy omawiać kolekcje.
Klasy generyczne
Można także tworzyć klasy z użyciem typów generycznych. Utwórz nowy projekt a w nim dwie klasy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package klasageneryczna; public class KlasaGen<T, S> { T element1, element2; S element3; public KlasaGen(T element1, T element2, S element3) { this.element1 = element1; this.element2 = element2; this.element3 = element3; } public void drukuj() { System.out.println("Element1: " + element1 + " klasa: " + element1.getClass()); System.out.println("Element2: " + element2 + " klasa: " + element2.getClass()); System.out.println("Element3: " + element3 + " klasa: " + element3.getClass()); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package klasageneryczna; public class KlasyGeneryczne { public static void main(String[] args) { String e1 = "Kot"; String e2 = "Pies"; Integer e3 = 123; KlasaGen kg1 = new KlasaGen(e1, e2, e3); kg1.drukuj(); System.out.println(" === "); Double e4 = 23.234; Double e5 = 13.14; Boolean e6 = true; KlasaGen kg2 = new KlasaGen(e4, e5, e6); kg2.drukuj(); } } |
Wynik będzie taki:
Element1: Kot klasa: class java.lang.String
Element2: Pies klasa: class java.lang.String
Element3: 123 klasa: class java.lang.Integer
===
Element1: 23.234 klasa: class java.lang.Double
Element2: 13.14 klasa: class java.lang.Double
Element3: true klasa: class java.lang.Boolean
Jak widać stworzone zostały dwa obiekty typu KlasaGen
przy użyciu różnych typów argumentów, chociaż w klasie jest tylko jeden konstruktor.
Leave a Reply
You must be logged in to post a comment.