Статьи

Java 8: правила разрешения методов по умолчанию

С введением методов по умолчанию в Java 8 теперь класс может наследовать один и тот же метод из нескольких мест (например, другого класса или интерфейса). Следующие правила могут быть использованы для определения того, какой метод выбран в таких случаях:

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

Давайте рассмотрим несколько примеров и применим эти правила.

Пример 1:

Что печатает следующий код?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface A {
  default void name() {
    System.out.println("A");
  }
}
 
public interface B {
  default void name() {
    System.out.println("B");
  }
}
 
public class C implements A {
  @Override
  public void name() {
    System.out.println("C");
  }
}
 
public class D extends C implements A, B {
  public static void main(final String... args) {
    new D().name();
  }
}

Ответ : C

Это связано с тем, что, как указано в правиле 1, объявление метода name() из суперкласса C имеет приоритет над объявлениями методов по умолчанию в A и B

Пример 2:

Что печатает следующий код?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public interface A {
  default void name() {
    System.out.println("A");
  }
}
 
public interface B extends A {
  @Override
  default void name() {
    System.out.println("B");
  }
}
 
public class C implements A {}
 
public class D extends C implements A, B {
  public static void main(final String... args) {
    new D().name();
  }
}

Ответ : Б

В отличие от предыдущего примера, C не переопределяет name() , но поскольку он реализует A , он имеет метод по умолчанию из A Согласно правилу 2, если в классе или суперклассе нет методов, выбирается наиболее конкретный интерфейс, предоставляющий по умолчанию. Поскольку B расширяет A , он более конкретен и, как результат, печатается «B».

Пример 3:

Что печатает следующий код?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public interface A {
  default void name() {
    System.out.println("A");
  }
}
 
public interface B {
  default void name() {
    System.out.println("B");
  }
}
 
public class D implements A, B {
  public static void main(final String... args) {
    new D().name();
  }
}

Ответ : Ошибка компилятора! Duplicate default methods named name with the parameters () and () are inherited from the types B and A

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

1
2
3
4
5
6
class D implements A, B {
  @Override
  public void name() {
    B.super.name();
  }
}

Пример 4:

Что печатает следующий код?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public interface A {
  default void name() {
    System.out.println("A");
  }
}
 
public interface B extends A {}
 
public interface C extends A {}
 
public class D implements B, C {
  public static void main(final String... args) {
    new D().name();
  }
}

Ответ : А

Подинтерфейсы B и C не переопределяют метод, поэтому на самом деле есть только метод из A на выбор. В качестве примечания: если бы B или C (но не оба) переопределили метод, то применимо правило 2. Кстати, это алмазная проблема .

Ссылка: Java 8: Правила разрешения методов по умолчанию от нашего партнера по JCG Фахда Шарифа в блоге fahd.blog .