Ссылка (Java)

Материал из Циклопедии
Перейти к навигации Перейти к поиску
Java для начинающих: Урок 6. Ссылочные типы данных [6:31]

Ссылки в Java — указатели на объекты. Другими словами, ссылка — это переменная, содержащая адрес ячейки памяти, в которой хранится объект. Кроме того, ссылка может быть инициализирована как null — нулевая ссылка, не указывающая ни на какой объект в памяти (именно это значение является значением по умолчанию). Внутри класса в нестатическом контексте также может быть использована ссылка this, указывающая на текущий объект, и ссылка super, указывающая на текущий объект суперкласса.

В Java различают переменные примитивного и ссылочного типа. К примитивным относят 8 типов — пять целочисленных, два для чисел с плавающей точкой и логический тип boolean. Все остальные типы данных относятся к ссылочным типам. Это значит, что объявленная переменная этого типа хранит не состояние объекта, а ссылку на него, сам же объект хранится в определённой области памяти (куче). Создание нового класса, абстрактного класса, интерфейса или перечисления равносильно определению нового ссылочного типа данных. В отличие от создания объектов, объявление ссылок типа абстрактный класс либо интерфейс не запрещено.

В спецификации Java не содержит конкретной информации о данных, хранящихся в ссылке. Во многих JVM ссылка реализована исключительно как указатель и занимает 32 бита в 32-битных JVM и 64 бита — в 64-битных. Тем не менее, существуют реализации JVM (например, Zing Virtual Machine от Azul), в целях оптимизации помещающие в ссылку также информацию о типе данных. Другие (например, Java HotSpot Virtual Machine), наоборот, используют технологию «Compressed Oops», позволяющую использовать ссылки размером в 32 бита на 64-битных операционных системах.[1]

Действия над ссылками[править]

Среди действий над ссылками можно выделить следующие[2]:

Присваивание (оператор '=') — позволяет определить новое значение ссылки. Важно, что при присваивании на этапе компиляции контролируется тип объекта по ссылке. таким образом, в отличие от слабо типизированных и нетипизированных языков, Java не позволит инстанциировать ссылку объектом, тип которого не является подтипом ссылки. Чтобы сделать это, необходимо воспользоваться явным приведением типа.

Wiki wiki = new Wiki("cyclowiki.org"); // вызов конструктора и присваивание результата
Wiki sameWiki = wiki; // простое присваивание

Непосредственно при присвоении новый объект не создаётся. Недопустимо присваивать новые значения ссылкам this и super.

Обращение к полям и методам объекта осуществляется с помощью точки. Следует помнить, что в соответствии с принципом полиморфизма вызываемый нестатический метод определяется не типом ссылки на этапе компиляции, а типом объекта на этапе выполнения. Если ссылка имеет значение null, попытка обращения к члену класса вызовет исключительную ситуацию NullPointerException.

Action action = new SomeAction(); // ссылка указывает на новый объект
action.perform(); // вызов метода perform

Операция приведения типа позволяет определить новую ссылку другого типа, указывающую на тот же самый объект. Может пригодится, например, в следующей ситуации: тип B является подтипом типа А и расширяет его интерфейс (то есть, например, класс Beaver унаследован от класса Аnimal и определяет дополнительные методы). На ссылке типа A вызывать методы, отсутствующие в типе А, запрещено компилятором, поскольку он не может гарантировать, что эта ссылка действительно в момент выполнения будет указывать на объект типа B. Поэтому чтобы вызвать характерные для типа B методы, необходимо прибегнуть к операции приведения типа:

A a = new B(); // ссылка типа A указывает на объект типа B
B b = (B) a; // приведение ссылки a к типу B

Если ссылка указывает на объект, который не может быть приведён к указанному типу, будет выброшено исключение ClassCastException.

Проверка принадлежности к определенному типу (оператор instanceof) позволяет определить, принадлежит ли объект, на который указывает ссылка, типу либо подтипу типа, переданного в качестве правого операнда. Может использоваться для последующей безопасной операции приведения типа, поскольку пройденная подобная проверка гарантирует отсутствие ClassCastException.

if(abstractCar instanceof Trolleybus) {
 Trolleybus tBus = (Trolleybus) abstractCar; // ClassCastException не произойдёт
}

Если ссылка нулевая (null), оператор instanceof вернёт false.

