Ссылочные типы данных в Java
Без понимания синтаксиса Java невозможно стать серьезным разработчиком, поэтому сегодня мы продолжаем изучать синтаксис. В одной из прошлых статей мы говорили о примитивных переменных, но так как видов переменных два, сегодня мы поговорим о втором виде — ссылочные типы в Java. Итак что это? Зачем нужны ссылочные типы данных в Java? Давайте представим, что у нас есть объект телевизор с некоторыми характеристиками, такими как номер канала, громкость звука и флаг включенности:
Как простой тип, например, int , может хранить эти данные? Напомним: одна переменная int — это 4 байта. А ведь там внутри есть две переменные (4 байта + 4 байта) этого же типа, да ещё и boolean (+1 байт). Итого — 4 к 9, а ведь как правило, в объекте хранится намного больше информации. Что делать? Нельзя же вложить объект в переменную. На этом моменте в нашей истории появляются ссылочные переменные. Ссылочные переменные хранят адрес ячейки памяти, в которой расположен определенный объект. То есть это “визитка” с адресом, имея которую мы можем найти наш объект в общей памяти и выполнять с ним некоторые манипуляции. Ссылка на любой объект в Java представляет собой ссылочную переменную. Как бы это выглядело с нашим объектом телевизора:
Переменной типа 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 уже ни на кого указывать не будет (что не мешает нам в дальнейшем присвоить ей ссылку на какой-нибудь объект типа 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 будет привязана к определенному объекту без возможности её как-либо отвязать (переопределить или приравнять к null ). То есть, после задания значения такой переменной, код вида:
будет вызывать ошибку компиляции. То есть final действует только на ссылку, а на сам объект влияния не оказывает. Если изначально он у нас изменяемый, мы без проблем можем менять его внутреннее состояние:
public void enableTV (final TV telly)
Это делается для того, чтобы в процессе написания метода эти аргументы нельзя было переопределить и соответственно создать меньше путаницы. А что если обозначить final ссылочную переменную, которая ссылается на неизменяемый объект? К примеру String :
final String PASSWORD = "password";
Как следствие, мы получим константу, аналог констант примитивного типа, ведь тут мы не можем ни переопределить ссылку, ни изменить внутреннее состояние объекта (внутренние данные).
Подведем итоги
- Если простые переменные хранят биты значений, то ссылочные переменные хранят биты, представляющие способ получения объекта.
- Ссылки на объекты объявляются лишь для одного вида объектов.
- Любой класс в Java — это ссылочный тип.
- По умолчанию в Java значение любой переменной ссылки — null .
- String — стандартный пример ссылочного типа. Также этот класс является неизменяемым (immutable).
- Ссылочные переменные с модификатором final привязаны лишь к одному объекту без возможности переопределения.
Типы данных Java: примитивные, ссылочные
Каждая переменная в Java имеет тип данных, которые разделены на две группы:
Переменная занимает некоторое место в памяти в зависимости от ее типа данных.
Примитив содержит значение переменной непосредственно в памяти, выделенной для нее. Например, число или символ.
Ссылочный типа отличается еще называется ссылкой. Не содержит объект, но содержит ссылку на него, которая указывает на другое место в памяти, где он хранится. Через такую ссылку можете получить доступ к полям и методам ссылочного объекта. Допустимо иметь много разных переменных, ссылающихся на один и тот же объект. Это невозможно с примитивами.
Примитивные типы данных
Тип | Значение |
boolean | Двоичное true или false |
byte | 8-битное со знаком, значения от -128 до 127 |
short | 16-битное со знаком, значения от -32,768 до 32,767 |
char | 16-битный символ юникода |
int | 32-битное со знаком, значения от -2.147.483.648 до 2.147.483.647 |
long | 64-битное со знаком, значения от -9,223.372.036.854.775.808 до 9.223.372.036.854.775.808 |
float | 32-разрядное с плавающей запятой |
double | 64-битное с плавающей запятой |
Примитивные типы данных не являются ни объектами, ни ссылками на них.
Типы объектов
Примитивные типы также входят в версии, которые являются полноценными объектами. Это означает, что можно:
- ссылаться на них через ссылку на объект;
- давать несколько ссылок на одно и то же значение;
- вызывать методы для них, как и для любого другого объекта.
Формат | Описание |
Boolean | A binary value of either true or false |
Byte | 8 bit signed value, values from -128 to 127 |
Short | 16 bit signed value, values from -32.768 to 32.767 |
Character | 16 bit Unicode character |
Integer | 32 bit signed value, values from -2.147.483.648 to 2.147.483.647 |
Long | 64 bit signed value, values from -9.223.372.036.854.775.808 to 9.223.372.036.854.775.808 |
Float | 32 bit floating point value |
Double | 64 bit floating point value |
String | N byte Unicode string of textual data. Immutable |
Обратите внимание, что типы объектов пишутся с заглавной буквы в начале их имени. Примитивная версия (иной вариант) пишется всеми строчными буквами. Существуют также различия в аббревиатурах, такие как int против Integer и char против Character.
Также можете создавать свои собственные более сложные типы данных, классы.
Когда объявляете переменную ссылки, она не указывает ни на один из них, т.к. сначала нужно создать (экземпляр) его. Вот как это делается:
Integer myInteger; myInteger = new Integer(45);
В этом примере переменная myInteger ссылается на объект Integer, который внутренне содержит 45. Именно новая часть Integer(45) кода создает Integer.
Создание при объявленной переменной:
Integer myInteger = new Integer(45);
Версии объектов примитивных типов данных неизменны
Значит, значения, хранящиеся в них, не могут быть изменены после установки. Например, значение, хранящееся в Integer, не может быть изменено после создания.
Переменная, которая ссылается на объект, может быть указана на другой. Вот пример:
Integer myInteger = new Integer(45); myInteger = new Integer(33);
Автобокс
До Java 5 вам приходилось вызывать методы для объектных примитивных типов, чтобы получить их значение:
Integer myInteger = new Integer(45); int myInt = myInteger.intValue();
С Java 5 у вас есть понятие, называемое «автобокс». Это означает, что оболочка может автоматически «упаковывать» примитивную переменную в объектную версию или «распаковывать» объектный примитив. В зависимости от того, что требуется. Предыдущий пример может быть написан так:
Integer myInteger = new Integer(45); int myInt = myInteger;
В этом случае Java автоматически извлечет int из myInteger и присвоит это значение myInt.
Точно так же создание объектной примитивной переменной было ручным действием:
int myInt = 45; Integer myInteger = new Integer(myInt);
С помощью автобокса оболочка может сделать это за вас. Теперь запись выглядит так:
int myInt = 45; Integer myInteger = myInt;
Затем Java автоматически «упаковывает» примитив в объектную версию соответствующего типа.
Функции автобокса позволяют использовать примитивы, для которых обычно требовалась его объектная версия и наоборот. Есть одна ловушка. Переменная object (ссылка) может указывать на ноль, то есть на ничто. Если попытаетесь преобразовать ноль в примитивное значение, получите исключение NullPointerException (ошибка, которая приводит к сбою программы). Этот код показывает пример этого:
Integer myInteger = null; int myInt = myInteger;
Этот код будет хорошо скомпилирован, но при выполнении он приведет к исключению NullPointerException, поскольку myInteger указывает на ноль. Таким образом, невозможно преобразовать (распаковать) значение объекта, на который он указывает, поскольку не указывает ни на один объект.