Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

TarasovVLJavaAndEclipse_08_PacketsInterfaces

.pdf
Скачиваний:
13
Добавлен:
08.04.2015
Размер:
1.27 Mб
Скачать

Далее следует исходный код (текст) для пакета р2. Два класса, определенных в р2, охватывают два других условия, на которые влияет управление доступом. Первый класс, Protection2, является подклассом p1.Protection. Он предоставляет доступ ко всем переменным класса p1.Protection, кроме n_pri (потому что она — private) и n (т. к. она объявлена с защитой, заданной по умолчанию). Помните, умолчание разрешает доступ только изнутри класса или пакета, не из подклассов внешних пакетов. Наконец, класс OtherPackage имеет доступ только к одной переменной, n_pub, которая была объявлена как public.

Файл Protection2.java:

package p2;

// Другой подкласс другого пакета

class Protection2 extends p1.Protection { Protection2(){

System.out.println("Производный конструктор другого пакета");

//только класс или пакет

//System.out.println("n = " + n);

//только класс

//System.out.println("n_pri = " + n_pri); System.out.println("n_pro = " + n_pro);

System.out.println("n_pub = " + n_pub);

}

}

Файл OtherPackage.java:

package p2;

// Другой неподкласс другого пакета

public class OtherPackage { OtherPackage(){

p1.Protection p = new p1.Protection(); System.out.println("Конструктор другого пакета");

//только класс или пакет

//System.out.println("n = " + p.n);

//только класс

//System, out .println ("n_pri = " + p.n_pri) ;

//только класс, подкласс или пакет

//System.out.println("njpro = " + p.n_pro); System.out.println("n_pub = " + p.n_pub);

}

}

Для тестирования пакета p1 создадим класс Demo_p1 (рис.8)

Рис. 8. Создание тестирующего класса для пакета p1

//Файл Demo_p1.java

//Тестирование пакета p1. package p1;

public class Demo_p1 {

// Создает объекты различных классов из p1. public static void main(String args[]){

System.out.println("main(). Тестирование пакета p1"); System.out.println("main(). Создание объекта класса Protection"); Protection ob1 = new Protection();

System.out.println("main(). Создание объекта класса Derived"); Derived ob2 = new Derived();

System.out.println("main(). Создание объекта класса SamePackage из пакета p1");

SamePackage ob3 = new SamePackage();

}

}

Данная программа выводит: main(). Тестирование пакета p1

main(). Создание объекта класса Protection Конструктор Protection

n = 1 n_pri = 2 n_pro = 3 n_pub = 4

main(). Создание объекта класса Derived Конструктор Protection

n = 1 n_pri = 2 n_pro = 3 n_pub = 4

Конструктор Derived n = 1

n_pro = 3 n_pub = 4

main(). Создание объекта класса SamePackage из пакета p1 Конструктор класса SamePackage из пакета p1.

Создаю объект класса Protection Конструктор Protection

n = 1 n_pri = 2 n_pro = 3 n_pub = 4

Продолжение работы конструктора SamePackage. Работаю с объектом класса Protection

n = 1 n_pro = 3 n_pub = 4

Для тестирования пакета p2 создадим следующий файл

Demo_p2.java:

//Файл Demo_p2.java

//Тестирование пакета p2

package p2;

//Создает объекты различных классов из р2. public class Demo_p2 {

public static void main(String args[]){ System.out.println("main(). Тестирование пакета p2");

System.out.println("main(). Создание объекта класса Protection2"); Protection2 ob1 = new Protection2();

System.out.println("main(). Создание объекта OtherPackage пакета p2"); OtherPackage ob2 = new OtherPackage();

}

}

Состав созданного проекта показан в окне Package Explorer на рис.9. Для запуска нужного класса нужно выбрать его в окне Project Explorer и выполнить команду меню Run, Run (комбинация клавиш Ctrl+F11).

Рис. 9. Состав проекта с двумя пакетами

Программа из класса Demo_p2 выводит: main(). Тестирование пакета p2

main(). Создание объекта класса Protection2 Конструктор Protection

n = 1 n_pri = 2 n_pro = 3 n_pub = 4

Конструктор Protection2, производного от Protection n_pro = 3

n_pub = 4

main(). Создание объекта OtherPackage пакета p2

Конструктор класса OtherPackage пакета p2 Создаю объект класса Protection Конструктор Protection

n = 1 n_pri = 2 n_pro = 3 n_pub = 4

Работаю с объектом класса Protection n_pub = 4

8.2. Импорт пакетов

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

Чтобы обеспечить видимость некоторых классов или полных пакетов Java, используется оператор import. После импортирования на класс можно ссылаться прямо, используя только его имя. Для записи законченной программы в операторе import нет технической необходимости, но он удобен для программиста. Если программа обращается к нескольким десяткам классов, оператор import сохранит массу усилий и времени при вводе кода.

В исходном файле Java оператор import следует после оператора package (если он используется) и перед любыми определениями класса. Общая форма оператора import:

import pkg1[.ркд2].(classname | *) ;

Здесь pkg1 — имя пакета верхнего уровня, pkg2 — имя подчиненного пакета внутри внешнего пакета (имена разделяются точкой). Нет никакого практического предела глубине пакетной иерархии, за исключением того, что обусловлен файловой системой. Наконец, определяется или явное имя класса, или звездочка (*), которая указывает, что компилятор Java должен импортировать полный пакет. Следующий кодовый фрагмент показывает использование обеих форм:

