Default-методы появились Java 8. В это статье рассказывается, что это такое, зачем появилось, и как ими пользоваться.
Default-метод — это метод, который реализуется прямо в интерфейсе, его помечают ключевым словом default.
Пример использования
Допустим, у нас есть интерфейс Animal:
public interface Animal {
String move();
}
Есть классы Cat и Fish, реализующие интерфейс Animal:
public class Cat implements Animal {
@Override
public String move() {
return "run";
}
}
public class Fish implements Animal {
@Override
public String move() {
return "swim";
}
}
Мы хотим добавить в интерфейс Animal метод sleep(), при этом не реализовывать его в каждом классе, а реализовать непосредственно в интерфейсе. Классы же будут наследовать этот метод по умолчанию. Для этого наш метод надо обозначить как default:
public interface Animal {
String move();
default String sleep() {
return ("sleep");
}
}
Теперь этот метод унаследуют все животные:
@Test
public void whenCatSleep_thenOk() {
assertEquals("sleep", cat.sleep());
}
Впрочем, его можно и переопределить в каком-либо из классов, например в Fish:
public class Fish implements Animal {
@Override
public String move() {
return "swim";
}
@Override
public String sleep() {
return "fish sleeps";
}
}
Убедимся, что рыба спит по-своему:
@Test
public void whenFishSleep_thenOnItsOwn() {
assertEquals("fish sleeps", fish.sleep());
}
Как наследуются default-методы
Возникает вопрос, какой метод унаследует класс, реализующий два интерфейса, если оба из них содержат default-методы с одинаковыми именами.
Например, есть второй интерфейс Man, который тоже содержит свой default метод sleep():
public interface Man {
default String sleep() {
return ("man sleeps");
}
}
И есть класс Kentavr, реализующий как интерфейс Man, так и Animal. Какой же метод sleep() унаследует Kentavr?
Чтобы не было неопределенности (и чтобы скомпилировался код), мы обязаны переопределить в Kentavr метод sleep(), причем можно просто вызвать в нем метод sleep() любого из интерфейсов — Man либо Animal, указав через точку и super, чей именно метод нужен:
public class Kentavr implements Man, Animal{
@Override
public String move() {
return "kentavr moves";
}
@Override
public String sleep() {
return Man.super.sleep();
}
}
Убедимся, что кентавр спит по-человечески:
@Test
public void whenKentavrSleep_thenSpecifyWhose() {
assertEquals("man sleeps", kentavr.sleep());
}
Причины появления default-методов
Наверно уже понятно, что default-методы упрощают рефакторинг — а именно, добавление новых методов.
До Java 8 все методы в интерфейсах были абстрактными. К чему это вело?
К тому, что при добавлении нового метода в интерфейс приходилось править все классы, реализующие интерфейс — реализовывать метод в этих классах. Это было неудобно. А в Java 8 (в классы ядра) захотели ввести новые методы в старые интерфейсы. Так что ввели ключевое слово default и эти методы сделали default. Например, в интерфейсе java.lang.Iterable появились новые default-методы forEach() и spliterator():
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
Поскольку огромное число классов реализуют Iterable , без default-методов дополнить этот интерфейс было бы практически невозможно.
Итог
Мы рассмотрели, что такое default метод в Java. Код примеров доступен на GitHub.