Переменные можно объявлять как объектные ссылки, которые используют тип интерфейса, а не тип класса. При помощи такой переменной можно ссылаться на любой экземпляр любого класса, реализующего объявленный интерфейс.
При вызове метода с помощью одной из таких ссылок выбор нужной версии будет производиться в зависимости от конкретного экземпляра интерфейса, на который выполняется ссылка.
Скачать исходники для статьи можно ниже
Это — одна из главных особенностей интерфейсов.
Поиск выполняемого метода осуществляется динамически во время выполнения, что позволяет создавать классы позже, чем код, который вызывает методы по отношению к этим классам.
Диспетчеризация кода может выполняться с использованием интерфейса без необходимости наличия каких-либо сведений о “вызывающем”.
Этот процесс аналогичен использованию ссылки на суперкласс для доступа к объекту подкласса.
В следующем примере метод callback() вызывается через ссылочную переменную интерфейса.
interface Callback { void callback(int param); } class Client implements Callback { // Реализует интерфейс Callback public void callback(int p) { System.out.println("Метод callback, вызванный со значением " + p) ; } void nonIfaceMeth() { System.out.println("Классы, которые реализуют интерфейсы" + "могут определять также и другие члены."); } }} } class TestIface { public static void main(String args[]) { Callback с = new Client(); с.callback(42); } }
Эта программа создает следующий вывод:
Метод callback, вызванный со значением 42
Обратите внимание на то, что хотя переменная с объявлена с типом интерфейса Callback, ей был присвоен экземпляр класса Client. Хотя переменную с можно использовать для доступа к методу callback(), она не имеет доступа к каким-то другим членам класса Client.
Ссылочная переменная интерфейса располагает только сведениями о тех методах, которые объявлены в ее объявлении interface.
Таким образом, переменная с не может применяться для доступа к методу
nonIfaceMeth(), поскольку она объявлена классом Client, а не интерфейсом Callback.
Хотя приведенный пример формально показывает, как ссылочная переменная
интерфейса может получать доступ к объекту реализации, он не демонстрирует полиморфные возможности такой ссылки.
Чтобы продемонстрировать пример такого применения, вначале создадим вторую реализацию интерфейса Callback.
// Еще одна реализация интерфейса Callback, class AnotherClient implements Callback { // Реализация интерфейса Callback public void callback(int p) { System.out.println("Еще одна версия callback"); System.out.println("p в квадрате равно " + (p * p)); } }
Теперь проверим работу следующего класса.
lass Testlface2 { public static void main(String args[]) { Callback с = new Client(); AnotherClient ob = new AnotherClient(); с.callback(42); с = ob; // теперь с ссылается на объект AnotherClient с.callback(42); } }
Эта программа создает следующий вывод:
callback вызванный со значением 42
Еще одна версия callback
р в квадрате равно 1764
Как видите, вызываемая версия метода callback() определяется типом объекта, на который переменная с ссылается во время выполнения. Представленный пример очень прост, поэтому вскоре мы приведем еще один, более реальный пример.
Частичные реализации
Если класс содержит интерфейс, но не полностью реализует определенные им методы, он должен быть объявлен как abstract (абстрактный).
abstract class Incomplete implements Callback { int a, b; void show() { System.out.println(a + " " + b); } // . . . }
В этом примере класс Incomplete не реализует метод callback() и должен быть объявлен как абстрактный. Любой класс, который наследует класс
Incomplete, должен реализовать метод callback() либо быть также объявлен как abstract.
Использование интерфейсов.
Чтобы возможности интерфейсов были понятны, рассмотрим более реальный
пример.
Стек может также храниться в массиве, связанном списке, бинарном
дереве и т.п. Независимо от реализации стека, его интерфейс остается
неизменным. То есть методы push() и pop() определяют интерфейс стека
независимо от нюансов реализации.
Поскольку интерфейс стека отделен от его реализации, можно без труда определить интерфейс стека, предоставляя реализации определение специфичных особенностей.
Рассмотрим два примера.
Вначале создадим интерфейс, который определяет целочисленный стек.
Поместим его в файл IntStack.java. Этот интерфейс будет использоваться обеими реализациями стека.
// Определение интерфейса целочисленного стека, interface IntStack { void push(int item); // сохранение элемента int pop(); // извлечение элемента }
Следующая программа создает класс FixedStack, который реализует версию
целочисленного стека фиксированной длины.
// Реализация 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) {// использование члена длины стека 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 mystackl = new FixedStack(5); FixedStack mystack2 = new FixedStack(8); // заталкивание чисел в стек for(int i=0; i<5; i++){ mystackl.push(i); } for(int i=0; i<8; i++){ mystack2.push(i); } // выталкивание этих чисел из стека System.out.println("Стек в mystackl:"); for(int i=0; i<5; i++){ System.out.println(mystackl.pop()); } System.out.println("Стек в mystack2:"); for(int i=0; i<8; i++){ System.out.println(mystack2.pop()); } } }
Теперь создадим еще одну реализацию интерфейса IntStack, которая, используя то же самое определение interface, создает динамический стек.
В этой реализации каждый стек создается с начальной длиной. При превышении этой начальной длины размер стека увеличивается. Каждый раз, когда возникает потребность в дополнительном месте, размер стека удваивается.
// Реализация "увеличивающегося" стека, class DynStack implements IntStack { private int stck[]; private int tos; // резервирование и инициализация стека DynStack(int size) { stck = new intEsize]; 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 { static void main(String args[]) { DynStack mystackl = new DynStack(5); DynStack mystack2 = new DynStack(8); // Эти циклы увеличивают размеры каждого из стеков for(int i=0; i<12; i++) mystackl.push(i); for(int i=0; i<2 0; i++) mystack2.push(i); System.out.println("Стек в mystackl:"); for(int i=0; i<12; i++) System.out.println(mystackl.pop()); System.out.println("Стек в mystack2:"); for(int i=0; i<20; i++) System.out.println(mystack2.pop()); } }
Следующий класс использует обе реализации классов FixedStack и DynStack.
Для этого применяется ссылка на интерфейс. Это означает, что поиск версий при обращении к методам push() и pop() осуществляется во время выполнения, а не во время компиляции.
// Создание переменной интерфейса и обращение к стекам через нее. 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.println(mystack.pop()); mystack = fs; System.out.println("Значения в фиксированном стеке:"); for(int i=0; i<8; i++) System.out.printIn(mystack.pop()); } }
В этой программе mystack — ссылка на интерфейс IntStack.
Таким образом, когда она ссылается на переменную ds, программа использует версии методов push() и pop(), определенные реализацией DynStack.
Когда же она ссылается на переменную fs, программа использует версии методов push() и pop(), определенные реализацией FixedStack.
Как уже было сказано, эти решения принимаются во время выполнения. Обращение к нескольким реализациям интерфейса через ссылочную переменную интерфейса — наиболее мощный метод поддержки полиморфизма времени выполнения Java.