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

TarasovVLJavaAndEclipse_08_PacketsInterfaces

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

Рис. 10. Создание интерфейса

В результате в папке src создается файл Callback.java. Поместим в этот файл код интерфейса:

// Файл Callback.java interface Callback {

void callback(int param);

}

Далее добавляем в проект класс Client со следующим кодом:

// Файл Client.java

class Client implements Callback{

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

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

}

void nonIfaceMeth(){

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

}

}

И, наконец, добавляем в проект тестирующий класс:

// Файл TestIface.java

class TestIface {

public static void main(String args[]){

Callback c = new Client(); // c – ссылка типа интерфейс, c.callback(42); // ссылается на объект класса, реализующего интерфейс

// c.nonIfaceMeth(); // Нельзя

}

}

Вывод этой программы:

callback вызван с аргументом 42

Обратите внимание, что переменной c, объявленной с типом интерфейса Callback, был назначен экземпляр класса Client. Хотя разрешается использовать с для обращения к методу callback(), она не может обращаться к любым другим членам класса Client. Переменная интерфейсной ссылки обладает знаниями только методов, объявленных в соответствующей интерфейсной декларации. Таким образом, c нельзя использовать для обращения к методу nonIfaceMeth(), т. к. он определен классом Client, а не интерфейсом Callback.

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

Программа 42. Две реализации интерфейса

Добавим в предыдущий проект Progr41_Interface еще два класса. Первый выполняет еще одну реализацию интерфеса Callback:

// Файл AnotherClient.java //Другая реализация Callback.

class AnotherClient implements Callback { // Implement Callback's interface public void callback(int p){

System.out.println("Другая версия callback"); System.out.println("Квадрат p равен " + (p * p));

}

}

Второй класс выполняет тестирование двух реализаций интерфайса:

// Файл TestIface2.java class TestIface2 {

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

c = ob; // с теперь ссылается на объект AnotherClient

c.callback(42);

}

}

Вывод этой программы:

callback вызван с аргументом 42 Другая версия callback Квадрат p равен 1764

Здесь видно, что вызываемая версия callback() определяется типом объекта, к которому обращается c во время выполнения.

Частичные реализации

Если класс включает интерфейс, но полностью не реализует методы, определенные этим интерфейсом, то этот класс должен быть объявлен как abstract (абстрактный). Например:

abstract class Incomplete implements Callback { int a, b;

void show(){

System, out. println (a + “ " + b) ;

}

// ...

}

Здесь класс incomplete не реализует callback() и должен быть объявлен как абстрактный. Любой класс, который наследует Incomplete, должен реализовать callback() или объявить себя как abstract.

8.4. Применения интерфейсов

Чтобы понять мощь интерфейсов, рассмотрим более практический пример. Ранее мы разработали класс с именем stack, который реализовал простой стек фиксированного размера. Однако существуют и другие способы представления стека. Например, стек может иметь фиксированный размер, или быть "растущим". Стек может также содержаться в массиве, связном списке, двоичном дереве и т. д. Независимо от того, как стек реализован, интерфейс стека остается тем же самым. То есть методы push() и pop() определяют интерфейс к стеку независимо от подробностей реализации. Поскольку интерфейс к стеку отделен от его реализации, то легко определить интерфейс стека, оставляя за каждой реализацией определение специфики

Программа 43. Интерфейс стека

Ниже показан интерфейс, определяющий целый стек. Поместим его в файле с именем IntStack.java.

//Файл IntStack.java

// Определение интерфейса целого стека. interface IntStack{

void push (int item);

//

Запомнить элемент

int pop();

//

Извлечь элемент

}

Следующая программа создает класс с именем Fixedstack, который реализует версию целого стека фиксированной длины:

//Файл FixedStack.java

//Реализация IntStack, которая использует фиксированную память. class FixedStack implements IntStack {

private int stck[]; private int tos;

//Выделить память и инициализировать стек

FixedStack(int size){

stck = new int[size]; tos = -1;

}

// Поместить элемент в стек public void push(int item){

if(tos == stck.length - 1) // Использовать переменную length System.out.println("Стек заполнен.");

else

stck[++tos] = item;

}

//Извлечь элемент из стека public int pop(){

if(tos < 0){ System.out.println("Стек пуст.") ; return 0;

}

else

return stck[tos--];

}

}

class IFTest {

public static void main(String args[]){ FixedStack mystack1 = new FixedStack(5); FixedStack mystack2 = new FixedStack(8);

//Поместить в стек несколько чисел for(int i = 0; i < 5; i++)

mystack1.push(i);

for (int i = 0; i < 8; i++) mystack2.push(i);

//Извлечь из стека эти числа

System.out.println("Стек в mystack1:"); for(int i = 0; i < 5; i++)

System.out.println(mystack1.pop()) ; System.out.println("Стек в mystack2:"); for(int i = 0; i < 8; i++)

System.out.println(mystack2.pop());

}

}

При запуске получаем:

Стек в mystack1: 4 3 2 1 0

Стек в mystack2: 7 6 5 4 3 2 1 0

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

//Файл DynStack.java

//Реализует "растущий" стек.

class DynStack implements IntStack { private int stck[];

private int tos;

// Выделить память и инициализировать стек

DynStack(int size){ stck = new int[size]; tos = -1;

}

//Поместить элемент в стек public void push(int item){

//Если стек заполнен, увеличить его размер if (tos == stck.length - 1){

int temp[] = new int[stck.length * 2]; // Двойной размер for(int i = 0; i < stck.length; i++)

temp[i] = stck[i]; stck = temp; stck[++tos] = item;

}