Операции сравнения ссылок ('==' — «равно» и противоположный по смыслу оператор «не равно» '!=') позволяют определить, указывают ли две ссылки на один и тот же объект в памяти. Важно понимать, что сравнение объектов по значению в этом случае не производится; при необходимости следует прибегнуть к методу equals().

Помимо этого для ссылок некоторых типов определены дополнительные операторы. Основные среди них:

  • Операция конкатенации (+) для объектов типа java.lang.String.
  • Арифметические операции для классов-оболочек примитивных типов (таких как java.lang.Integer, java.lang.Double и прочих).

В отличие от некоторых других языков программирования, например языка C, ссылки в Java не требуют разыменования, работа с ними автоматически имитирует работу с объектами, на который они указывают. Также в Java отсутствует аналог арифметики указателей. В целом, сравнивая с языком C, можно сказать, что ссылки в Java предоставляют меньше возможностей, но более безопасны.[3]

Ссылки и модель памяти[править]

Основные аспекты, касающиеся ссылок и модели памяти в Java:

  • Java-объекты хранятся в куче, общей для всех потоков; локальные переменные ссылочных типов, как и переменные примитивных типов, хранятся в стеке потока.
  • При вызове методов переданные в качестве параметров переменные ссылочных и примитивных типов копируются. Это значит, что происходит передача примитивных типов по значению и ссылочных типов по ссылке (передача ссылки по значению). Изменения состояния объекта внутри метода будут видны вне метода, но изменение самой ссылки (выполнение операции присваивания) не повлияет на ту ссылку, которая выступила параметром метода.[4]
  • Объекты, на которые существуют ссылки, не могут быть удалены сборщиком мусора. Объект, на который отсутствуют ссылки, доступен для удаления. При этом, в зависимости от алгоритмов сборщика, удалению могут быть доступны также те объекты, которые хранят ссылки друг на друга (так называемые циклические ссылки), но на которые нет ссылок извне.[5]

Модификаторы[править]

Различные модификаторы, применяемые к полям класса, аргументам функций или локальным переменным, как правило, влияют на ссылку, а не на объект, на который она указывает. Например:

  • Ссылка, помеченная модификатором final, не может быть изменена после присваивания. При этом объект, на который она указывает, может быть изменён.
  • Актуальное значение ссылки, помеченной модификатором volatile, в многопоточной среде всегда будет доступно всем потокам (то есть оно не будет кэшироваться из соображений оптимизации, что гарантированно позволит избежать ситуации, когда один поток изменил ссылку, после чего второй прочитал старое значение).

Пакет java.lang.ref[править]

Помимо обыкновенных ссылок, известных так же как Strong References (сильные ссылки), в Java представлен пакет java.lang.ref, содержащий реализации Soft (мягких), Weak (слабых) и Phantom (фантомных) ссылок, которые по-разному обрабатываются сборщиком мусора и предоставляют пользователю возможность более гибко работать с памятью.

ReferenceQueue[править]

Объект ReferenceQueue представляет собой очередь, хранящую ссылки на объекты, подготовленные сборщиком мусора для удаления, и предоставляющую методы по получению и удалению объектов. С помощью очереди имеется возможность отлавливать удаление объектов и выполнять некоторую логику (например, удаление ссылавшихся на собранные сборщиком мусора ссылки).[6]

Интересно, что между попаданием объекта в очередь и его удалением сборщиком мусора проходит некоторое время, поэтому очередь позволяет спасти объект, определив на него новую сильную ссылку.

Ссылка[править]

Абстрактный класс java.lang.ref.Reference[7] предоставляет базу для ссылочных классов. В нём определены следующие методы:

  • get — метод, возвращающий сильную ссылку на объект, на который указывает ссылка.
  • clear — очищает ссылку.
  • isEnqueued и enqueue, отвечающие за взаимодействие ссылки и объекта ReferenceQueue.

Мягкая ссылка[править]

Мягкая ссылка — объект класса java.lang.ref.SoftReference. Особенность такой ссылки в том, что если она указывает на объект, на который не указывают сильные ссылки, этот объект может быть удалён сборщиком мусора в случае угрозы OutOfMemoryError. Конкретные правила удаления подобных объектов варьируются:

  • В JVM HotSpot Virtual Machine объект удаляется, если время, прошедшее с последнего обращения к нему, превосходит значение выражения F * MSPerMB, где F — количество свободных мегабайт в куче, а MSPerMB — коэффициент, который пользователь может задать с помощью ключа -XX:SoftRefLRUPolicyMSPerMB (по умолчанию его значение — 1000 мс/МБ).
  • Виртуальная машина IBM ориентируется не на время последнего обращения к объекту, а на количество запусков сборщика мусора, которые пережил объект.[8]

