Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ПРИС_шпоры.doc
Скачиваний:
3
Добавлен:
01.03.2025
Размер:
3 Мб
Скачать
☆

19. C# и Java. Интерфейсы.

Интерфейс определяет набор методов, которые будут реализованы классом. Сам

интерфейс не реализует методы. Таким образом, интерфейс — это логическая конст-

рукция, которая описывает методы, не устанавливая жестко способ их реализации. В

этой главе рассматриваются еще два типа данных С#: структуры и перечисления.

Структуры подобны классам, за исключением того, что они обрабатываются как типы

значений, а не как ссылочные типы. Перечисления — это списки именованных цело-

численных констант. Структуры и перечисления вносят существенный вклад в общую

копилку средств и инструментов, составляющих среду программирования С#.

Интерфейсы

В объектно-ориентированном программировании иногда требуется определить,

что класс должен делать, а не как он будет это делать. Вы уже видели такой подход на

примере абстрактного метода. Абстрактный метод определяет сигнатуру для метода,

но не обеспечивает его реализации. В производном классе каждый абстрактный ме-

тод, определенный базовым классом, реализуется по-своему. Таким образом, абст-

рактный метод задает интерфейс для метода, но не способ его реализации. Несмотря

на всю полезность абстрактных классов и методов, эту идею можно развить. В С#

предусмотрена возможность полностью отделить интерфейс класса от его реализации

с помощью ключевого слова interface.

Интерфейсы синтаксически подобны абстрактным классам. Однако в интерфейсе

ни один метод не может включать тело, т.е. интерфейс в принципе не предусматрива-

ет какой бы то ни было реализации. Он определяет, что должно быть сделано, но не

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

чество классов. При этом один класс может реализовать любое число интерфейсов.

Для реализации интерфейса класс должен обеспечить теля (способы реализации)

методов, описанных в интерфейсе. Каждый класс может определить собственную реа-

лизацию. Таким образом, два класса могут реализовать один и тот же интерфейс раз-

личными способами, но все классы поддерживают одинаковый набор методов. Следо-

вательно, код, "осведомленный" о наличии интерфейса, может использовать объекты

любого класса, поскольку интерфейс для всех объектов одинаков. Предоставляя про-

граммистам возможность применения такого средства программирования, как интер-

фейс, С# позволяет в полной мере использовать аспект полиморфизма, выражаемый

как "один интерфейс — много методов".

Интерфейсы объявляются с помощью ключевого слова i n t e r f a c e . Вот как выгля-

дит упрощенная форма объявления интерфейса:

i n t e r f a c e имя{

тип_возврата имя_метода1 {список_параметров) ;

тип_возврата имя_метода2 {список_параметров) ;

// . . .

тип_возврата имя_методаЫ(список_параметров) ;

}

Имя интерфейса задается элементом имя. Методы объявляются с использованием

лишь типа возвращаемого ими значения и сигнатуры. Все эти методы, по сути, — аб-

страктные. Как упоминалось выше, для методов в интерфейсе не предусмотрены спо-

собы реализации. Следовательно, каждый класс, который включает интерфейс, дол-

жен реализовать все его методы. В интерфейсе методы неявно являются открытыми

(public-методами), при этом не разрешается явным образом указывать спецификатор

доступа.

Рассмотрим пример интерфейса для класса, который генерирует ряд чисел.

