Интерфейсы
и внутренние
классы
предоставляют
более
изощренные
пути для
организации
и контроля
над
объектами в
вашей системе.
Ключевом
слове abstract,
которое
позволяет
вам
создавать
один или несколько
методов в
классе,
которые не
имеют
определений,
Вы
предоставляете
только
интерфейс, а
его реализация
будет
осуществлена
уже в наследниках.
Ключевое
слово interface
создает полностью
абстрактный
класс,
который не
предоставляет
никаких
реализаций
ни одного
своего
компонента.
Вы
далее
узнаете, что interface
есть нечто
большее, чем
просто
абстрактный
класс,
доведенный
до конца
абстракции,
поскольку он позволяет
вам
создавать
вариации C++
множественного
наследования,
посредством
создания
класса,
который может
быть
приведен к
базовому
типу больше
раз, чем к один.
Ключевое
слово interface
осуществляет,
на шаг
дальше,
концепцию,
реализованную
в abstract. Вы
можете
думать, что
это просто
чисто abstract класс.
Он позволяет
создателю
заложить форму
(структуру)
класса: имена
методов,
списки
аргументов, возвращаемые
типы, но
только не
тела методов.
Interface также
может
содержать
поля, но все
они будут,
хотя и
косвенно static
и final.
Interface
предоставляет
только форму,
образ, но не
предоставляет
его реализацию.
Interface
"говорит":
"Все классы,
реализующие
этот особый
интерфейс
будут
выглядеть
одинаково".
Поэтому,
любой код,
использующий
interface знает,
какой из
методов
может быть
вызван для
этого interface,
впрочем, это
все. Так что interface
используется
в качестве
установления
"протокола"
между классами.
(Некоторые
ООЯ имеют
даже
встроенное
ключевое
слово protocol,
делающее то
же самое
действие.)
Что
бы создать interface,
используйте
ключевое
слово interface
вместо
ключевого
слова class.
Как
и у класса, Вы
можете
добавить
ключевое слово
public до interface (но
только если
этот
интерфейс
определен в файле
с тем же
именем) или
оставить его
пустым, тогда
он станет "friendly" и его
можно будет
использовать
только членам
одного с ним
пакета.
Для
создания
класса
согласованного
с особенным interface
(или группой interface-ов)
используйте
ключевое
слово implements.
Тем самым Вы
объявляете "Interface
это на что
похож мой
класс, а
теперь я
скажу, как он
должен
работать."
Все
остальное,
кроме этого,
выглядит, как
наследование.
Диаграмма
для примера с
инструментами:
interface Instrument {
int i = 5; // public & static & final
// Не
могут быть
получены
определения
методов:
void play(); //
автоматически
public & abstact
String what();
void adjust();
}
class Wind implements
Instrument {
public void play() {
System.out.println("Wind.play()");
}
public String what() { return
"Wind"; }
public void adjust() {}
}
Interface
это не просто
более чистая
форма
абстрактного
класса. Он
имее более
"высокое"
применение.
Поскольку
interface не имеет
реализации
всего, что
есть в нем, то
нет и
массива-хранилища
связанного с
ним, нет
ничего
мешающего
для
комбинации
нескольких
интерфейсов.
И это ценно,
поскольку
иногда вам
требуется
нечто:
"x есть a и b и c."
В
C++, этот акт
множественных
интерфейсов
называется множественное
наследование,
и при этом
этот тип
тянет за
собой
"прилипший"
багаж,
поскольку
каждый тип
имеет свою реализацию.
В
Java Вы можете
осуществить
то же самое,
но только
один из этих
классов
может иметь
реализацию,
так что
проблемы,
возникающие
в C++, не возникают
в Java, при
комбинировании
множества интерфейсов:
В
дочерних
классах, Вы
не можете
насильно получить
доступ к
базовому
классу,
поскольку он
так же
абстрактен и
монолитен -
нерушим (один
без
абстрактных
методов).
Если
Вы
наследуете
не от
интерфейса, Вы
можете
наследовать
только от
него одного.
Все
остальные из
элементов
базового класса
должны быть
интерфейсами.
Вы
помещаете
имена всех
интерфейсов
после
ключевого
слова implements и
разделяете
их при помощи
запятых. Вы
можете
использовать
столько
интерфейсов,
сколько
хотите, каждый
из них
становится
независимым
типом, к
которому Вы в
последствии
можете
привести.
Следующий
пример
демонстрирует
комбинирование
класса с
несколькими
интерфейсами
для создания
нового
класса:
//: c08:Adventure.java
//
Множество
интерфейсов.
interface CanFight {
void fight();
}
interface CanSwim {
void swim();
}
interface CanFly {
void fly();
}
class ActionCharacter {
public void fight() {}
}
class Hero extends
ActionCharacter
implements
CanFight, CanSwim, CanFly {
public void swim() {}
public void fly() {}
}
public class Adventure {
static void t(CanFight
x) { x.fight(); }
static void u(CanSwim
x) { x.swim(); }
static void v(CanFly x)
{ x.fly(); }
static void w(ActionCharacter
x) { x.fight(); }
public static void
main(String[] args) {
Hero h = new Hero();
t(h); // Treat - CanFight
u(h); // Treat - CanSwim
v(h); // Treat - CanFly
w(h); // Treat - ActionCharacter
}
} ///:~
Запомните
пожалуйста
причину
использования
интерфейсов,
кратко ее
можно
изложить так:
возможность
приведения к
более чем
одному
базовому
типу.
В
дополнение
вторая
причина для
использования
интерфейсов
в том же, в чем
и причина использования
абстрактных
базовых классов:
предотвращение
создания
объектов
этого класса
программистами
и понимания,
что это всего
лишь
интерфейс.
Вы
можете с
легкостью
добавлять
объявления
методов, в интерфейсы,
используя
наследование,
а так же Вы
можете комбинировать
несколько
интерфейсов
в один новый
интерфес с
наследованием.
В обоих случаях
Вы получите
новый
интерфейс ,
как видно из
примера ниже:
//: c08:HorrorShow.java
//
Расширение
интерфейса с
наследованием.
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
class DragonZilla implements
DangerousMonster {
public void menace() {}
public void destroy()
{}
}
interface Vampire
extends DangerousMonster, Lethal {
void drinkBlood();
}
class HorrorShow {
static void u(Monster
b) { b.menace(); }
static void
v(DangerousMonster d) {
d.menace();
d.destroy();
}
public static void
main(String[] args) {
DragonZilla
if2 = new DragonZilla();
u(if2);
v(if2);
}
} ///:~
Вы
можете с
легкостью
добавлять
объявления
методов, в интерфейсы,
используя
наследование,
а так же Вы
можете комбинировать
несколько
интерфейсов
в один новый
интерфес с
наследованием.
В обоих
случаях Вы
получите
новый
интерфейс ,
как видно из
примера ниже:
//: c08:HorrorShow.java
//
Расширение
интерфейса с
наследованием.
interface Monster {
void menace();
}
interface DangerousMonster extends Monster {
void destroy();
}
interface Lethal {
void kill();
}
class DragonZilla implements
DangerousMonster {
public void menace() {}
public void destroy()
{}
}
interface Vampire
extends DangerousMonster, Lethal {
void drinkBlood();
}
class HorrorShow {
static void u(Monster
b) { b.menace(); }
static void
v(DangerousMonster d) {
d.menace();
d.destroy();
}
public static void
main(String[] args) {
DragonZilla
if2 = new DragonZilla();
u(if2);
v(if2);
}
} ///:~
Начиная
с версии 1.5, в Java
появились
два
интерфейса java.lang.Comparable и java.util.Comparator.
Объекты
классов,
реализующие
один из этих интерфейсов,
могут быть
упорядоченными.
Интерфейс
Comparable
В
интерфейсе Comparable
объявлен
всего один
метод compareTo(Object
obj),
предназначенный
для
реализации
упорядочивания
объектов
класса. Его
удобно использовать
при
сортировке
упорядоченных
списков или
массивов
объектов.
Данный
метод
сравнивает
вызываемый
объект с obj. В
отличие от
метода equals, который
возвращает true или false, compareTo
возвращает:
·
0,
если
значения
равны;
·
Отрицательное
значение,
если
вызываемый объект
меньше
параметра;
·
Положительное
значение , если
вызываемый
объект
больше
параметра.
Метод
может
выбросить
исключение ClassCastException,
если типы
объектов не
совместимы
при сравнении.
Необходимо
помнить, что
аргумент
метода compareTo имеет
тип
сравниваемого
объекта
класса.
Классы Byte, Short, Integer, Long, Double,
Float, Character, String уже
реализуют
интерфейс Comparable.
Давайте
приведем
пример
реализующий
интерфейс.
import java.util.TreeSet;
class Comp implements Comparable {
String str;
int number;
Comp(String str, int number) {
this.str = str;
this.number = number;
}
@Override
public int compareTo(Object obj) {
Comp entry = (Comp) obj;
int result = str.compareTo(entry.str);
if(result != 0) {
return result;
}
result = number - entry.number;
if(result != 0) {
return (int) result / Math.abs(
result );
}
return 0;
}
}
public class Example {
public static void main(String[]
args) {
TreeSet<Comp> ex = new TreeSet<Comp>();
ex.add(new Comp("Stive Global", 121));
ex.add(new Comp("Stive Global",
221));
ex.add(new Comp("Nancy Summer",
3213));
ex.add(new Comp("Aaron Eagle", 3123));
ex.add(new Comp("Barbara Smith",
88786));
for(Comp e : ex) {
System.out.println("Str: " + e.str +
", number: " + e.number);
}
}
}
/*результат:
* Str: Aaron Eagle, number: 3123
* Str: Barbara Smith, number:
88786
* Str: Nancy Summer, number:
3213
* Str: Stive Global, number: 121
* Str: Stive
Global, number: 221
*/
В
данном
примере,
значения
сортируются
сначала по
полю str,
а затем по number. Это
хорошо видно
по двум
последним
строкам
результата.
Интерфейс
Comparator
В
интерфейсе Comparator объявлено
два метода compare(Object obj1, Object obj2) и equals(Object obj).
compare(Object obj1, Object obj2) – так
же, как
и метод compareTo интерфейса Comparable, упорядочивает
объекты
класса. Точно так
же на выходе
получает 0,
положительное
значение и
отрицательное
значение.
Метод
может
выбросить
исключение ClassCastException,
если типы
объектов не
совместимы
при сравнении.
Основным
отличием
интерфейса Comparator от Comparable является
то, что вы
можете
создавать
несколько
видов независимых
сортировок.
Создадим
класс Product с полями name, price и quantity. В
данном
классе будет
реализовано
два вида
сортировки:
по названию и
по цене.
package my.value;
import
java.util.Arrays;
import java.util.Comparator;
//
создадим
простой
объект, в
котором
будем хранить
данные
class Product {
private String name;
private double price;
private int quantity;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double
price) {
this.price = price;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int
quantity) {
this.quantity = quantity;
}
}
//
теперь
собственно
реализуем
интерфейс Comparator, для
сортировки
по названию
class
SortedByName implements Comparator<Product> {
public int compare(Product obj1, Product
obj2) {
String str1 = obj1.getName();
String str2 = obj2.getName();
return str1.compareTo(str2);
}
}
// а так
же реализуем
интерфейс Comparator, для
сортировки
по цене
class
SortedByPrice implements Comparator<Product> {
public int compare(Product obj1, Product
obj2) {
double price1 = obj1.getPrice();
double price2 = obj2.getPrice();
if(price1 > price2) {
return 1;
}
else if(price1 < price2) {
return -1;
}
else
{
return 0;
}
}
}
// ну и
собственно
работа с
нашими
данными
public class
Example {
public static void main(String[]
args) {
Product[] p = new Product[3];
// заполним
объект Product содержимым
p[0] = new Product();
p[0].setName("Milk");
p[0].setPrice(7.56);
p[0].setQuantity(56);
p[1] = new Product();
p[1].setName("Coffee");
p[1].setPrice(17.00);
p[1].setQuantity(32);
p[2] = new Product();
p[2].setName("Tea");
p[2].setPrice(12.50);
p[2].setQuantity(0);
// выведем
данные без
сортировки
System.out.println("============ no sorted
==============");
for(Product i : p) {
System.out.println("Name: " +
i.getName() +
" price: " + i.getPrice() +
" quantity: "
+ i.getQuantity());
}
//
отсортируем
и выведем
данные по цене
System.out.println("==========
sorted by price===========");
Arrays.sort(p, new SortedByPrice());
for(Product i : p) {
System.out.println("Name: " +
i.getName() +
" price: " + i.getPrice() +
" quantity: "
+ i.getQuantity());
}
//
отсортируем
и выведем
данные по
названию
System.out.println("==========
sorted by name ===========");
Arrays.sort(p, new SortedByName());
for(Product i : p) {
System.out.println("Name: " +
i.getName() +
" price: " + i.getPrice() +
" quantity: "
+ i.getQuantity());
}
}
}
/*
результат:
============
no sorted ==============
Name:
Milk price: 7.56 quantity: 56
Name:
Coffee price: 17.0 quantity: 32
Name:
Tea price: 12.5 quantity: 0
==========
sorted by price===========
Name:
Milk price: 7.56 quantity: 56
Name:
Tea price: 12.5 quantity: 0
Name:
Coffee price: 17.0 quantity: 32
==========
sorted by name ===========
Name:
Coffee price: 17.0 quantity: 32
Name:
Milk price: 7.56 quantity: 56
Name: Tea price: 12.5 quantity: 0
*/
В
чем
преимущество
этого
примера? Как
вы, возможно,
заметили, мы
реализовали
два независимых
компаратора -SortedByName и SortedByPrice.
Сортировку
мы
производим с
помощью
класса Arrays, у
которого
есть метод sort.
Данный метод
в качестве
второго
аргумента
принимает
тип
компаратора.
Arrays.sort(T[] arg1, Comparator<? super
T> arg2);
Так
же можно
использовать
метод sort класса Collections, но
он в качестве
первого
входного
аргумента
принимает
список:
Collections.sort(List<T>
arg1, Comparator<? super T> arg2);
В
Java есть
возможность
поместить
определение
одного
класса
внутри
определения
другого
класса. Такое
помещение
называется внутренний
класс.
Внутренний
класс
позволяет
вам
группировать
классы
вместе,
которые
логично было
бы разместить
в одном месте
и при этом
ими легко
управлять визуально.
Однако важно
понять, чем
внутренний
класс
отличается
от
композиции.
Зачастую,
пока Вы
узнаете о
внутренних
классах, Вы
задаетесь
вопросом о
целесообразности
их
применения, а
иногда, что
их применение
даже вовсе и
не нужно. В
конце же этой
секции, после
того, как
будет описан
весь
синтаксис и семантика
внутренних
классов, Вы
найдете несколько
примеров,
которые
прояснят все преимущества
от
внутренних
классов.
Для
создания
внутреннего
класса, как
может быть Вы,
и ожидали,
нужно
поместить
его описание внутри
класса:
//: c08:Parcel1.java
//
Создание
внутреннего
класса.
public class Parcel1 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label =
whereTo;
}
String
readLabel() { return label; }
}
//
Использование
внутреннего
класса
//
похоже на
использование
обычного
класса:
public void ship(String
dest) {
Contents c =
new Contents();
Destination
d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void
main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("Tanzania");
}
} ///:~
Внутренний
класс,
использованный
внутри ship( ),
выглядит так
же, как и
любой другой
класс. И только
одно
различие
бросается в
глаза, это то,
что его имя
расположено
после Parcel1. Но в
дальнейшем
Вы увидите,
что это
далеко не
единственное
различие.
Более
типичный
случай -
внешний
класс имеет
метод,
который
возвращает
ссылку на
внутренний
класс,
например,
так:
//: c08:Parcel2.java
//
Возвращение
ссылки на
внутренний
класс.
public class Parcel2 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label =
whereTo;
}
String
readLabel() { return label; }
}
public Destination to(String s) {
return new
Destination(s);
}
public Contents cont() {
return new Contents();
}
public void ship(String
dest) {
Contents c =
cont();
Destination
d = to(dest);
System.out.println(d.readLabel());
}
public static void
main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tanzania");
Parcel2 q = new
Parcel2();
//
Определение
ссылки на
внутренний
класс:
Parcel2.Contents c = q.cont();
Parcel2.Destination d = q.to("Borneo");
}
} ///:~
Если
Вы хотите
создать
объект
внутреннего
класса, где
либо еще,
кроме не
статического
метода
внешнего
класса, то Вы
должны
определить
тип этого
объекта, как OuterClassName.InnerClassName,
как,
например, в main( ).
Следующий
пример
несколько
странен:
//: c08:Parcel6.java
// Метод
возвращающий
анонимный
внутренний
класс.
public class Parcel6 {
public Contents cont() {
return new Contents()
{
private int i = 11;
public int value() { return i; }
}; // В этом
случае
требуется
точка с
запятой
}
public static void
main(String[] args) {
Parcel6 p = new Parcel6();
Contents c =
p.cont();
}
} ///:~
Метод
cont( )
комбинирует
создание
возвращаемого
значения с
описанием
класса,
который и
есть это
возвращаемое
значение! В
дополнение
этот класс
еще и не
имеет своего
имени. Делая
тему обсуждения
немного
запутанной,
он выглядит
как будто Вы
начинаете
создавать
объект Contents:
return new Contents()
Но
затем, до
того, как Вы
поставите
точку запятую,
Вы заявляете:
"Но
подождите, я
думаю, я
описался в
определении
класса":
return new Contents()
{
private int i = 11;
public int value() { return i; }
};
А
вот, что
означает
этот
синтаксис:
"Создание
объекта
анонимного
класса,
который наследует
от Contents."
Ссылка,
возвращаемая
выражением new,
автоматически
приводится к
базовому типу,
к ссылке Contents.
Синтаксис
анонимного
внутреннего
класса -
короткая
запись
следующего
кода:
class MyContents implements
Contents {
private int i = 11;
public int value() { return i; }
}
return new
MyContents();
В
анонимном
внутреннем
классе, Contents
создается при
помощи
конструктора
по умолчанию.
До
этого
момента Вы
видели
множество
примеров
синтаксиса и
семантического
описания
работы
внутренних
классов, но
еще не было
ответа на
вопрос "А
зачем они
собственно?".
Зачем Sun пошел
на такие
значительные
усилия, что
бы добавить
эту возможность
языка?
Обычно
внутренний
класс или
наследует от класса
или
реализует
интерфейс, а
код в нем манипулирует
объектами
внешнего
класса, того,
в котором он
создан. Так
что Вы можете
предположить,
что внутренние
классы
предоставляют
этакую
разновидность
окна во
внешнем
классе.
Вопрос,
глубоко
задевающий
внутренние
классы: если
мне нужна
ссылка на
интерфейс, почему
я должен
реализовывать
его во внешнем
классе? А вот
и ответ: "Если
это все , что
нужно, то как
этого
добиться?"
Так где
разница,
между реализацией
интерфейса
внутренним и
внешним классами?
А ответ такой
- Вы не всегда
можете
удобно
работать с
интерфейсами,
иногда нужно
работать и с
реализацией.
Отсюда самая
непреодолимая
причина
работы с
классами:
Каждый
внутренний
класс может
быть независимо
наследован
от
реализации.
Поэтому, внутренний
класс не
ограничен
тем, если
внешний
класс уже
наследовал
от
реализации.
Без
способности
внутренних
классов наследовать
более, чем от
одного
конкретного
или
абстрактного
класса,
некоторые
проекты
столкнулись
бы с
трудноразрешимыми
проблемам.
Так
что
единственное
решение для
внутренних
классов, это
проблема
множественного
наследования.
То есть,
внутренние
классы эффективно
позволяют
вам
наследовать
больше чем от
одного не
интерфейса.