Выбрать главу

class Dog implements Voice{

@Override

public void voice(){

System.out.println("Gav-gav!");

}

}

class Cat implements Voice{

@Override

public void voice(){

System.out.println("Miaou!");

}

}

class Cow implements Voice{

@Override

public void voice(){

System.out.println("Mu-u-u!");

}

} public class Chorus{

public static void main(String[] args){

Voice[] singer = new Voice[3]; singer[0] = new Dog(); singer[1] = new Cat(); singer[2] = new Cow(); for (Voice v: singer) v.voice();

}

}

Здесь используется интерфейс Voice вместо абстрактного класса Pet, описанного в листинге 2.2.

Что же лучше использовать: абстрактный класс или интерфейс? На этот вопрос нет однозначного ответа.

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

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

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

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

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

Листинг 3.4. Система управления светофором

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);

public static void main(String[] args){

System.out.println("Stop!"); break;

System.out.println("Wait!"); break; System.out.println("Walk!"); break; System.err.println("Time Error"); break; System.err.println("Unknown light."); return;

for(int k = 0; k < 10; k++) switch(t.shift()){ case Lights.RED: case Lights.YELLOW: case Lights.GREEN: case Lights.ERROR: default:

}

}

Здесь, в интерфейсе Lights, определены константы, общие для всего проекта.

Класс Timer реализует этот интерфейс и использует константы напрямую как свои собственные. Метод shift() этого класса подает сигналы переключения светофору с разной задержкой в зависимости от цвета. Задержку осуществляет метод sleep () класса Thread из стандартной библиотеки, которому передается время задержки в миллисекундах. Этот метод нуждается в обработке исключений try{}catch(){}, о которой мы будем говорить в главе 21.

Класс TrafficRegulator не реализует интерфейс Lights и пользуется полными именами Lights.RED и т. д. Это возможно потому, что константы RED, YELLOW и GREEN по умолчанию являются статическими.

Перечисления

Просматривая листинг 3.4, вы, наверное, заметили, что создавать интерфейс только для записи констант не совсем удобно. Начиная с версии Java SE 5 для этой цели в язык введены перечисления (enumerations). Создавая перечисление, мы сразу же указываем константы, входящие в него. Вместо интерфейса Lights, описанного в листинге 3.4, можно воспользоваться перечислением, сделав такую запись:

enum Lights{ RED, YELLOW, GREEN, ERROR }

Как видите, запись сильно упростилась. Мы записываем только константы, не указывая их характеристики. Каков же, в таком случае, их тип? У них тип перечисления Lights.

Перечисления в языке Java образуют самостоятельные типы, что указывается словом enum в описании перечисления, но все они неявно наследуют абстрактный класс java.lang.Enum. Это наследование не надо указывать словом extends, как мы обычно делаем, определяя классы. Оно введено только для того, чтобы включить перечисления в иерархию классов Java API. Тем не менее мы можем воспользоваться методами класса Enum для получения некоторых характеристик перечисления, как показано в листинге 3.5.

Листинг 3.5. Общие свойства перечислений

enum Lights { RED, YELLOW, GREEN, ERROR }

public class EnumMethods{

public static void main(String[] args){ for (Lights light: Lights.values()){

System.out.println("Тип: " + light.getDeclaringClass());

System.out.println("4HcnoBoe значение: " + light.ordinal());

}

}

}

Обратите внимание, во-первых, на то, как задается цикл для перебора всех значений перечисления Lights. В заголовке цикла определяется переменная light типа перечисления Lights. Метод values (), имеющийся в каждом перечислении, дает ссылку на его значения. Эти значения получает последовательно, одно за другим, переменная light.