Twitter: @grzegg
Kategoria: java, Tagi: - - - .

Java [31] – Wrappery i typy generyczne

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 intInteger oraz charCharacter) 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.

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:

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:

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