yandex rtb 1
ГоловнаЗворотній зв'язок
yande share

Объектно-ориентированное программирование

Лекция 14.

Пакеты. Управление доступом в пакетах. Интерфейсы.

 

При разработке собственного класса и использовании классов других разработчиков надо быть уверенным, что выбранное для класса имя будет уникальным. В противном случае это имя может вступить в противоречие с уже использующимся именем. Соответственно, компилятор языка не поймет, какой именно класс должен быть использован. Таким образом, возникает необходимость организовать свое собственное уникальное пространство имен. Кроме того, необходим способ каким-то образом структурно упорядочить созданные классы. Если создаваемый набор состоит из десятков или сотен классов, им невозможно управлять без создания какой-то внутренней структуры.

Язык Java обеспечивает специальный механизм для разделения пространства имен классов на управляемые части. Этот механизм называется «пакеты» (packages). Пакет является механизмом как именования, так и управления видимостью. Все классы Java распределяются по пакетам. Кроме классов, пакеты могут включать в себя интерфейсы и вложенные подпакеты, образуя древовидную структуру.

Эта структура в точности отображается на структуру файловой системы. Все классы, принадлежащие одному пакету, хранятся в одном каталоге файловой системы. Подпакеты собираются в подкаталоги этого каталога.

Каждый пакет образует одно пространство имен. Это означает, что все имена классов, подпакетов и интерфейсов в пакете должны быть уникальны. Имена в разных пакетах могут совпадать. Таким образом, ни один класс, интерфейс или подпакет не могут оказаться одновременно в двух пакетах.

Для того, чтобы создать пакет, необходимо в начале файла с исходным кодом включить оператор:

package имя_пакета;

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

Точно таким же образом создается и иерархия пакетов:

package имя_пакета.имя_подпакета;

Для создания подпакета необходимо отделить имя подпакета от стоящего выше по иерархии с помощью операции «точка».

Полное имя класса, попадающего в пакет, состоит из имени пакета и имени класса. Например, если в пакете MyPack.MySubpack объявлен класс MyClass, то его полное имя будет MyPack.MySubpack.MyClass.

Компилятор ищет классы только в пределах пакета, который указан в начале файла. Заставить компилятор искать класс в другом пакете можно, указав его полное имя. Если полное имя длинное, а класс используется часто, становится утомительным каждый раз писать его. Для сокращения имени используется оператор import:

import MyPack.MySubpack.MyClass;

Этот оператор (или операторы, если требуется включить несколько классов) в исходном файле всегда следует после оператора package и до определения классов пакета.

Включив оператор import, мы в дальнейшем можем использовать имя класса без имени пакета. Если требуется включить несколько классов из одного пакета, можно использовать другую форму оператора:

import MyPack.MySubpack.*;

Тем самым мы указываем компилятору, в каком пакете (а в данном случае – в каком подпакете) следует искать необходимые классы. Импорту подлежат только те классы пакета, котрые объявлены как public.

Пакет создается даже в том случае, когда оператор package не используется. В таком случае компилятор создает неименованный пакет (unnamed package), которому соответствует текущий каталог файловой системы. Поэтому при компиляции файла без определения пакета class-файл попадает в тот же каталог, что и исходный файл. Если же пакет определен, компилятор сам создает для него каталог и помещает туда скомпилированный файл.

Пакеты добавляют еще одно измерение к управлению доступом. Пакеты действуют как контейнеры для классов и других зависимых пакетов. Классы действуют как контейнеры для данных и кода. Класс – самый мелкий модуль абстракции Java. Рассмотрим права доступа к членам класса.

Из-за взаимодействия между классами и пакетами Java адресует четыре категории видимости для членов класса:

·         подкласс в том же пакете (для нашего рисунка – доступ членов класса Подкласс_1 из Класс_1);

·         неподклассы в том же пакете (доступ членов Подкласс_2 из Класс_2);

·         подклассы в различных пакетах (доступ членов Подкласс_2 из Класс_1);

·         классы, которые не находятся в том же пакете и не являются подклассами (доступ членов Класс_2 из Класс_1).

Рассмотрим таблицу, описывающую все многообразие вариантов доступа:

 

Private

Без модификатора

Proteced

Public

Тот же класс

True

True

True

True

Подкласс того же пакета

False

True

True

True

Неподкласс того же пакета

False

True

True

True

Подкласс другого пакета

False

False

True

True

Неподкласс другого пакета

False

False

False

True

Таким образом, все, объявленное как public, может быть доступно отовсюду. Все, объявленное как private, не может быть доступно кроме как собственного класса. Если спецификатор доступа не объявлен, он видим в своем пакете. Это – доступ по умолчанию. Члены класса, объявленные как protected. видимы в своем пакете и во всех подклассах, даже если они находятся в других пакетах.

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

Язык Java не допускает множественное наследование. Это было сделано для того, чтобы не возникало проблем, связанных со случаями, когда класс порождается от нескольких классов, которые в свою очередь порождаются от единого предка. Что же делать, когда класс все-таки должен сочетать в себе свойства нескольких классов. В этом случае применяется еще одна конструкция языка – интерфейс. Интерфейс (interface) содержит только константы и сигнатуры методов, без их реализации. Интерфейсы размещаются в тех же пакетах и подпакетах, что и классы, и компилируются также в class-файлы. Определение интерфейса во многом подобно определению класса:

access interface interface_name {

return_type method1(parameter_list1);

return_type methodN(parameter_listN);

type final-variable1 = value1;

type final-variableM = valueM;

}

Здесь access – спецификатор доступа (или public, или без спецификатора). Если спецификатор доступа не используется, интерфейс виден только членам пакета. Если спецификатор доступа включен, интерфейс виден из любого класса.

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

Интерфейсы допускают наследование: после имени интерфейса может стоять слово extends и через запятую список интерфейсов-предков. Таким образом, интерфейсы могут порождать свою, независимую от классов, иерархию, в которой допускается множественное наследование.

Приведем пример определения простого интерфейса:

interface MyInterface {

void myMethod(int param);

}

Когда интерфейс определен, он может реализовываться одним или несколькими классами. Для реализации интерфейса в определение класса включают предложение implements, за которым через запятую следуют имена интерфейсов:

access class class_name [extends superclass] [ implements interface_name1 [, interface_name2 …]] {

// тело класса

}

Методы, которые реализуют интерфейс, должны быть объявлены как public. приведем пример:

class MyClass implements MyInterface {

public void myMethod(int param) {

      System.out.println(“param=”+param);

}

}

Если в классе реализуются не все методы интерфейса, он должен быть объявлен как абстрактный.

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

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

MyInterface m=new MyClass();

m.myMethod(1);

Понятно, что ссылочная переменная не может обращаться к методам класса, не реализующим интерфейс. Но с ее помощью можно обращаться к одним и тем же методам в разных классах (если эти методы реализуют данный интерфейс). Таким образом, интерфейсы дают еще один способ организации полиморфизма.

 

 

16