Defaut method в Java

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.

Другие особенности Java 8: Лямбда-выражения

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *