Затем, в фигурных скобках, записываются в любом порядке константы и заголовки методов. Можно сказать, что в интерфейсе все методы абстрактные, но слово abstract писать не надо. Константы всегда статические, но слова static и final указывать не нужно.
Все константы и методы в интерфейсах всегда открыты, не надо даже указывать модификатор public.
Вот какую схему можно предложить для иерархии автомобилей:
interface Automobile{ . . . }
interface Car extends Automobile{ . . . }
interface Truck extends Automobile{ . . . }
interface Pickup extends Car, Truck{ . . . }
Таким образом, интерфейс -- это только набросок, эскиз. В нем указано, что делать, но не указано, как это делать.
Как же использовать интерфейс, если он полностью абстрактен, в нем нет ни одного полного метода?
Использовать нужно не интерфейс, а его реализацию (implementation). Реализация интерфейса -- это класс, в котором расписываются методы одного или нескольких интерфейсов. В заголовке класса после его имени или после имени его суперкласса, если он есть, записывается слово implements и, через запятую, перечисляются имена интерфейсов.
Вот как можно реализовать иерархию автомобилей:
interface Car extends Automobile! . . . }
class Truck implements Automobile! . . . }
class Pickup extends Truck implements Car{ . . . }
или так:
class Pickup implements Car, Truck{ . . . }
Реализация интерфейса может быть неполной, некоторые методы интерфейса расписаны, а другие -- нет. Такая реализация -- абстрактный класс, его обязательно надо пометить модификатором abstract.
Как реализовать в классе Рickup метод f(), описанный и в интерфейсе саг, и в интерфейсе Truck с одинаковой сигнатурой? Ответ простой -- никак. Такую ситуацию нельзя реализовать в классе Pickup. Программу надо спроектировать по-другому.
Итак, интерфейсы позволяют реализовать средствами Java чистое объектно-ориентированное проектирование, не отвлекаясь на вопросы реализации проекта.
Мы можем, приступая к разработке проекта, записать его в виде иерархии интерфейсов, не думая о реализации, а затем построить по этому проекту иерархию классов, учитывая ограничения одиночного наследования и видимости членов классов.
Интересно то, что мы можем создавать ссылки на интерфейсы. Конечно, указывать такая ссылка может только на какую-нибудь реализацию интерфейса. Тем самым мы получаем еще один способ организации полиморфизма.
Листинг П.3 показывает, как можно собрать с помощью интерфейса «хор» домашних животных.
Листинг П.3. Использование интерфейса для организации полиморфизма
interface Voice{
void voice();
}
class Dog implements Voice{
public void voice (){
System.out.println("Gav-gav!");
class Cat implements Voice{
System.out.println("Miaou!");
class Cow implements Voice{
public void voice(){
System.out.println("Mu-u-u!");
public class Chorus{
public static void main(String[] args){
Voiced singer = new Voice[3];
singer[0] = new Dog();
singer[1] = new Cat();
singer[2] = new Cow();
for(int i = 0; i < singer.length; i++)
singer[i].voice();
Здесь используется интерфейс voice .
Что же лучше использовать: абстрактный класс или интерфейс? На этот вопрос нет однозначного ответа.
Создавая абстрактный класс, вы волей-неволей погружаете его в иерархию классов, связанную условиями одиночного наследования и единым предком -- классом Оbject. Пользуясь интерфейсами, вы можете свободно проектировать систему, не задумываясь об этих ограничениях.
С другой стороны, в абстрактных классах можно сразу реализовать часть методов. Реализуя же интерфейсы, вы обречены на переопределение всех методов.
Есть еще одно ограничение: все реализации методов интерфейсов должны быть открытыми, public, поскольку при переопределении можно лишь расширять доступ, а методы интерфейсов всегда открыты.
Вообще же наличие и классов, и интерфейсов дает разработчику богатые возможности проектирования. В нашем примере, вы можете включить в хор любой класс, просто реализовав в нем интерфейс voice.
Наконец, можно использовать интерфейсы просто для определения констант, как показано в листинге П.4.
Листинг П.4. Система управления светофором
interface Lights{
int RED = 0;
int YELLOW = 1;
int GREEN = 2;
int ERROR = -1;
class Timer implements Lights{
private int delay;
private static int light = RED;
Timer(int sec)(delay = 1000 * sec;}
public int shift(){
int count = (light++) % 3;
try{
switch(count){
case RED: Thread.sleep(delay); break;
case YELLOW: Thread.sleep(delay/3); break;
case GREEN: Thread.sleep(delay/2); break;
}catch(Exception e){return ERROR;}
return count;
class TrafficRegulator{
private static Timer t = new Timer(1);
for (int k = 0; k < 10; k++)
switch(t.shift()){
case Lights.RED: System.out.println("Stop!"); break;
case Lights.YELLOW: System.out.println("Wait!"); break;
case Lights.GREEN: System.out.println("Go!"); break;
case Lights.ERROR: System.err.println("Time Error"); break;
default: System.err.println("Unknown light."); return;
Здесь, в интерфейсе Lights, определены константы, общие для всего проекта.
Класс Timer реализует этот интерфейс и использует константы напрямую как свои собственные. Метод shift () этого класса подает сигналы переключения светофору с разной задержкой в зависимости от цвета. Задержку осуществляет метод sleep() класса Thread из стандартной библиотеки, которому передается время задержки в миллисекундах. Этот метод нуждается в обработке исключений try{} catch() {}.
Класс TrafficReguiator не реализует интерфейс Lights и пользуется полными именами Lights.RED и т.д. Это возможно потому, что константы RED, YELLOW и GREEN по умолчанию являются статическими.
Приложение 2. Вложенные классы
В теле класса можно сделать описание другого, вложенного (nested) класса. А во вложенном классе можно снова описать вложенный, внутренний (inner) класс и т. д. Можно ли из вложенного класса обратиться к членам внешнего класса? Можно, для того это все и задумывалось.
· А можно ли в таком случае определить экземпляр вложенного класса, не определяя экземпляры внешнего класса? Нет, нельзя, сначала надо определить хоть один экземпляр внешнего класса, матрешка ведь!
· А если экземпляров внешнего класса несколько, как узнать, с каким экземпляром внешнего класса работает данный экземпляр вложенного класса? Имя экземпляра вложенного класса уточняется именем связанного с ним экземпляра внешнего класса. Более того, при создании вложенного экземпляра операция new тоже уточняется именем внешнего экземпляра.
Все вложенные классы можно разделить на вложенные классы-члены класса (member classes), описанные вне методов, и вложенные локальные классы (local classes), описанные внутри методов и/или блоков. Локальные классы, как и все локальные переменные, не являются членами класса.
Классы-члены могут быть объявлены статическим модификатором static. Поведение статических классов-членов ничем не отличается от поведения обычных классов, отличается только обращение к таким классам. Поэтому они называются вложенными классами верхнего уровня (nestee tep-level classes), хотя статические классы-члены можно вкладывать друг в друга. В них можно объявлять статические члены. Используются они обычно для того, чтобы сгруппировать вспомогательные классы вместе с основным классом.
Все нестатические вложенные классы называются внутренними (inner). В них нельзя объявлять статические члены.
Локальные классы, как и все локальные переменные, известны только в блоке, в котором они определены. Они могут быть безымянными (anonymous classes).
Пример.
Листинг Вложенные классы
class Nested{
static private int pr; // Переменная pr объявленa статической
// чтобы к ней был доступ из статических классов А и АВ
String s = "Member of Nested";
// Вкладываем статический класс.
static class .А{ // Полное имя этого класса -- Nested.A
private int a=pr;
String s = "Member of A";
// Во вложенньм класс А вкладываем еще один статический класс
static class AB{ // Полное имя класса -- Nested.А.АВ
private int ab=pr;
String s = "Member of AB";
//В класс Nested вкладываем нестатический класс
class В{ // Полное имя этого класса -- Nested.В
private int b=pr;
String s = "Member of B";
Страницы: 1, 2, 3, 4