Интерфейсы и внутренние классы

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

Ключевом слове 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 это на что похож мой класс, а теперь я скажу, как он должен работать."

Все остальное, кроме этого, выглядит, как наследование. Диаграмма для примера с инструментами:

TIJ223

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() {}

}

 

Множественное наследование в Java

Interface это не просто более чистая форма абстрактного класса. Он имее более "высокое" применение.

Поскольку interface не имеет реализации всего, что есть в нем, то нет и массива-хранилища связанного с ним, нет ничего мешающего для комбинации нескольких интерфейсов. И это ценно, поскольку иногда вам требуется нечто:

"x есть a и b и c."

В C++, этот акт множественных интерфейсов называется множественное наследование, и при этом этот тип тянет за собой "прилипший" багаж, поскольку каждый тип имеет свою реализацию.

В Java Вы можете осуществить то же самое, но только один из этих классов может иметь реализацию, так что проблемы, возникающие в C++, не возникают в Java, при комбинировании множества интерфейсов:

 

TIJ224

В дочерних классах, Вы не можете насильно получить доступ к базовому классу, поскольку он так же абстрактен и монолитен - нерушим (один без абстрактных методов).

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

Вы помещаете имена всех интерфейсов после ключевого слова 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);

  }

} ///:~

 

Сортировка и упорядочивание. Интерфейсы Comparable и Comparator

 

Начиная с версии 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 пошел на такие значительные усилия, что бы добавить эту возможность языка?

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

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

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

Без способности внутренних классов наследовать более, чем от одного конкретного или абстрактного класса, некоторые проекты столкнулись бы с трудноразрешимыми проблемам.

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