else

stck[++tos] = item;

}

// Извлечь элемент из стека public int pop(){

if(tos < 0){ System.out.println("Стек пуст,"); return 0;

}

else

return stck[tos--];

}

}

class IFTest2 {

public static void main(String args[]){ DynStack mystack1 = new DynStack(5); DynStack mystack2 = new DynStack(8);

//Эти циклы заставляют каждый стек расти for(int i = 0; i < 12; i++)

mystack1.push(i); for(int i = 0; i < 20; i++)

mystack2.push(i); System.out.println("Стек в mystack1:"); for(int i = 0; i < 12; i++)

System.out.print(mystackl.pop() + " "); System.out.println("\nСтек в mystack2:"); for(int i = 0; i < 20; i++)

System.out.println(mystack2.pop() + " ");

}

}

В данной программе, как и в программа 42, есть два класса, включающие метод main(), которые, следовательно, можно направлять на выполнение. В программе 42 классы с методом main() находятся в файлах названия которых (TestIface и TestIface2) совпадают с именами классов (рис. 11), поэтому для их запуска нужно выбрать в окне Package Explorer соответствующий файл и выполнить команду Run, Run или нажать Ctrl + F11.

Рис. 11. Состав проектов

В рассматриваемой программе имена классов с методом main() отличаются от имен соответствующих файлов, поэтому для запуска нужного класса нужно выделить в окне Package Explorer не файл, а сам класс, затем в контекстном меню, вызываемом правой кнопкой мыши,

выбрать команду Run As, Java Application (рис 12).

Рис. 12. Запуск класса из файла с отличающимся именем

При запуске класса IFTest2 получаем вывод:

Стек в mystack1:

11 10 9 8 7 6 5 4 3 2 1 0 Стек в mystack2:

19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

Следующий класс использует обе реализации. DynStack и FixedStack через интерфейсную ссылку. Это означает, что обращение к push() и pop() осуществляется во время выполнения, а нево время компиляции.

// Файл IFTest3.java

/* Создать интерфейсную переменную и обратиться к стекам через нее. */ class IFTest3{

public static void main(String args[]){

IntStack mystack; // Создать ссылочную переменную интерфейса

DynStack ds = new DynStack(5); FixedStack fs = new FixedStack(8);

mystack = ds;

// Загрузить динамический стек

// Поместить несколько чисел в стек

for(int i = 0; i < 12; i++)

mystack.push(i);

 

mystack = fs;

// Загрузить фиксированный стек

for(int i = 0; i < 8;

i++)

mystack.push(i);

 

mystack = ds;

 

System.out.println("Значения динамического стека:"); for(int i = 0; i < 12; i++)

System.out.print(mystack.pop() + " ") ; mystack = fs;

System.out.println("\nЗначения фиксированного стека:"); for(int i = 0; i < 8; i++)

System.out.print(mystack.pop() + " ");

}

}

Программа выводит:

Значения динамического стека: 11 10 9 8 7 6 5 4 3 2 1 0

Значения фиксированного стека: 7 6 5 4 3 2 1 0

В этой программе mystack — ссылка на интерфейс IntStack. Таким образом, когда она обращается к ds, то использует версии push() и pop(), определенные реализацией DynStack. Когда она обращается к fs, то использует версии push() и pop(), определенные в FixedStack. Напомним, что эти определения делаются во время выполнения. Доступ к множественным реализациям интерфейса через интерфейсную ссылочную переменную — это мощное средство, с помощью которого Java достигает полиморфизма времени выполнения.

Переменные в интерфейсах

Можно использовать интерфейсы для импорта разделяемых констант во множественные классы просто объявлением интерфейса, который содержит переменные, инициализированные желательными значениями. Когда этот интерфейс реализуется классом все имена указанных переменных окажутся в области их видимости как константы. Это подобно использованию файла заголовка в C/C++, который создает большое количество констант #defined или const- объявлений. Если интерфейс не содержит методов, то любой класс, который включает такой интерфейс, фактически не реализует ничего. Это выглядит так, как если бы данный класс импортировал постоянные переменные в пространство имен класса как final-переменные. Следующий пример использует эту методику, чтобы реализовать автоматизированное "средство принятия решений".

Программа 44. Переменные в интерфейсах

В этой программе интерфейс и все классы расположены в одном файле.

// Файл AskMe.java import java.util.Random;

interface SharedConstants { int NO=0;

int YES = 1; int MAYBE = 2; int LATER = 3; int SOON = 4; int NEVER = 5;

}

class Question implements SharedConstants { Random rand = new Random();

int ask(){

 

 

int prob =

(int)(100 * rand.nextDouble());

if (prob <

30)

 

return NO;

// 30%

else if (prob < 60)

 

return YES;

// 30%

else if (prob < 75)

 

return MAYBE;

// 15%

else if (prob < 90)

 

return LATER;

// 15%

else if (prob < 98)

 

return SOON;

// 8%

else

 

return NEVER;

// 2%

}

}

class AskMe implements SharedConstants { static void answer(int result){

switch(result){ case NO:

System.out.println("Нет"); break;

case YES: System.out.println("Да");

break; case MAYBE:

System.out.println("Возможно") ; break;

case LATER: System.out.println("Позже");

break; case SOON:

System.out.println("Вскоре"); break;

case NEVER: System.out.println("Никогда");

break;

}

}

public static void main(String args[]){ Question q = new Question(); for(int i = 0; i < 10; i++)

answer(q.ask());

}

}

Компилятор разместит интерфейс и все классы в отдельных файлах

(рис 13).