Без понимания синтаксиса Java невозможно стать серьезным разработчиком, поэтому сегодня мы продолжаем изучать синтаксис.
В одной из прошлых статей мы говорили о примитивных переменных, но так как видов переменных два, сегодня мы поговорим о втором виде — ссылочные типы в Java.
Итак что это? Зачем нужны ссылочные типы данных в Java?
Давайте представим, что у нас есть объект телевизор с некоторыми характеристиками, такими как номер канала, громкость звука и флаг включенности:
public class TV {
int numberOfChannel;
int soundVolume;
boolean isOn;
}
Как простой тип, например, int
, может хранить эти данные? Напомним: одна переменная int
— это 4 байта. А ведь там внутри есть две переменные (4 байта + 4 байта) этого же типа, да ещё и boolean
(+1 байт)... Итого — 4 к 9, а ведь как правило, в объекте хранится намного больше информации.
Что делать? Нельзя же вложить объект в переменную. На этом моменте в нашей истории появляются ссылочные переменные.
Ссылочные переменные хранят адрес ячейки памяти, в которой расположен определенный объект. То есть это “визитка” с адресом, имея которую мы можем найти наш объект в общей памяти и выполнять с ним некоторые манипуляции.
Ссылка на любой объект в Java представляет собой ссылочную переменную.
Как бы это выглядело с нашим объектом телевизора:
TV telly = new TV();
Переменной типа TV с именем telly
мы задаем ссылку на создаваемый объект типа TV. То есть, JVM выделяет память в куче под объект TV, создает его и адрес на его местоположение в памяти, кладется в переменную telly
, которая хранится в стеке.
Подробнее о памяти, а именно — о стеке и еще массе полезного, можно почитать в этой лекции.
Переменная типа TV и объект типа TV, заметили? Это неспроста: объектам определенного типа должны соответствовать переменные того же типа (не считая наследования и реализаций интерфейсов, но сейчас мы это не учитываем). В конце концов, не будем же мы в стаканы наливать суп?
Получается, что у нас объект — это телевизор, а ссылочная переменная для него — как бы пульт управления. С помощью этого пульта мы можем взаимодействовать с нашим объектом и его данными. Например, задать характеристики для нашего телевизора:
telly.isOn = true;
telly.numberOfChannel = 53;
telly.soundVolume = 20;
Тут мы использовали оператор точки .
— чтобы получить доступ и начать использование внутренних элементов объекта, на который ссылается переменная.
Например, в первой строке мы сказали переменной telly
: “Дай нам внутреннюю переменную isOn
объекта, на который ты ссылаешься, и задай ей значение true” (включи нам телевизор).
Переопределение ссылочных переменных
Допустим, у нас есть две переменные ссылочного типа и объекты, на которые они ссылаются:
TV firstTV = new TV();
TV secondTV = new TV();
Если мы напишем:
firstTV = secondTV;
это будет означать, что мы первой переменной в качестве значения присвоили копию адреса (значение битов адреса) на второй объект, и теперь обе переменные ссылаются на второй объект (иначе говоря, два пульта от одного и того же телевизора).
В тоже время, первый объект остался без переменной, которая на него ссылается. В итоге у нас есть объект, к которому невозможно обратиться, ведь переменная была такой условной ниточкой к нему, без которой он превращается в мусор, просто лежит в памяти и занимает место. Впоследствии этот объект будет уничтожен из памяти сборщиком мусора.
Прервать связующую ниточку с объектом можно и без другой ссылки:
secondTV = null;
В итоге ссылка на объект останется одна — firstTV
, а secondTV
уже ни на кого указывать не будет (что не мешает нам в дальнейшем присвоить ей ссылку на какой-нибудь объект типа TV).
Класс String
Отдельно хотелось бы упомянуть класс String. Это базовый класс, предназначен для хранения и работы с данными, которые хранятся в виде строки. Пример:
String text = new String("This TV is very loud");
Здесь мы передали строку для хранения в конструкторе объекта.
Но никто так не делает. Ведь строки можно создавать:
String text = "This TV is very loud";
Гораздо удобнее, правда?
По популярности использования String
не уступает примитивным типам, но всё же это класс, и переменная, которая ссылается на него — не примитивного, а ссылочного типа.
У String
есть вот такая замечательная возможность конкатенации строк:
String text = "This TV" + " is very loud";
В итоге мы снова получим текст: This TV is very loud
, так как две строки соединятся в одно целое, и переменная будет ссылаться на этот полный текст.
Важным нюансом является то, что String
— это неизменяемый класс. Что это значит?
Возьмем такой пример:
String text = "This TV";
text = text + " is very loud";
Вроде, бы всё просто: объявляем переменную, задаем ей значение. На следующей строке изменяем его. Но не совсем-то и изменяем. Так как это неизменяемый класс, на второй строке начальное значение не меняется, а создается новое, которое в свою очередь состоит из первого + " is very loud"
.
Ссылочные константы
В статье про примитивные типы мы затрагивали тему констант. Как же будет себя вести ссылочная переменная, когда мы объявим её final?
final TV telly = new TV();
Возможно, вы подумаете, что это сделает объект неизменяемым. Но нет, это не так.
Ссылочная переменная с модификатором final
будет привязана к определенному объекту без возможности её как-либо отвязать (переопределить или приравнять к null
).
То есть, после задания значения такой переменной, код вида:
telly = new TV();
или
telly = null;
будет вызывать ошибку компиляции.
То есть final
действует только на ссылку, а на сам объект влияния не оказывает. Если изначально он у нас изменяемый, мы без проблем можем менять его внутреннее состояние:
telly.soundVolume = 30;
Иногда, переменные обозначают как final даже в аргументах метода!
public void enableTV (final TV telly){
telly.isOn = true;
}
Это делается для того, чтобы в процессе написания метода эти аргументы нельзя было переопределить и соответственно создать меньше путаницы.
А что если обозначить final
ссылочную переменную, которая ссылается на неизменяемый объект? К примеру String
:
final String PASSWORD = "password";
Как следствие, мы получим константу, аналог констант примитивного типа, ведь тут мы не можем ни переопределить ссылку, ни изменить внутреннее состояние объекта (внутренние данные).
Подведем итоги
- Если простые переменные хранят биты значений, то ссылочные переменные хранят биты, представляющие способ получения объекта.
- Ссылки на объекты объявляются лишь для одного вида объектов.
- Любой класс в Java — это ссылочный тип.
- По умолчанию в Java значение любой переменной ссылки —
null
. String
— стандартный пример ссылочного типа. Также этот класс является неизменяемым (immutable).- Ссылочные переменные с модификатором
final
привязаны лишь к одному объекту без возможности переопределения.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