1. Свойства: геттер и сеттер
Когда большой проект разрабатывают десятки программистов одновременно, часто бывают проблемы с тем, что они по-разному относятся к данным, которые хранятся в полях класса.
Никто детально не изучает документацию по классам, или в ней могут быть не описаны все случаи, поэтому часто могут возникать ситуации, когда данные внутри объекта «портятся», и объект становится не валидным.
Чтобы избежать таких ситуаций, в Java принято все поля класса делать private. Только методы класса могут менять переменные класса, и никакие методы из других классов не имеют доступа к переменным класса напрямую. Вот так.
Если вы хотите, чтобы другие классы могли получать или менять данные внутри объектов вашего класса, вы должны добавить в код вашего класса два метода — get-метод и set-метод. Пример:
Код | Примечание |
---|---|
|
private -поле nameИнициализация поля через конструктор getName() — метод возвращает значение поля namesetName() — метод изменяет значение поля name
|
Никакой другой класс не сможет изменить значение поля name напрямую. Если кому-то нужно получить значение поля name, ему придется вызвать метод getName()
у объекта типа Person
. Если какой-то код хочет поменять значение поля name, ему нужно будет вызвать метод setName()
у объекта типа Person
.
Метод getName()
еще называют «геттер поля name», а метод setName()
— «сеттер поля name».
Это очень распространённый подход. В 80-90% всего Java кода вы никогда не увидите публичные переменные класса. Вместо этого они будут объявлены private
(ну или protected
), и у каждой переменной будут публичные геттеры и сеттеры.
Этот подход делает код длиннее, но надежнее.
Обращение к переменной класса напрямую — это как поворот через двойную сплошную: проще и быстрее, но если так будут делать все, то всем же будет от этого и хуже.
Допустим, вы хотите создать класс, который описывает точку на плоскости x
и y
. Вот как это сделал бы программист-новичок:
class Point
{
public int x;
public int y;
}
А вот как это бы сделал опытный Java-программист:
Код |
---|
|
Код стал длиннее? Безусловно.
Зато в сеттеры и геттеры можно добавить валидацию параметров. Например, можно следить за тем, чтобы x
и y
всегда были больше нуля (или не меньше нуля). Пример:
Код | Примечание |
---|---|
|
2. Время жизни объекта
Вы уже знаете, что объекты создаются с помощью оператора new
, а вот как объекты удаляются? Не существуют же они вечно — для этого никакой памяти не хватит.
Во многих языках программирования, таких как С++, для удаления объекта есть специальный оператор delete
. А как ситуация с этим обстоит в Java?
В Java все устроено немного иначе, и оператора delete в Java нет. Значит ли это, что объекты в Java не удаляются? Нет, удаляются конечно же. Иначе в Java-приложениях быстро закончилась бы память, и ни о каких месяцах беспрерывной работы и речи бы не шло.
В Java процесс удаления объектов полностью автоматизирован – удалением объектов занимается сама Java-машина. Такой процесс называется сборкой мусора (garbage collecting), а механизм, который собирает мусор — сборщиком мусора — Garbage Collector или сокращенно GC.
Так как Java-машина узнает, что какой-то объект нужно удалить и когда?
Все объекты сборщик мусора делит на достижимые и недостижимые. Если на объект есть хотя бы одна ссылка, он считается достижимым. Если нет ни одной переменной, которая ссылается на объект, такой объект считается недостижимым и объявляется мусором: значит, его можно удалять.
В Java нельзя взять и создать ссылку на существующий объект: ее можно только присвоить. Если мы стерли все ссылки на объект, он утерян навсегда.
Циклические ссылки
Предыдущая логика звучит отлично, пока мы не придумаем простой контрпример: у нас есть два объекта, которые ссылаются друг на друга (хранят ссылки друг на друга). Больше никто никаких ссылок на эти объекты не хранит.
К этим объектам нельзя обратиться из остального кода, однако ссылки на них все же есть.
Именно поэтому сборщик мусора делит объекты не на «объекты со ссылками» и «объекты без ссылок», а на достижимые и недостижимые.
Достижимые объекты
Сначала в список достижимых добавляются те объекты, которые 100% живые. Например, текущий поток (Thread.current()
) или Консоль (System.in
).
Затем список достижимых объектов пополняют те, на которые ссылаются первые достижимые объекты. Затем те, на кого ссылаются вторые и т.д.
Таким образом, если есть некая группа объектов, которые ссылаются только друг на друга, но от достижимых объектов до них никак добраться нельзя, такие объекты будут считаться мусором и будут удалены.
3. Сборка мусора
Фрагментация памяти
Еще один важный момент, связанный с удалением объектов — фрагментация памяти. Если постоянно создавать и удалять объекты, скоро вся память будет вперемешку: области занятой памяти будут постоянно перемежаться пустыми областями.
И легко может случиться ситуация, когда мы не можем создать большой объект (например, массив на миллион элементов), потому что нет большого куска свободной памяти. Т.е. свободная память вроде и есть, и много, но вот большого цельного куска свободной памяти может и не быть
Оптимизация (дефрагментация) памяти
Java-машина решает эту проблему специфическим образом. Выглядит это примерно так:
Память делится на две части. Все объекты создаются (и удаляются) только в одной ее половине. Когда наступает время убрать дырки в памяти, все объекты из первой половины копируются во второю половину. Но копируются уже вплотную друг другу, чтобы дыр не было.
Выглядит этот процесс примерно так:
Этап 1: После создания объектов
Этап 2: Появление «дыр»
Этап 3: Устранение «дыр»
Таким образом, даже не нужно удалять объекты. Java-машина просто копирует все достижимые объекты в новое место, а всю область памяти со старыми объектами объявляет свободной.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