1. Właściwości: getter i setter
Kiedy duży projekt jest rozwijany przez kilkudziesięciu programistów jednocześnie, często pojawia się problem z tym, że mają oni różne podejście do danych, które są przechowywane w polach klasy.
Nikt szczegółowo nie studiuje dokumentacji klasy lub może ona nie opisywać wszystkich przypadków, dlatego często mogą wystąpić sytuacje, gdy dane wewnątrz obiektu zostaną „uszkodzone” i obiekt stanie się nieważny.
Aby uniknąć takich sytuacji, w Javie zwyczajowo ustawia się wszystkie pola klasy jako prywatne . Tylko metody klasowe mogą zmieniać zmienne klasowe i żadne metody z innych klas nie mają bezpośredniego dostępu do zmiennych klasowych. Lubię to.
Jeśli chcesz, aby inne klasy mogły pobierać lub zmieniać dane wewnątrz obiektów Twojej klasy, musisz dodać dwie metody do kodu swojej klasy — metodę get i metodę set. Przykład:
Kod | Notatka |
---|---|
|
private -nazwa pola Inicjalizacja pola poprzez konstruktor getName() - metoda zwraca wartość nazwy pola setName() - metoda zmienia wartość nazwy pola |
Żadna inna klasa nie może bezpośrednio zmienić wartości pola nazwy. Jeśli ktoś chce uzyskać wartość pola name, będzie musiał wywołać metodę getName()
na obiekcie typu Person
. Jeśli jakiś kod chce zmienić wartość pola name, będzie musiał wywołać metodę setName()
na obiekcie typu Person
.
Metoda getName()
jest również nazywana „ pobieraczem pola nazwy ”, a metoda setName()
nazywana jest „ ustawiaczem pola nazwy ”.
To bardzo powszechne podejście. W 80-90% całego kodu Java nigdy nie zobaczysz zmiennych klasy publicznej. Zamiast tego zostaną zadeklarowane private
(well lub protected
), a każda zmienna będzie miała publiczne gettery i settery.
Takie podejście sprawia, że kod jest dłuższy, ale bardziej niezawodny.
Bezpośredni dostęp do zmiennej klasowej jest jak przechodzenie przez podwójną bryłę : jest prostszy i szybszy, ale jeśli wszyscy to zrobią, wszyscy będą na tym gorsi.
Powiedzmy, że chcesz utworzyć klasę opisującą punkt na płaszczyźnie x
i y
. Oto jak zrobiłby to początkujący programista:
class Point
{
public int x;
public int y;
}
A oto jak zrobiłby to doświadczony programista Java:
Kod |
---|
|
Czy kod stał się dłuższy? Niewątpliwie.
Ale możesz dodać walidację parametrów do seterów i getterów. Na przykład możesz zapewnić, że x
i y
są zawsze większe od zera (lub nie mniejsze od zera). Przykład:
Kod | Notatka |
---|---|
|
2. Czas życia obiektu
Wiesz już, że obiekty są tworzone za pomocą operatora new
, ale w jaki sposób obiekty są usuwane? Nie istnieją wiecznie – nie wystarczy na to żadna pamięć.
Wiele języków programowania, takich jak C++, ma specjalny operator do usuwania obiektu delete
. A jak wygląda sytuacja z tym w Javie?
W Javie wszystko jest zorganizowane trochę inaczej i nie ma w Javie operatora usuwania. Czy to oznacza, że obiekty w Javie nie są usuwane? Nie, oczywiście są usuwane. W przeciwnym razie aplikacjom Java szybko zabrakłoby pamięci i nie byłoby mowy o miesiącach nieprzerwanej pracy.
W Javie proces usuwania obiektów jest całkowicie zautomatyzowany – usuwaniem obiektów zajmuje się sama maszyna Java . Taki proces nazywa się Garbage Collection (garbage collection), a mechanizm zbierający śmieci nazywa się Garbage Collector - Garbage Collector lub w skrócie GC .
Skąd więc maszyna Java wie, że jakiś obiekt musi zostać usunięty i kiedy?
Garbage collector dzieli wszystkie obiekty na osiągalne i nieosiągalne. Jeśli obiekt ma co najmniej jedno odniesienie, jest uważany za osiągalny. Jeśli nie ma ani jednej zmiennej, która odnosiłaby się do obiektu, taki obiekt jest uważany za nieosiągalny i jest uznawany za śmieci: oznacza to, że można go usunąć.
W Javie nie można wziąć i utworzyć odniesienia do istniejącego obiektu: można go tylko przypisać. Jeśli usunęliśmy wszystkie odniesienia do obiektu, zostaje on utracony na zawsze.
Referencje cykliczne
Poprzednia logika brzmi świetnie, dopóki nie znajdziemy prostego kontrprzykładu: mamy dwa obiekty, które odwołują się do siebie (przechowują odniesienia do siebie). Nikt inny nie przechowuje żadnych odniesień do tych obiektów.
Nie można uzyskać dostępu do tych obiektów z pozostałej części kodu, ale nadal istnieją odwołania.
Dlatego Garbage Collector dzieli obiekty nie na „obiekty z referencjami” i „obiekty bez referencji”, ale na osiągalne i nieosiągalne.
Osiągalne obiekty
Najpierw te obiekty, które są w 100% żywe, są dodawane do listy osiągalnych. Na przykład bieżący wątek ( Thread.current()
) lub Konsola ( System.in
).
Lista osiągalnych obiektów jest następnie dodawana do tych, do których odwołują się pierwsze osiągalne obiekty. Następnie te, o których mowa w drugim, i tak dalej.
Zatem jeśli istnieje pewna grupa obiektów, które tylko się do siebie odnoszą, ale nie ma możliwości dostania się do nich z obiektów osiągalnych, takie obiekty zostaną uznane za śmieci i zostaną usunięte.
3. Wywóz śmieci
Fragmentacja pamięci
Kolejnym ważnym punktem związanym z usuwaniem obiektów jest fragmentacja pamięci. Jeśli ciągle tworzysz i usuwasz obiekty, wkrótce cała pamięć zostanie pomieszana: obszary zajętej pamięci będą stale przeplatane pustymi obszarami.
A łatwo może się zdarzyć, że nie uda nam się stworzyć dużego obiektu (na przykład tablicy zawierającej milion elementów), bo nie ma dużego kawałka wolnej pamięci. Te. wydaje się, że jest wolna pamięć i to dużo, ale może nie być całego dużego fragmentu wolnej pamięci
Optymalizacja pamięci (defragmentacja)
Maszyna Java rozwiązuje ten problem w określony sposób. Wygląda to mniej więcej tak:
Pamięć jest podzielona na dwie części. Wszystkie obiekty są tworzone (i usuwane) tylko w jednej połowie. Kiedy przychodzi czas na oczyszczenie dziur w pamięci, wszystkie obiekty z pierwszej połowy są kopiowane do drugiej połowy. Ale są już skopiowane blisko siebie, więc nie ma dziur.
Proces ten wygląda następująco:
Etap 1: Po utworzeniu obiektów
Etap 2: Pojawienie się „dziur”
Etap 3: Likwidacja „dziur”
W ten sposób nie trzeba nawet usuwać obiektów. Maszyna Java po prostu kopiuje wszystkie osiągalne obiekty do nowej lokalizacji i deklaruje, że cały obszar pamięci ze starymi obiektami jest wolny.
GO TO FULL VERSION