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.