Главная » Java » Что в действительности означает protected Java

0

 

Мы уже вкратце упоминали о том, что член класса, помеченный как protected, открыт для доступа из производных классов, но это слишком общая трактовка предмета. Если говорить более точно, будучи доступным в самом

классе и в контексте "родного" пакета (за сведениями о пакетах обращайтесь к главе 13), член с признаком protected допускает обращение посредством ссылки на объект, который относится к тому же типу или производным типам. Приведем пример, который поможет разобраться в сказанном.

Рассмотрим класс, реализующий структуру данных очереди (queue) посредством модели односвязного списка и обладающий методами add (добавление очередного объекта в конец очереди) и remove (удаление объекта из начала очереди). Элемент очереди – это объект класса Сеll, в котором содержатся ссылки на следующий элемент очереди и на текущий объект.

Class Cell {

private Сеll next;

private Object element;

public Cell (Object  element) {

this.element = element;

}

 

public Сеll (Object element, Сеll next) {

      this.element = element;

this.next = next;

}

 

public Object getElement() {

      return element;

}

public void setElement(Object element) {

this.element = element;

 

}

public Сеll getNext() {

 

return next;

}

 

public void setNext(Cell next) {

this.next = next;

}

}

 

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

public class SingleLinkedQueue {

 

protected Сеll head;     // Элемент "головы" очереди

protected Сеll tail;         // Элемент "хвоста" очереди

public void add(Object item) {/* … */}

public Object remove() {/* … */}

 

Ссылки, указывающие на "голову" и "хвост" очереди, обозначены как Protected, поэтому в расширенных классах позволяется манипулировать элементами списка напрямую, исключая потребность в обращении к методам add и remove, которое связано с дополнительными издержками, обусловленными

необходимостью выполнения некоторых вспомогательных операций упаковки и распаковки элементов.

Какой-то группе разработчиков требуется очередь с приоритетами, в которой элементы хранятся в соответствии с определенным критерием, а не просто добавляются в конец очереди. Поэтому объявляется класс PriоrityQueue и размещается в другом пакете; класс является наследником SingleListQueue и содержит переопределенной метод add, позволяющий расположить новый элемент в нужном месте очереди. В варианте метода add, принадлежащего классу PriоrityQueue, предусмотрено обращение к полям head и tail, унаследованным от класса SingleListQueue, – код находится в производном классе, поэтому доступ к членам protected разрешен. Все это вполне нормально и предсказуемо.

Группе, которая проектирует класс, реализующий очередь с приоритетами, понадобилось дополнительное средство – необходимо обеспечить возможность слияния двух очередей с приоритетами в одну. После выполнения операции слияния одна из очередей будет содержать все элементы двух исходных, а вторая опустеет. Начало объявления метода merge, выполняющего слияние, может выглядеть так:

 

public void merge(PriоrityQueue q) {

Сеll first = q.head;

// …

}

 

Здесь не предполагается доступ к ргоtесtеd-члену текущего объекта – в качестве аргумента передается ссылка на объект, к члену которого выполняется обращение. Это позволено, поскольку и метод, пытающийся обратиться к полю protected, и объект, переданный в качестве параметра, принадлежат классу PriоrityQueue. Даже если бы параметр q был объявлен в виде объекта класса, производного от PriorityQueue, все оставалось бы в норме.

Предположим, что наша творческая группа в своих изысканиях пошла еще дальше – ей потребовалось средство, обеспечивающее слияние очереди класса singleLinkQueue с очередью типа PriоrityQueue. Поэтому "всплывает" перегруженная версия метода merge, которую кратко можно было бы представить так:

 

public void merge(singleLinkQueue q) {

Сеll first = q.head;

// …

}

 

Этот код не будет откомпилирован.

Проблема состоит в том, что класс, метод которого пытается получить доступ к ргоtесtеd-члену, есть PriоrityQueue, а переданная в метод ссылка указывает на объект типа SingleLinkQueue. Класс singleLinkQueue не равнозначен PriоrityQueue и не является производным от него. Хотя каждый объект РriоrityQueue с полным правом можно воспринимать как объект класса singleLinkQueue, обратное утверждение, вообще говоря, не верно.

Вот причины, которые обусловливают рассмотренное ограничение. Каждый производный класс наследует контракт базового класса и расширяет последний тем или: иным образом. Предположим, что один из производных классов, реализуя собственный контракт, каким-то образом ограничивает область допустимых значений полей protected, унаследованных от базового класса. Если бы другой про из водный класс ТОГО же базового класса мог обращаться к ргоtесtеd-членам объектов первого, он приобрел бы возможность и напрямую манипулировать их содержимым, ничего не "зная" о введенных ограничениях и рискуя нарушить контракт первого Производного класса, а подобного, разумеется, допустить нельзя.

Члены, обозначенные как protected и statiс одновременно, открыты для отступа в любом расширенном классе. Если бы поле head класса SingleListQueue было снабжено модификатором statiс, любой метод (не важно, статический или нет) класса  получил бы право обращаться к head. Это вполне допустимо, поскольку производный класс не способен воздействовать на ту часть контракта базового класса, которая относится к статическим членам, и может только скрыть, но не переопределить их, а поэтому опасность нарушения контракта другим классом совершенно исключена.

Члены класса, обозначенные модификатором protected, доступны также из любого кода внутри пакета, которому принадлежит класс. Если бы рассмотренные выше классы очередей размещались в пределах одного пакета, они получили бы взаимные права доступа к полям head и tail – и любым protected-членам других типов, определенных в том же пакете. Классы, принадлежащие одному пакету, находятся в "доверительных" отношениях и не нарушают контрактов друг друга (более подробные сведения об этом приведены в главе 13). Если модификаторы доступа перечислить в таком порядке: private, доступ уровня пакета, protected и publiс, – то каждый последующий наращивает возможности доступа к соответствующим членам класса.

 

Источник: Арнолд, Кен, Гослинг, Джеймс, Холмс, Дэвид. Язык программирования Java. 3-е изд .. : Пер. с англ. – М. : Издательский дом «Вильяме», 2001. – 624 с. : ил. – Парал. тит. англ.

По теме:

  • Комментарии