
- •Использование абстрактных классов
- •// Простая демонстрация абстракций Java.
- •Использование final для отказа от переопределения
- •Void meth() { // ошибка! Нельзя переопределять.
- •Использование final для отмены наследования
- •Интерфейсы
- •IntStack mystack; //создать ссылочную переменную
- •Интерфейсы и обратные вызовы (Хорстманн стр. 253 (279-new))
- •Внутренние классы (Хорстманн стр. 282-new)
Интерфейсы
Для того чтобы освободить интерфейс класса от его реализации, иметь возможность динамического вызова методов во время выполнения, исключив при этом определение метода или набора методов из иерархии наследования, в Java применяются интерфейсы. Используя ключевое слово interface, вы можете указать, что класс должен делать, но не как он это делает. Интерфейсы синтаксически подобны классам, но в них нет экземплярных переменных, и их методы объявляются без тела. Практически, это означает, что вы можете определять интерфейсы, которые не делают предположений относительно того, как они реализованы. Как только интерфейс определен, то реализовать его может любое число классов. И наоборот, один класс может реализовать любое число интерфейсов.
Для реализации интерфейса класс должен создать полный набор методов, определенных интерфейсом. Однако каждый класс может сам определять детали своей реализации.
Интерфейсы разработаны для поддержки динамического вызова методов во время выполнения. Они исключают определение метода или набора методов из иерархии наследования. Ключевое слово interface позволяет полностью использовать аспект полиморфизма, декларируемый как "один интерфейс, множественные методы".
Определение интерфейса
Определение интерфейса во многом подобно определению класса. Общая форма интерфейса выглядит так:
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 (это означает, что они не могут быть изменены реализующим классом), а также должны быть инициализированы постоянными значениями.
Пример определения интерфейса (Папка 3\Интерфейсы по Шилдту):
interface IntStack {
void push(int item); // запомнить элемент
int pop(); // извлечь элемент
}
Реализация интерфейсов
Когда интерфейс определен, он может реализовываться одним или несколькими классами. Для реализации интерфейса в определение класса включают предложение с ключевым словом implements и затем создают методы, определенные в интерфейсе. Общая форма класса, который включает implements предложение, выглядит так:
access class classname [extends superclass]
[implements interface [,interface...] ] {
// тело-класса
}
Здесь access — спецификатор доступа (public или не используется). Если класс реализует более одного интерфейса, они разделяются запятой.
Например (Папка 3\Интерфейсы по Шилдту):
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--]; }
}
При реализации метода интерфейса он должен быть объявлен как public.
Для классов, которые реализуют интерфейсы, допустимо определять дополнительные собственные члены.
Реализации доступа через интерфейсные ссылки
Можно объявлять переменные как объектные ссылки, которые используют интерфейсный тип, а не тип класса. В такой переменной можно сохранять всякий экземпляр любого класса, который реализует объявленный интерфейс. Когда вы вызываете метод через ссылку такого рода, будет вызываться его правильная версия, основанная на актуальном экземпляре интерфейса. Выполняемый метод отыскивается динамически (во время выполнения), что позволяет создавать классы позже кода, который вызывает их методы. Кодом вызова можно управлять через интерфейс, ничего не зная об объекте вызова. Этот процесс подобен использованию ссылки суперкласса для доступа к объекту подкласса, но без использования механизма наследования.
Частичные реализации
Если класс включает интерфейс, но полностью не реализует методы, определенные этим интерфейсом, то этот класс должен быть объявлен как abstract (абстрактный).
Любой класс, который наследует такой класс, должен реализовать все методы, или объявить себя как abstract.
Применения интерфейсов
Рассмотрим пример. Ранее был представлен класс с именем Stack, который реализовал простой стек фиксированного размера. Однако существуют и другие способы. Например, стек может быть "растущим". Стек может также содержаться в массиве, связном списке, двоичном дереве и т. д. Независимо от того, как стек реализован, интерфейс стека остается тем же самым. То есть методы push() и pop() определяют интерфейс к стеку независимо от подробностей реализации. Поскольку интерфейс к стеку отделен от его реализации, то легко определить интерфейс стека, оставляя за каждой реализацией определение специфики. Рассмотрим два примера.
Ниже показан интерфейс, определяющий целый стек. Поместим его в файле с именем IntStack.java. Этот интерфейс будет использоваться обеими реализациями стека.
// Определение интерфейса целого стека.
interface IntStack {
void push(int item); // запомнить элемент
int pop(); // извлечь элемент
}
Следующая программа создает класс с именем FixedStack, который реализует версию целого стека фиксированной длины.
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--]; }
}
Код представленный ниже демонстрирует использование этого класса
public 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());
}
}
Далее следует другая реализация стека, которая создает динамический стек, используя то же самое его определение. В ней каждый стек создается с некоторой исходной длиной. Если эта исходная длина превышена, то стек увеличивается в размере. Каждый раз, когда необходим больший участок памяти, размер стека удваивается.
// Реализует "растущий" стек.
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--]; }
}
Код представленный ниже демонстрирует использование этого класса
сlass IFTest2 {
public 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<20; 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() ) ; }
}
Следующий класс использует oбe реализации DynStack и FixedStack через интерфейсную ссылку. Это означает, что обращение к push() и pop() осуществляется во время выполнения, а не во время компиляции.
/* Создать интерфейсную переменную
и обратиться к стекам через нее. */
class IFTest3 {
public static void main(String args []) {