Bez zrozumienia składni Java nie można zostać poważnym programistą, dlatego dzisiaj kontynuujemy naukę składni. W jednym z poprzednich artykułów mówiliśmy o zmiennych prymitywnych, ale ponieważ istnieją dwa typy zmiennych, dzisiaj porozmawiamy o drugim typie - typach referencyjnych w Javie. Więc co to jest? Dlaczego w Javie potrzebne są referencyjne typy danych? Wyobraźmy sobie, że mamy obiekt telewizyjny z pewnymi cechami, takimi jak numer kanału, głośność dźwięku i flaga:
public class TV {
int numberOfChannel;
int soundVolume;
boolean isOn;
}
W jaki sposób prosty typ, taki jak , może int
przechowywać te dane? Pamiętajmy: jedna zmienna int
to 4 bajty. Ale wewnątrz znajdują się dwie zmienne (4 bajty + 4 bajty) tego samego typu, a także boolean
(+1 bajt)... Razem - 4 do 9, ale z reguły w obiekcie przechowywanych jest znacznie więcej informacji. Co robić? Nie można umieścić obiektu w zmiennej. W tym momencie naszej historii pojawiają się zmienne referencyjne. Zmienne referencyjne przechowują adres komórki pamięci, w której znajduje się określony obiekt. Oznacza to, że jest to „wizytówka” z adresem, za pomocą którego możemy znaleźć nasz obiekt w pamięci współdzielonej i wykonać na nim pewne manipulacje. Odwołanie do dowolnego obiektu w Javie jest zmienną referencyjną. Tak by to wyglądało w przypadku naszego obiektu telewizyjnego:
TV telly = new TV();
Ustawiamy zmienną typu TV z nazwą telly
na odnośnik do utworzonego obiektu typu TV. Oznacza to, że JVM przydziela pamięć na stercie dla obiektu TV, tworzy go, a adres jego lokalizacji w pamięci umieszcza w zmiennej telly
, która jest przechowywana na stosie. Więcej o pamięci, a mianowicie stosie i wielu innych przydatnych informacjach, możesz przeczytać w tym wykładzie . Zauważyłeś zmienną typu TV i obiekt typu TV? Nie bez powodu: obiekty określonego typu muszą mieć odpowiadające sobie zmienne tego samego typu (nie licząc dziedziczenia i implementacji interfejsu, ale teraz nie bierzemy tego pod uwagę). Przecież nie będziemy nalewać zupy do szklanek, prawda? Okazuje się, że naszym obiektem jest telewizor, a zmienną odniesienia dla niego jest jak panel sterowania. Za pomocą tego pilota możemy wchodzić w interakcję z naszym obiektem i jego danymi. Przykładowo ustaw charakterystykę naszego telewizora:
telly.isOn = true;
telly.numberOfChannel = 53;
telly.soundVolume = 20;
Tutaj użyliśmy operatora kropki .
, aby uzyskać dostęp i rozpocząć korzystanie z wewnętrznych elementów obiektu, do którego odnosi się zmienna. Przykładowo w pierwszej linijce powiedzieliśmy zmiennej telly
: „Podaj nam zmienną wewnętrzną isOn
obiektu, do którego się odwołujesz i ustaw ją na true” (włącz dla nas telewizor).
Redefinicja zmiennych referencyjnych
Załóżmy, że mamy dwie zmienne typu referencyjnego i obiekty, do których się one odnoszą:TV firstTV = new TV();
TV secondTV = new TV();
Jeśli napiszemy:
firstTV = secondTV;
będzie to oznaczać, że pierwszej zmiennej przypisaliśmy jako wartość kopię adresu (wartość bitów adresu) do drugiego obiektu i teraz obie zmienne odnoszą się do drugiego obiektu (innymi słowy, dwa piloty do tego samego TELEWIZJA). Jednocześnie pierwszy obiekt pozostawiono bez zmiennej, która się do niego odnosi. W efekcie mamy obiekt do którego nie można się dostać, bo zmienna była dla niego takim wątkiem warunkowym, bez którego zamienia się w śmiecie, po prostu leży w pamięci i zajmuje miejsce. Obiekt ten zostanie następnie usunięty z pamięci przez moduł zbierający elementy bezużyteczne . Możesz przerwać wątek łączący z obiektem bez innego łącza:
secondTV = null;
W efekcie do obiektu będzie tylko jedno łącze - firstTV
, i secondTV
nie będzie ono już wskazywało na nikogo (co nie przeszkadza nam w przypisywaniu mu w przyszłości linku do jakiegoś obiektu np. TV).
Klasa string
Osobno chciałbym wspomnieć o klasie String . Jest to klasa bazowa przeznaczona do przechowywania i pracy z danymi przechowywanymi w postaci ciągu znaków. Przykład:String text = new String("This TV is very loud");
Tutaj przekazaliśmy ciąg znaków, który ma być przechowywany w konstruktorze obiektu. Ale nikt tego nie robi. W końcu można tworzyć ciągi znaków:
String text = "This TV is very loud";
Dużo wygodniej, prawda? Pod względem popularności użycia String
nie ustępuje typom pierwotnym, jednak nadal jest klasą, a zmienna, która się do niej odwołuje, nie jest typem pierwotnym, a typem referencyjnym. Mamy String
tę wspaniałą zdolność łączenia ciągów znaków:
String text = "This TV" + " is very loud";
W rezultacie ponownie otrzymamy tekst: This TV is very loud
, ponieważ obie linie zostaną połączone w jedną całość, a zmienna będzie odnosić się do tego pełnego tekstu. Ważnym niuansem jest to, że String
jest to klasa niezmienna. Co to znaczy? Weźmy ten przykład:
String text = "This TV";
text = text + " is very loud";
Wydaje się, że wszystko jest proste: deklarujemy zmienną, nadajemy jej wartość. W następnej linijce to zmieniamy. Ale tak naprawdę się nie zmieniamy. Ponieważ jest to klasa niezmienna, w drugiej linii wartość początkowa nie jest zmieniana, ale tworzona jest nowa, która z kolei składa się z pierwszej + " is very loud"
.
Stałe odniesienia
W artykule o typach pierwotnych poruszyliśmy temat stałych. Jak zachowa się zmienna referencyjna, gdy zadeklarujemy ją jako ostateczną ?final TV telly = new TV();
Można by pomyśleć, że dzięki temu obiekt będzie niezmienny. Ale nie, to nieprawda. Zmienna referencyjna z modyfikatorem final
zostanie powiązana z konkretnym obiektem bez możliwości odłączenia jej w jakikolwiek sposób (przedefiniowania lub zrównania z null
). Oznacza to, że po ustawieniu wartości takiej zmiennej kod taki jak:
telly = new TV();
Lub
telly = null;
spowoduje błąd kompilacji. Oznacza to, że final
działa tylko na łącze i nie ma wpływu na sam obiekt. Jeśli początkowo mamy go jako zmienny, możemy bez problemu zmienić jego stan wewnętrzny:
telly.soundVolume = 30;
Czasami zmienne są określane jako ostateczne nawet w argumentach metod!
public void enableTV (final TV telly){
telly.isOn = true;
}
Dzieje się tak, aby podczas pisania metody argumenty te nie mogły zostać przesłonięte i w związku z tym powodowały mniej zamieszania. Co by było, gdybyśmy oznaczyli final
zmienną referencyjną, która odnosi się do niezmiennego obiektu? Np String
:
final String PASSWORD = "password";
W rezultacie otrzymamy stałą, analogię stałych typu pierwotnego, ponieważ tutaj nie możemy ani przedefiniować odniesienia, ani zmienić stanu wewnętrznego obiektu (danych wewnętrznych).
Podsumujmy to
- Podczas gdy proste zmienne przechowują bity wartości, zmienne referencyjne przechowują bity reprezentujące sposób pobierania obiektu.
- Odniesienia do obiektów deklarowane są tylko dla jednego typu obiektu.
- Każda klasa w Javie jest typem referencyjnym.
- Domyślna wartość dowolnej zmiennej referencyjnej w Javie to
null
. String
jest standardowym przykładem typu referencyjnego. Ta klasa jest również niezmienna.- Zmienne referencyjne z modyfikatorem
final
są przypisane tylko do jednego obiektu bez możliwości redefinicji.
GO TO FULL VERSION