Klasy osłonowe w Javie
Klasy osłonowe (ang. wrapper class) pojawiły się w szkoleniu już wcześniej. W ostatnim wpisie dotyczącym klasy Object
okazało się, że gdy utworzymy uniwersalny kontener w postaci tablicy typu Object
mimo wpisania wartości 1 (typ prosty int) program pokazał, że element jest obiektem klasy Integer
.
1 2 3 |
Object[] tab = {"1", 1}; System.out.println(Arrays.toString(tab)); System.out.println("Parametr 1: " + tab[0].getClass().getName() + ", Parametr 2: " + tab[1].getClass().getName()); |
na wyjściu pokazuje się następujący komunikat.
1 2 |
[1, 1] Parametr 1: java.lang.String, Parametr 2: java.lang.Integer |
Temat klas opakowujących sięga jednak dalej – okazuje się, że przemknął dość dyskretnie w przypadku kolekcji, a dokładniej klas implementujących interfejsy List, Set, Queue i Map
. W celu usystematyzowania wiedzy przypominamy, że wszystkie klasy implementujące powyższe interfejsy są generyczne, co w dużym uproszczeniu oznacza, że kolekcje mogą przechowywać elementy określonego typu.
1 2 3 |
ArrayList<Integer> lista = new ArrayList<>(); ArrayList<Double> lista = new ArrayList<>(); ArrayList<Boolean> lista = new ArrayList<>(); |
W nawiasie ostrym znajduje się nazwa klasy. Jedną z nich jest Integer
, która jest klasą osłonową. Nie jest to typ prosty int i nie należy ich mylić.
Klasa osłonowa
Zadaniem klasy osłonowej jest zamknięcie wartości typu prostego w obiekt. Każdy z nich posiada swój obiektowy odpowiednik. Poniżej znajduje się ich lista podzielona według typów.
Liczby całkowite
Byte – byte
Short – short
Integer – int
Long – long
Liczby zmiennoprzecinkowe
Float – float
Double – double
Wartości logiczne
Boolean – boolean
Zmienne znakowe
Character – char
Przykładowa implementacja klasy opakowującej może wyglądać następująco.
1 2 3 4 5 6 7 8 9 10 |
class Integer { int value; //pola statyczne Integer(int val){ this.value = val } //metody statyczne konwersji } |
Jak widać w powyższym listingu oprócz zmiennej, która przechowuje wartość obiektu znajduje się kilka pól oraz metod statycznych. Przykładami pól są przydatne informacje takie jak największa lub najmniejsza wartość danego typu prostego.
Zamiana typów (parsowanie)
W Javie w porównaniu do C# nie istnieje klasa taka jak Convert
, która zawiera szereg metod do zamiany typów. Zamiast tego rozwiązania każda klasa osłonowa zawiera metody statyczne, które pozwalają zamieniać String
na inne typy.
Zamiana String
na int.
1 2 3 4 5 |
//konwersja String "1" na int int intFromString = Integer.parseInt("1"); //konwersja int na String String s = Integer.toString(1); |
Warto zwrócić uwagę, że jeśli użyjemy operatora + (w przypadku łańcucha oznacza on konkatenację), to wartość int zostanie automatycznie skonwertowana do string.
1 |
String s = "automatyczna konwersja " + 1; |
Podobnie można konwertować inne wartości całkowitoliczbowe np. String
na long, String
na short oraz String
na byte.
Zamiana String
na float, double
1 2 3 4 5 |
//konwersja String na float float floatFromString = Float.parseFloat("1.0"); //konwersja float na String String s = Float.toString(1.0); |
1 2 3 4 5 |
//konwersja String na double double doubleFromString = Double.parseDouble("1.0"); //konwersja double na String String s = Double.toString(1.0); |
Zamiana String
na bool
1 2 3 4 5 |
//konwersja String na bool bool boolFromString = Boolean.parseBoolean("True"); //konwersja double na String String s = Double.toString(true); |
Metoda parseBoolean()
konwertuje wartość „True” lub „true” (ignorując wielkość liter) na wartość logiczną true. W każdym innym przypadku zostanie przypisana wartość false.
Zamiana String
na char
Pokażemy przykład jak zamienić obiekt String
na tablicę char oraz odwrotny przypadek.
1 2 3 4 5 6 7 8 9 |
//konwersja String na tablicę char char[] tabChar = "String to char".toCharArray(); //konwersja tablicy char String String s = String.valueOf(tabChar); //zamiana char na String char a = 'a'; String s = Character.toString(a); |
Typ prosty a klasa osłonowa
W zmiennej typu int (oraz każdej innej typu prostego) można przechowywać wartość (oraz modyfikować), natomiast z uwagi na to, że nie są klasami, to nie posiadają żadnych zdefiniowanych metod. Natomiast sama ich deklaracja oraz działania są proste.
1 2 |
int paczki = 10; paczki -= 1; |
Jeżeli zmienna pokazuje liczbę pączków, które pozostały na sprzedaż, to można ją w prosty sposób modyfikować przez działania arytmetyczne.
Takie swobody jednak nie ma w przypadku obiektów Integer
(oraz innych klas osłonowych). Dotychczas obiekty klasy Integer
(podobnie jak innych klas osłonowych) można było tworzyć przy pomocy słowa kluczowego new.
1 2 |
//obecnie niezalecane użycie konstruktora Integer val = new Integer(1); |
Ze względów wydajnościowych optymalnym rozwiązaniem jest użycie statycznej metody valueOf()
dostępnej w klasie Integer
.
1 |
Integer val = Integer.valueOf(1); |
Odpakowanie wartości z obiektu odbywa się za pomocą metody intValue()
.
1 2 |
//Zwrócenie typu prostego int primitiveVal = val.intValue(val); |
Opakowane wartości są stałe, co oznacza, że nie można ich zmieniać w taki sposób jak typy proste. Podobnie jak w innych obiektach należy użyć znanej już metody valueOf()
.
1 2 3 |
Integer val = Integer.valueOf(1); ... val = Integer.valueOf(5); |
Analogicznie sytuacja wygląda gdy chcemy wykorzystać obiekt typu String
. Nie należy korzystać z konstruktora.
1 2 |
String number = "1" Integer val = Integer.valueOf(number); |
Automatyczne opakowywanie (ang. autoboxing)
Wróćmy teraz do klasy ArrayList
, która implementuje kolekcję przechowującą obiekty dowolnej klasy. Nie jest możliwe przechowywanie typów prostych, takich jak int. Spójrzmy na poniższy listing.
1 2 3 4 5 |
ArrayList<Integer> lista = new ArrayList<>(); lista.add(1); //1 jest typem prostym int lista.add(2); ... int a = lista.get(0); //lista(0) |
W linii 2 do listy przechowującej kolekcję typu Integer
został dodany typ prosty int? Z kolei w ostatniej linii wydaje się, że została wykonana odwrotna operacja. W rzeczywistości nie ma tu żadnej magii gdyż kompilator wykonuje konwersje w tle (automatycznie pakuje i wypakowuje wartość z obiektu osłonowego). Powyższy kod mógłby zostać również napisany w ten sposób.
1 2 3 4 5 |
ArrayList<Integer> lista = new ArrayList<>(); lista.add(Integer.valueOf(1)); lista.add(Integer.valueOf(2)); ... int a = lista.get(0).intValue(); |
Jednakże tak napisany kod wydaje się być mniej czytelny.
1 2 3 |
Integer val = Integer.valueOf(1); Integer val2 = Integer.valueOf(3); System.out.print(++val + val2); |
Podobna sytuacja ma miejsce tutaj. Wartości val oraz val2 są najpierw wypakowywane, a następnie val jest powiększana operatorem preinkrementacji o 1. Potem obliczana jest suma dwóch wartości, a na sam koniec następuje niejawna konwersja typu int do String
.
W następnej części przyjrzymy się klasom StringBuilder i StringBuffer.