Несколько примеров использования мягких ссылок:

  • Кэширование. В частности, этот подход использован в классе java.lang.Class — информация о полях, конструкторах и методах класса закэширована с помощью мягких ссылок.
  • Защита от OutOfMemoryError при получении потенциально огромного количества информации. В этом случае данные всё равно не будут обработаны, но вовремя удалённый объект позволит серверу продолжить работу в штатном режиме, тогда как системная ошибка нехватка памяти может дестабилизровать работу других Java-приложений на этом сервере.[9]

Объявление мягкой ссылки:

SoftReference<Thing> thing = new SoftReference<Thing>(new Thing()); //мягкая ссылка на объект класса Thing

Слабая ссылка[править]

Слабая ссылка — объект класса java.lang.ref.WeakReference. Особенность такой ссылки в том, что если она указывает на объект, на который не указывают сильные и мягкие ссылки, этот объект может быть удалён сборщиком мусора.

Несколько примеров использования слабых ссылок:

  • Ассоциация объектов. Например, имеется множество объектов Action, каждый из которых создаётся, запускаем на выполнение некоторую логику и после того, как она отработает, более не нужен. Пусть для каждого такого объекта создаётся уникальная строка-идентификатор. Сопоставить идентификатор и Action можно с помощью ассоциативного массива (Map). В этом случае в массиве будет храниться сильная ссылка на каждый Action, а значит, они не будут удаляться из памяти. Чтобы избежать этого, можно хранить в Map слабые ссылки на Action. Как только Action завершит работу и на него пропадут сильные ссылки, он будет удалён; до тех же пор к нему можно будет обращаться по сопоставленному идентификатору.
  • WeakHashMap — представитель Java Collections Framework, в основе которого лежат слабые ссылки. В отличие от описанного выше подхода, хранит слабые ссылки на ключи. Когда на ключ не остаётся сильных ссылок, он удаляется сборщиком мусора, затем с помощью ReferenceQueue определяется, какой ключ был удалён. Соответствующее ему значение также удаляется.
  • С помощью слабых ссылок можно обеспечить отсутствие дублирующихся объектов: в классе создаётся пул слабых ссылок на объекты и реализуется метод intern() (по аналогии с одноименным методом в классе java.lang.String). Вызванный на объекте, этот метод либо вернёт ссылку на объект из пула, если в нём хранится объект, равный данному, либо поместит этот объект в пул.

Фантомная ссылка[править]

Фантомная ссылка — объект класса java.lang.ref.PhantomReference. Объект, на который существуют только фантомные ссылки, доступен сборщику мусора. В отличие от описанных выше типов ссылок, фантомная ссылка не даёт пользователю возможности «спасти» объект, создав на него сильную ссылку: метод get на ней всегда возвращает null.

Зато с помощью фантомной ссылки можно определить, что объект собирается быть удалён. В силу этих особенностей фантомная ссылка бесполезна без объекта ReferenceQueue, поэтому для неё определён единственный конструктор:

ReferenceQueue<Wiki> queue = new ReferenceQueue<Wiki>(); //создание очереди 
PhantomReference<Wiki> ref = new PhantomReference<Wiki>(new Wiki("cyclowiki.org"), queue);

Основное предназначение фантомных ссылок — замена методу finalize, имеющему большое количество недостатков.[10] С их помощью можно реализовать логику подготовки объекта к удалению, например освобождение захваченных им ресурсов, очистка сессии, сохранение состояния или логгирование. Фантомная ссылка, указывающая на удаляемый объект, попадает в ReferenceQueue, откуда она может быть извлечена. Поскольку базовая фантомная ссылка не предоставляет пользователю возможности определить, на какой именно объект она ссылается, стоит реализовывать собственного потомка класса PhantomReference, хранящего идентификатор объекта (не ссылку на сам объект, ибо это будет сильная ссылка — объект перестанет быть доступен сборщику мусора). В таком случае после обращения к идентификатору можно однозначно определить, какой объект был удалён, и выполнить все необходимые операции.[11]

Источники[править]