public interface ISeries {

int getNext(); // Возвращает следующее число ряда,

void reset (); // Выполняет перезапуск,

void setStart(int x) ; // Устанавливает начальное

// значение.

Этот интерфейс имеет имя ISeries. Хотя префикс " I " необязателен, • многие

программисты его используют, чтобы отличать интерфейсы от классов. Интерфейс

ISeries объявлен открытым, поэтому он может быть реализован любым классом в

любой программе.

Помимо сигнатур методов интерфейсы могут объявлять сигнатуры свойств, индек-

саторов и событий. События рассматриваются в главе 15, поэтому здесь мы остано-

вимся на методах, индексаторах и свойствах. Интерфейсы не могут иметь членов дан-

ных. Они не могут определять конструкторы, деструкторы или операторные методы.

Кроме того, ни один член интерфейса не может быть объявлен статическим.

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

Итак, если интерфейс определен, один или несколько классов могут его реализо-

вать. Чтобы реализовать интерфейс, нужно указать его имя после имени класса по-

добно тому, как при создании производного указывается базовый класс. Формат запи-

си класса, который реализует интерфейс, таков:

class имя_класса : имя__интерфейса {

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

Нетрудно догадаться, что имя реализуемого интерфейса задается с помощью эле-

мента имя_интерфейса.

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

лизация интерфейса не может быть выполнена частично.

Классы могут реализовать несколько интерфейсов. В этом случае имена интерфей-

сов отделяются запятыми. Класс может наследовать базовый класс и реализовать один

или несколько интерфейсов. В этом случае список интерфейсов должно возглавлять

имя базового класса.

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

в том, что методы внутри интерфейса неявно объявляются открытыми, поэтому их

реализации также должны быть открытыми. Кроме того, сигнатура типа в реализации

метода должна в точности совпадать с сигнатурой типа, заданной в определении ин-

терфейса.

Рассмотрим пример реализации интерфейса ISeries, объявление которого приве-

дено выше. Здесь создается класс с именем ByTwos, генерирующий ряд чисел, в кото-

эом каждое следующее число больше предыдущего на два.

// Реализация интерфейса ISeries,

class ByTwos : ISeries {

int start;

int val;

public ByTwos() {

start = 0;

val = 0;

public int getNext() {

val += 2;

return val;

public void reset() {

val = start;

public void setStart(int x) {

start = x;

val = start;

Как видите, класс ByTwos реализует все три метода, определенные интерфейсом

ISeries. Иначе и быть не может, поскольку классу не разрешается создавать частич-

ную реализацию интерфейса.

Рассмотрим пример, демонстрирующий использование класса ByTwos. Вот его код:

// Демонстрация использования интерфейса,

// реализованного классом ByTwos.

using System;

class SeriesDemo {

public static void Main() {

ByTwos ob = new ByTwos() ;

for(int i=0; i < 5;

Console.WriteLine("Следующее значение равно " +

ob.getNext());

Console.WriteLine("ХпПереход в исходное состояние.")

ob.reset ();

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

Console.WriteLine("Следующее значение равно " +

ob.getNext());

Console.WriteLine("ХпНачинаем с числа 100.");

ob.setStart (100);

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

Console.WriteLine("Следующее значение равно " +"

ob.getNext());

Чтобы скомпилировать программу SeriesDemo, необходимо включить в процесс

компиляции файлы, которые содержат классы ISeries, ByTwos и SeriesDemo. Для

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

файла. Если эти файлы называются, например, ISeries, cs, ByTwos. cs и

SeriesDemo.es, то программа скомпилируется посредством выполнения такой ко-

мандной строки:

I >csc SeriesDemo.cs ISeries.cs ByTwos.cs

В классах, которые реализуют интерфейсы, можно определять дополнительные

члены. Например, в представленную ниже версию класса ByTwos добавлен метод

getPrevious (), который возвращает предыдущее значение ряда.

// Реализация интерфейса ISeries с дополнительно

// определяемым методом getPrevious().

class ByTwos : ISeries {

int start;

int val;

int prev;

public ByTwos() {

start = 0;

val = 0;

prev = -2;

}

public int getNextO {

prev = val;

val += 2;

return val;

}

public void reset() {

val = start;

prev = start - 2;

}

public void setStart(int x) {

start = x;

val = start;

prev = val - 2;

}

// Метод, не объявленный в интерфейсе ISeries,

public int getPrevious() {

return prev;

Обратите внимание на то, что добавление метода getPrevious () потребовало

внесения изменений в реализацию методов, определенных интерфейсом ISeries. Но

поскольку интерфейс для этих методов остается прежним, при изменении не разру-

шается код, написанный ранее. В этом и заключается одно из достоинств использова-

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

Как упоминалось выше, интерфейс может реализовать любое количество классов.

Рассмотрим, например, класс Primes, который генерирует ряд простых чисел. Обра-

тите внимание на то, что его способ реализации интерфейса ISeries в корне отлича-

ется от используемого классом ByTwos.

// Использование интерфейса ISeries для реализации

// ряда простых чисел.

class Primes : ISeries {

int start;

int val;

public Primes () {

start = 2;

val = 2;

}

public int getNext() {

int i, j;

bool isprime;

for(i = val; i < 1000000;

isprime = true;

for(j = 2; j < (i/j + 1)

if((i%j)==0) {

isprime = false;

break;

if(isprime) {

val = i;

break;

return val;

public void reset() {

val = start;

public void setStart(int x) {

start = x;

val = start;

Здесь важно понимать, что, хотя классы Primes и ByTwos генерируют разные ряды

чисел, оба они реализуют один и тот же интерфейс ISeries. И в этом нет ничего

удивительного, поскольку каждый класс волен решить эту задачу так, как "считает"

нужным.

Интерфейсы Java

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

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

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

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

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

9.1 Объявление интерфейса

Объявление интерфейса определяет новый ссылочный тип:

InterfaceDeclaration:

InterfaceModifiersopt interface Identifier

ExtendsInterfacesopt InterfaceBody

 

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

class Point { int x, y; }

interface Point { void move(int dx, int dy); }

ошибка времени компиляции происходит, потому что class и interface в одном и том же пакете не могут иметь одинаковые имена.

9.1.1 Область действия имени интерфейсного типа

Identifier определяет имя интерфейса областью действия которого является весь пакет, в котором он объявлен. Это правило схоже с правилом для имен классового типа; смотрите пример, содержащий классы.

9.1.2 Модификаторы интерфейса

Объявлению интерфейса может предшествовать объявление модификаторов:

InterfaceModifiers:

InterfaceModifier

InterfaceModifiers InterfaceModifier

InterfaceModifier: один из

public abstract

 

Если один и тот же модификатор появляется более одного раза в объявлении интерфейса, то происходит ошибка времени компиляции.

9.1.2.1 Абстрактные интерфейсы

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

9.1.3 Суперинтерфейсы

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

ExtendsInterfaces:

extends InterfaceType

ExtendsInterfaces , InterfaceType

Для большей ясности, следующее повторяется из §4.3:

InterfaceType:

TypeName

Каждый InterfaceType в предложении extends из описания интерфейса должен именовать доступный интерфейсный тип; иначе происходит ошибка времени компиляции.

Ошибка времени компиляции происходит, если имеется рекурсия такая что интерфейс непосредственно или косвенно расширяет себя.

Нет никакого аналога классу Object для интерфейсов; то есть, в то время как каждый класс - расширение класса Object, нет никакого отдельного интерфейса в котором все интерфейсы являются расширениями.

Отношение суперинтерфейса - переходное смыкание отношения прямого суперинтерфейса. Интерфейс K - суперинтерфейс интерфейса I, если следующее является верным:

K- непосредственный суперинтерфейс I.

Cуществует интерфейс J такой, что K является суперинтерфейсом J, и J - суперинтерфейс I, при рекурсивном применении этого определения.

Интерфейс I, как говорят, является подинтерфейсом интерфейса K, когда K - суперинтерфейс I.

9.1.4 Тело интерфейса и объявление членов

Тело интерфейса может объявлять члены интерфейса:

InterfaceBody:

{ InterfaceMemberDeclarationsopt }

InterfaceMemberDeclarations:

InterfaceMemberDeclaration

InterfaceMemberDeclarations InterfaceMemberDeclaration

InterfaceMemberDeclaration:

ConstantDeclaration

AbstractMethodDeclaration

Область действия члена, объявленного в типе интерфейса - все тело объявленного интерфейса.

9.1.5 Доступ к именам членов интерфейса

Все члены интерфейса по умолчанию public. Они доступны вне пакета, где интерфейс объявлен, если интерфейс также объявлен как public и пакет, содержащий интерфейс - доступен, как описывается в .

9.2 Члены интерфейса

Члены интерфейса - это члены, которые унаследованы от прямого суперинтерфейса и члены, которые объявлены в интерфейсе.

Интерфейс наследует из интерфейсов расширения, все члены этих интерфейсов, кроме полей, которые скрываются и методов которые игнорируются.

9.3 Объявление (константного) поля

ConstantDeclaration:

ConstantModifiers Type VariableDeclarator

ConstantModifiers: one of

public static final

Каждое объявление поля в теле интерфейса поумолчанию public, static и final. Это разрешено,но рекомендуется как соответствие со стилем,резервно определять любой или все этимодификаторы для таких полей.

Объявление константы в интерфейсе не должно включать никакой из модификаторов -synchronized, transient, или volatile, иначе происходит ошибка времени компиляции .

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

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

9.3.1 Инициализация полей в интерфейсах

Каждое поле в теле интерфейса должно иметь выражение инициализации, которое не должно быть константой. Инициализатор -переменная определена и присваивание, выполняется только один раз, когда интерфейс - инициализирован (§12.4).

Ошибка времени компиляции происходит, если инициализация выражения поля интерфейса содержит ссылку на простое имя того же поля или другого поля, текстовое объявление которого в том же самом интерфейсе происходит позже. Таким образом:

interface Test {

float f = j;

int j = 1;

int k = k+1;

}

}

происходят две ошибки времени компиляции, потому что j упомянута в   инициализации f прежде j объявлен и потому что инициализация k обращается к k.

(Одна тонкость состоит в том, что, во время выполнения, fields, которые инициализируются постоянными значениями во время компиляции - инициализируются первыми. Это также применяется к полям static final в классах (§8.3.2.1). Это означает, в частности, что эти поля никогда не будут иметь их начальные значения по умолчанию (§4.5.4). Для более углубленного изучения см. § 12.4.2 и § 13.4.8.)

Если ключевое слово this (§ 15.7.2) или ключевое слово super (§ 15.10.2, § 15.11) происходит в инициализации выражения для поля интерфейса, тогда происходит ошибка времени компиляции.

9.4.1 Наследование и замещение

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

Если метод объявлен в интерфейсе замещает объявление метода в другом интерфейсе, произойдет ошибка времени компиляции если методы имеют различные типы возврата или если один имеет тип возврата, а другой - void. Кроме того, объявление метода не должно иметь предложения throws которое конфликтует с любыми из тех методов, которые замещаются; иначе, произойдет ошибка времени компиляции.

Методы замещаются в соответствии с сигнатурами. Если, например, интерфейс объявляет два метода public с одним и тем же именем, и подинтерфейс замещает один из них, то подинтерфейс все еще наследует другой метод.

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

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

Существует несколько путей, которыми одно и тоже объявление метода наследуется от интерфейса. Это не вызывает никаких трудностей и никогда не приводит к ошибке при компиляции.

9.4.2 Перегрузка

Если два метода интерфейса (или оба объявлены в одном и том же интерфейсе, или оба наследованы интерфейсом, или один объявлен и один унаследован) имеют одно и то же имя, но различные сигнатуры, то имя метода, как говорят, является игнорированным. Это не вызывает никаких трудностей и никогда не приводит к ошибке времени компиляции. Типы возвращаемых значений и предложения trows двух методов с одинаковым именем и разными сигнатурами не имеют друг к другу никакого отношения.

9.4.3 Примеры объявления абстрактных методов

Следующие примеры иллюстрируют некоторый (возможно тонкие) пункты относительно объявлений абстрактных методов.

9.4.3.1 Пример: замещения

Методы, объявленные в интерфейсах - abstract и таким образом не содержат реализацию. Относительно того, что может быть выполнено замещением объявления метода, подтверждает сигнатура метода, что должны быть ограничены исключения, которые могли бы быть брошен реализацией метода. Кроме задания сигнатуры в объявлении замещающего метода указываются исключения, которые могут быть сгенерированы реализацией метода. Здесь вариант примера, показанного в §8.4.3.1:

class BufferEmpty extends Exception {

BufferEmpty() { super(); }

BufferEmpty(String s) { super(s); }

}

class BufferError extends Exception {

BufferError() { super(); }

BufferError(String s) { super(s); }

}

public interface Buffer {

char get() throws BufferEmpty, BufferError;

}

public interface InfiniteBuffer extends Buffer {

char get() throws BufferError; // override

}

9.4.3.2 Пример: Перегрузка

В коде примера:

interface PointInterface {

void move(int dx, int dy);

}

interface RealPointInterface extends PointInterface {

void move(float dx, float dy);

void move(double dx, double dy);

}

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