16 июл. 2015 г.

Интерфейсы. Часть 3 – наследование в интерфейсах.

Ключевое слово extends позволяет одному интерфейсу наследовать другой. Синтаксис определения такого наследования аналогичен синтаксису наследования классов, но в отличие от них интерфейсы могут наследоваться от любого множества интерфейсов, а не от одного как классы.
Интерфейс, который расширяет (наследуется) более одного интерфейса, наследует все методы и константы от каждого родителя и может определять собственные дополнительные абстрактные методы и константы.
Когда класс реализует интерфейс, который наследует другие интерфейсы, он должен предоставлять реализации всех методов, определенных внутри цепочки наследования интерфейса.
Приведем простой пример. На котором, я кстати покажу, что интерфейсы и классы могут содержаться в одном java файле, но в таком случае только один класс может быть public, остальные должны быть с пакетным доступом. Эффект ничем не отличается от создания отдельного java-файла для каждого класса и интерфейса. После компиляции получаются отдельные class файлы для каждого интерфейса и класса.

В этом примере интерфейс В наследуется от интерфейса А, в котором определены два метода с реализациями по умолчанию. В интерфейса В определен один метод без реализации по умолчанию.
Как видим в классе Ext1 мы реализовали только метод methB1(), поскольку для него нет реализации по умолчанию. Другие же методы – methA1() и methA2() были унаследованы в своей реализации по умолчанию.
В классе Ext2 мы реализовали все методы интерфейсов А и В переопределив их по своему.
Внимание стоит обратить на строки 54 и 58, где мы создаем объектные интерфейсные ссылки ext2 и ext3 соответственно. Но ссылка ext3 имеет интерфейсный тип А, именно поэтому на ней нельзя вызывать метод methB2(), хотя он у нас и реализован в классе Ext2.
Вывод у программы следующий:
I0018
GrabliДалее в наследовании интерфейсов начинаются грабельки. А что если в каком либо из родителей и потомков объявлены методы с одинаковой сигнатурой? А что если параметры методов совпадают, но отличается тип возвращаемого значения? И т.д и т.п.
Попробуем со всем этим разобраться… Хотя, возможно, все случаи тут и не перечислю, так как их достаточно много, но основные идеи постараюсь донести. А на остальные случаи грабли вам в помощь :)
  • Если класс реализует интерфейсы в которых есть методы с одинаковой сигнатурой и возвращаемым значением, но сам не переопределяет реализацию этих одинаковых методов, то тогда возникнет ошибка компиляции. Если же он переопределяет эти методы, то тогда все нормально.
  • Если в интерфейсах наследуемых один от другого есть одинаковые методы с реализациями по умолчанию, то предпочтение отдается реализации самого нижнего метода по умолчанию в цепочке, так как действует правило переопределения методов. Но если в классе переопределена реализация этого метода, то предпочтение отдается реализации метода в классе. Это естественное правило. Но благодаря новой форме ключевого слова super, можно обратиться к реализации метода по умолчанию родительского интерфейса из метода интерфейса наследника. Эта форма ключевого слова super выглядит следующим образом:

    имя_интерфейса.super.имя_метода();
    Если, допустим, из метода с реализацией по умолчанию интрефейса В, который унаследовался от интерфейса А, надо обратиться к методу по умолчанию интерфейса А, то это может выглядеть например так:

    A.super
.method();
  • Если наследуемые методы интерфейсов имеют одинаковые параметры, но различный тип возвращаемого значения, то возникает ошибка компиляции.

  • Теперь посмотрим это на примере:

    Программа генерирует следующий вывод:
    I0019
    Первые четыре строки генерируются имплементацией метода по умолчанию meth() интерфейса D, вызванные на объекте класса Dd. Последняя строка выводится имплементацией метода meth() в классе Cb вызванного на объекте этого класса. Опять же, обратите внимание на строки 47 и 50, на то каким образом определены типы ссылок.

    5 комментариев:

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

      ОтветитьУдалить
    2. Да, они там действительно лишние, но на работоспособоность кода это ни как не влияет.

      ОтветитьУдалить
    3. Вот пример №1:

      ```java
      package pro.java.multiext01;

      import static pro.java.util.Print.*;

      interface A {
      default void methA1() {
      println("Method A1");
      }

      default void methA2() {
      println("Method A2");
      }
      }

      interface B extends A {
      void methB1();
      }

      class Ext1 implements B {
      @Override
      public void methB1() {
      print("Метод В1");
      }
      }

      class Ext2 implements B {

      @Override
      public void methA1() {
      println("Метод A1");
      }

      @Override
      public void methA2() {
      println("Метод A2");
      }

      @Override
      public void methB1() {
      print("Метод В1");
      }

      }

      public class MulitEx {

      public static void main(String[] args) {

      Ext1 ext1 = new Ext1();
      ext1.methA1();
      ext1.methA2();
      ext1.methB1();
      printLnLineLn();
      B ext2 = new Ext2(); // внимание сюда
      ext2.methA1();
      ext2.methA2();
      ext2.methB1();
      A ext3 = new Ext2(); // внимание сюда
      printLnLineLn();
      ext3.methA1();
      ext3.methA2();
      // ext3.methB1(); // ОШИБКА
      }

      }

      ```

      ОтветитьУдалить