import

java.util.Date;

//

Импорт

класса Date

import

java.io.*;

//

Импорт

всех классов пакета io

Форма со звездочкой может увеличить время компиляции — особенно если импортируется несколько больших пакетов. По этой причине лучше явно называть классы, которые нужно использовать, а не импортировать целые пакеты. Однако такая форма не имеет абсолютно никакого влияния на эффективность времени выполнения или на размер классов.

Все стандартные классы Java хранятся в пакете с именем Java. Основные функции языка размещаются во внутреннем пакете Java с именем java.lang. Обычно нужно импортировать каждый пакет или класс, который требуется использовать, но, так как Java бесполезен без многих функциональных возможностей java.lang, указанный пакет неявно импортируется компилятором для всех программ. Это эквивалентно появлению следующей строки в начале всех программ:

import java.lang.*;

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

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

import java.util.*;

class MyDate extends Date {

}

Тот же пример без оператора import выглядит так:

class MyDate extends java.util.Date {

}

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

Программа 40. Открытый класс в пакете

Создадим проект с названием Progr40_PublicPacketClass. При содании проекта создается папка с тем же именем, что и имя проекта, в которой создается структура каталогов, в частности, каталог src для исходных файлов и каталог bin для откомпилированных классов.

В составе этого проекта создадим пакет MyPack, что приведет к появлению папки с таким именем.

Затем в составе пакета MyPack создадим класс Balance, что приведет к созданию файла Balance.java.

package MyPack;

// Файл Balance.java

/* Теперь класс Balance, его конструктор и метод show()

*являются общими (public). Это означает, их может использовать

*код неподкласса, находящийся вне их пакета. */

public class Balance { String name; double bal;

public Balance(String n, double b){ name = n;

bal = b;

}

public void show(){ if(bal < 0)

System.out.print(" -->> "); else

System.out.println(name + ": $" + bal);

}

}

Теперь класс Balance — с общим (public) доступом. То же можно сказать о его конструкторе и методе show (). Это означает, что они доступны любым типам кода вне пакета МуРаск. Например, в следующей программе класс TestBalance импортирует пакет МуРаск и получает возможность использовать класс Balance:

// Файл TestBalance.java import MyPack.*;

class TestBalance {

public static void main(String args[]){

/* Из-за того, что Balance теперь public, можно использовать класс Balance и вызывать его конструктор. */

Balance test = new Balance("J. J. Jaspers", 99.88);

test.show();

// Можно также вызвать show ()

}

 

}

 

Если удалить спецификатор public из класса Balance то при компиляции TestBalance взникнет ошибка.

8.3. Интерфейсы

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

Для реализации интерфейса класс должен создать полный набор методов, определенных интерфейсом. Однако каждый класс волен сам определять детали своей реализации. Ключевое слово interface позволяет полностью использовать аспект полиморфизма, декларируемый как "один интерфейс, множественные методы".

Интерфейсы разработаны для поддержки динамического вызова методов во время выполнения. Обычно для вызова метода одного класса из другого нужно, чтобы оба класса присутствовали во время компиляции и компилятор Java мог проверить совместимость сигнатур методов. Само по себе это требование предъявляется к статической и нерасширяемой среде классификации. В иерархической же многоуровневой системе, где функциональные возможности обеспечиваются длинными цепочками связанных в иерархию классов, этот механизм неизбежно используется все большим и большим числом

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

Заметим, что интерфейсы добавляют во многие приложения те функциональные возможности, которые обычно потребовали бы использования множественного наследования языков типа С++.

Определение интерфейса

Определение интерфейса во многом подобно определению класса. Общая форма интерфейса выглядит так:

access interface name {

return-type method-name1(parameter-list) ; return-type method-name2(parameter-list) ; type final-varname1 = value;

type final-varname2 = value; //...

return-type method-nameN(parameter-list); type final-varnameN = value;

}

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

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

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

interface Callback {

void callback(int param);

}

Здесь объявлен простой интерфейс, содержащий один метод с именем callback(), который имеет единственный целый параметр.

Реализация интерфейсов

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

access class classname [extends superclass] [implements interface [,interface...] ] {

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

}

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

Пример небольшого класса, который реализует интерфейс Callback, показанный ранее:

class Client implements Callback {

//Реализация Callback-интерфейса public void callback(int p){

System.out.println("callback вызван с аргументом " + p);

}

}

Обратите внимание, что callback() объявлен со спецификатором доступа public.

При реализации метода интерфейса он должен быть объявлен как public.

Классы, реализующие интерфейсы, могут определять дополнительные собственные члены. Например, следующая версия класса Client реализует метод callback() и добавляет метод nonIfaceMeth():

class Client implements Callback{ // Реализация Callback интерфейса public void callback(int p){

System.out.println("callback вызван с аргументом " + p);

}

void nonIfaceMeth(){

System.out.println("Классы, реализующие интерфейсы " + "могут также определять другие члены.");

}

}

Реализации доступа через интерфейсные ссылки

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

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

В следующем примере вызов метода callback() выполняется через ссылочную переменную интерфейса:

class TestIface {

public static void main(String args[]){ Callback с = new Client(); c.callback(42);

}

}

Программа 41. Пример использования интерфейса

Соберем приведенный выше код в работающую программу. Создадим проект с именем Progr41_Interface.

Командой File, New, Interface (рис. 10) создадим интерфейс Callback