3. Opakowanie typów pierwotnych
Widzieliśmy już w praktyce, że znakowa reprezentacja liczb (napisy reprezentujące
liczby) - to zupelnie co innego niż ich wewnętrzna, binarna postać.
Właśnie dlatego uporczywie musieliśmy używać metody parseInt z klasy Integer
do przekształcania znakowej (napisowej) postaci liczb całkowitych na ich
binarną wewnętrzną postać.
Powstaje pytanie: co to za klasa Integer? I co robić z innymi typami liczb: np. rzeczywistymi ?
Otóż bardzo często występuje potrzeba traktowania liczb jako obiektów. Tymczasem
liczby są reprezentowane przez typy pierwotne (i nie są obiektami). Dlatego
w standardowym pakiecie java.lang umieszczono specjalne klasy, opakowujące
liczby (w ogóle wszystkie typy pierwotne) i czyniące z nich obiekty.
Należą do nich następujące klasy:
- Integer
- Short
- Byte
- Long
- Float
- Double
Obiekty tych klas reprezentują (w sposób obiektowy) liczby odpowiednich typów.
Np. obiektowy odpowiednik liczby 5 typu int możemy uzyskać tworząc obiekt klasy Integer:
Integer a = new Integer(5);
Mówimy o opakowaniu liczby 5, bowiem liczba ta jest umieszczana "w środku" obiektu klasy Integer, jako jego element.
Klasy opakowujące nie dostarczają żadnych metod do wykonywania operacji na
liczbach. Z obiektu takiej klasy musimy liczbę "wyciągnąć" za pomocą odpowiednich
metod, np.
int i = a.intValue(); // zwraca wartość typu int, "zawartą" w obiekcie a
Podobnie:
Double dd = new Double(7.1);
double d = dd.doubleValue(); // d == 7.1
Po co to wszystko? Po co takie opakowanie może być potrzebne? Pamiętamy z wykladów o klasach i tablicach, że:
- każda klasa w Javie dziedziczy pośrednio (lub bezpośrednio) klasę Object
- zatem referencji do obiektu klasy Object możemy przypisac referencję do obiektu dowolnej innej klasy
- tablice mogą zawierać referencje do dowolnych obiektów
Wyobraźmy sobie teraz, że mamy jakąś tablicę, w kolejnych elementach której
chcemy zapisywać dowolne informacje. Czy to możliwe? Ależ tak - właśnie dzięki
wymienionym wyżej trzem właściwościom - pod warunkiem jednak, że informacja
ta będzie przedstawiana w sposób obiektowy (jako obiekty).
Możemy zatem stworzyć tablicę, za pomocą której jednocześnie będziemy rejestrować
np. i napisy (łańcuchy znakowe) i liczby. Te ostatnie jednak musimy "opakować"
odpowiednimi klasami: Integer, Double itd.
Na przykład:
Object[] tab = new Object[4];
tab[0] = "Kowalski";
tab[1] = new Integer(28);
tab[2] = "Kowalska";
tab[3] = new Integer(30);
W łatwy sposób możemy pokazać wszystkie elementy tablicy.
Metoda toString() zastosowana wobec obiektu dowolnej klasy zwraca
napis, będący znakową reprezentacją tego obiektu. Definicje tej metody w
klasach okreslają jak obiekty tych klas są przedstawiane w postaci napisów
W przypadku powyższego przykładu wypisanie wszystkich elementów tablicy wygląda tak:
for (int i=0; i<tab.length; i++)
System.out.println(tab[i].toString());
co da napisy:
Kowalski
28
Kowalska
30
Jednak, gdybyśmy wobec obiektów zarejestrowanych w tablicy chcieli użyć metod
ich klas (albo - wobec obiektów klasy String - operatora konkatenacji łańcuchów
znakowych), to natkniemy się na dwa problemy.
Po pierwsze, po to by zastosować metody jakiejś klasy musimy rozpoznać właściwe obiekty, stwierdzić do jakiej klasy należą.
Operujemy przecież elementami typu Object. W naszym przykładzie każdy z nich
może wskazywać na obiekt klasy Integer albo obiekt klasy String.
Stwierdzeniu do jakiej klasy należy obiekt służy operator instanceof.
Wyrażenie:
nazwa_zmiennej instanceof nazwa_klasy
ma wartość true, jeśli zmienna nazwa_zmiennej jest referencją do obiektu klasy nazwa_klasy
Np. jeśli:
Object[] tab = { "Kowalski", new Integer(28) };
to
tab[0] instanceof String - ma wartość true
tab[0] instanceof Integer - ma wartość false
tab[1] instanceof String - ma wartość false
tab[1] instanceof Integer - ma wartość true
Drugi problem polega na tym, że dla kompilatora wszystkie elementy tablicy
tab w omawianym przykładzie są typu Object. Jak pamiętamy, kompilator sprawdza
zgodność typów. Zatem w kontekście:
Object[] tab = { "Kowalski", new Integer(28) };
takie zapisy:
String s = "";
if (tab[0] instanceof String) s += tab[0];
int i;
if (tab[1] instanceof Integer) i += tab[1].intValue();
spowodują błędy w kompilacji, bowiem:
- tab[1] jest typu Object, a wobec Object nie możemy stosować operatora +
- tab[2] jest typu Object, a w klasie Object nie ma metody intValue
Ale przecież wewnątrz obiektów oznaczanych przez tab[1] i tab[2] "siedzą"
naprawdę (odpowiednio) - obiekt klasy String i obiekt klasy Integer. Mówi
nam o tym wynik zastosowania operatora instanceof, który w obu przypadkach
jest true. Możemy o tym powiedzieć kompilatorowi za pomocą zawężającej konwersji
referencyjnej (lub - inaczej mówiąc - obiektowej).
Jeśli klasa B dziedziczy klasę A, to po:
B b = new B();
A a = b;
możemy poinformować kompilator, że a wskazuje na obiekt klasy B za pomocą jawnej konwersji zawężającej:
B c = (B) a;
po czym wobec zmiennej c będziemy mogli używać metod klasy B
W naszym przypadku powinniśmy więc zapisać:
Object[] tab = { "Kowalski", new Integer(28) };
String s = "Jan";
if (tab[0] instanceof String) s += (String) tab[0];
int i = 200;
if (tab[1] instanceof Integer) {
Integer liczba = (Integer) tab[1];
i += liczba.intValue();
}
i w tym momencie zarówno kompilacja jak i wykonaie programu będą pomyślne, dając:
s == "JanKowalski"
i == 228;
Zadanie: w dwóch tablicach zapisane są imiona graczy (w jakąś grę) oraz punkty przez nich uzyskane:
String[] name = { "Ala", "Ela", "Jan"};
int[] points = { 10, 12, 11 };
Stworzyć jedną tablicę tab, w której będzie zapisana sekwencja wyników: imię, punkty, imię, punkty...
po czym, korzystając tylko z tablicy tab, w kolejnych wierszach
wypisać zestawy imię - punkty, podać (w jednym wierszu) imiona wszystkich
graczy, a następnie sumę punktów, którą uzyskali.
Przykładowy wydruk:
W grze punkty uzyskali:
Ala 10
Ela 12
Jan 11
Grali: Ala Ela Jan
Zdobyli łącznie: 33 pkt.
Przed lekturą dalszego tekstu proszę to zadanie rozwiązać samodzielnie
Możliwe rozwiązanie:
public class Wrap {
public static void main(String[] args) {
String[] name = { "Ala", "Ela", "Jan"};
int[] points = { 10, 12, 11 };
Object[] tab = new Object[name.length*2];
int k = 0;
for (int i=0; i<name.length; i++) {
tab[k++] = name[i];
tab[k++] = new Integer(points[i]);
}
System.out.println("W grze punkty uzyskali: ");
for (int i=0; i <tab.length; i+=2)
System.out.println(tab[i].toString() + " " + tab[i+1].toString());
String players = "";
int pointSum = 0;
for (int i=0; i < tab.length; i++) {
if (tab[i] instanceof String)
players += " " + (String) tab[i];
else if ( tab[i] instanceof Integer)
pointSum += ((Integer) tab[i]).intValue();
}
System.out.println("Grali: " + players);
System.out.println("Zdobyli łącznie: " + pointSum + " pkt.");
}
}
Niejako przy okazji, w klasach opakowujących typy pierwotne zdefiniowano statyczne metody:
public static ttt parseTtt(String s)
zwracające wartości typu ttt reprezentowane przez napis s
gdzie: ttt nazwa typu pierwotnego (np. int, double), Ttt - ta sama nazwa z kapitalizowaną pierwszą literą (np. Int, Double)
A więc, po to by przekstałcić napis s reprezentujący liczbę rzeczywistą do typu double należy napisać:
double d = Double.parseDouble(s);
np.
String s = "12.4";
double d = Double.parseDouble(s); // d == 12.4
Użytecznych dodatkowych metod dostarcza klasa Character (opakowujaca typ char).
Należą do nich metody stwierdzania rodzaju znaku np.:
isDigit() // czy znak jest znakiem cyfry isLetter() // czy znak jest znakiem litery isLetterOrDigit() // litera klub cyfra isWhiteSpace() // czy to "biały" znak (spacja, tabulacja etc.) isUpperCase(); // czy to duża litera isLowerCase(); // czy to mała litera
Metody te zwracają wartości true lub false.
Proszę zapoznać się z dokumentacją klasy Character.
W klasach opakowujących typy numeryczne zdefiniowano także wiele użytecznych
statycznych stałych. Należą do nich stałe zawierające maksymalne i minimalne
wartości danego typu.
Mają one nazwy MAX_VALUE i MIN_VALUE.
Dzięki temu nie musimy pamiętać zakresów wartości danego typu.
Możemy np. zsumować wszystkie dodatnie liczby typu short:
public class MaxVal {
public static void main(String[] args) {
long sum = 0;
for (int i=1; i <= Short.MAX_VALUE; i++) sum +=i;
System.out.println(sum);
}
}
|