Java Language
Методы по умолчанию
Поиск…
Вступление
Метод по умолчанию, введенный в Java 8, позволяет разработчикам добавлять новые методы в интерфейс без нарушения существующих реализаций этого интерфейса. Он обеспечивает гибкость, позволяющую интерфейсу определять реализацию, которая будет использоваться по умолчанию, когда класс, который реализует этот интерфейс, не может обеспечить реализацию этого метода.
Синтаксис
- public default void methodName () {/ * метод body * /}
замечания
Методы по умолчанию
- Может использоваться в интерфейсе, чтобы ввести поведение, не заставляя существующие подклассы реализовывать его.
- Может быть переопределено подклассами или под-интерфейсом.
- Не разрешено переопределять методы в классе java.lang.Object.
- Если класс, реализующий более одного интерфейса, наследует методы по умолчанию с идентичными сигнатурами методов из каждой из intefaces, тогда он должен переопределять и предоставлять свой собственный интерфейс, как если бы они не были методами по умолчанию (как часть разрешения множественного наследования).
- Хотя они предназначены для введения поведения без нарушения существующих реализаций, существующие подклассы со статическим методом с той же сигнатурой метода, что и новый метод по умолчанию, будут по-прежнему нарушаться. Однако это верно даже в случае введения метода экземпляра в суперкласс.
Статические методы
- Может использоваться в интерфейсе, в первую очередь предназначенном для использования в качестве метода утилиты для методов по умолчанию.
- Не может быть переопределен подклассами или под-интерфейсом (скрыт от них). Однако, как и в случае со статическими методами даже сейчас, каждый класс или интерфейс могут иметь свои собственные.
- Не разрешено переопределять методы экземпляра в классе java.lang.Object (как и в случае с подклассами).
Ниже приведена таблица, суммирующая взаимодействие между подклассом и суперклассом.
- | SUPER_CLASS-INSTANCE-METHOD | SUPER_CLASS-STATIC-МЕТОД |
---|---|---|
SUB_CLASS-INSTANCE-METHOD | Переопределение | генерирует-compiletime-ошибку |
SUB_CLASS-STATIC-МЕТОД | генерирует-compiletime-ошибку | шкуры |
Ниже приведена таблица, суммирующая взаимодействие между интерфейсом и классом реализации.
- | ИНТЕРФЕЙС-DEFAULT-МЕТОД | ИНТЕРФЕЙС-STATIC-МЕТОД |
---|---|---|
IMPL_CLASS-INSTANCE-METHOD | Переопределение | шкуры |
IMPL_CLASS-STATIC-МЕТОД | генерирует-compiletime-ошибку | шкуры |
Рекомендации :
- http://www.journaldev.com/2752/java-8-interface-changes-static-method-default-method
- https://docs.oracle.com/javase/tutorial/java/IandI/override.html
Основное использование методов по умолчанию
/**
* Interface with default method
*/
public interface Printable {
default void printString() {
System.out.println( "default implementation" );
}
}
/**
* Class which falls back to default implementation of {@link #printString()}
*/
public class WithDefault
implements Printable
{
}
/**
* Custom implementation of {@link #printString()}
*/
public class OverrideDefault
implements Printable {
@Override
public void printString() {
System.out.println( "overridden implementation" );
}
}
Следующие утверждения
new WithDefault().printString();
new OverrideDefault().printString();
Будет производить этот вывод:
default implementation
overridden implementation
Доступ к другим методам интерфейса в рамках метода по умолчанию
Вы также можете получить доступ к другим методам интерфейса из вашего метода по умолчанию.
public interface Summable {
int getA();
int getB();
default int calculateSum() {
return getA() + getB();
}
}
public class Sum implements Summable {
@Override
public int getA() {
return 1;
}
@Override
public int getB() {
return 2;
}
}
Следующий оператор напечатает 3 :
System.out.println(new Sum().calculateSum());
Методы по умолчанию могут использоваться вместе со статическими методами интерфейса:
public interface Summable {
static int getA() {
return 1;
}
static int getB() {
return 2;
}
default int calculateSum() {
return getA() + getB();
}
}
public class Sum implements Summable {}
Следующее утверждение также напечатает 3:
System.out.println(new Sum().calculateSum());
Доступ к переопределенным методам по умолчанию из класса реализации
В классах super.foo()
будет выглядеть только в суперклассах. Если вы хотите вызвать реализацию по умолчанию из суперинтерфейса, вам нужно квалифицировать super
с именем интерфейса: Fooable.super.foo()
.
public interface Fooable {
default int foo() {return 3;}
}
public class A extends Object implements Fooable {
@Override
public int foo() {
//return super.foo() + 1; //error: no method foo() in java.lang.Object
return Fooable.super.foo() + 1; //okay, returns 4
}
}
Зачем использовать методы по умолчанию?
Простой ответ заключается в том, что он позволяет вам развивать существующий интерфейс без нарушения существующих реализаций.
Например, у вас есть интерфейс Swim
который вы опубликовали 20 лет назад.
public interface Swim {
void backStroke();
}
Мы отлично поработали, наш интерфейс очень популярен, в мире есть много реализаций, и вы не контролируете их исходный код.
public class FooSwimmer implements Swim {
public void backStroke() {
System.out.println("Do backstroke");
}
}
Через 20 лет вы решили добавить новые функции в интерфейс, но похоже, что наш интерфейс заморожен, потому что он нарушит существующие реализации.
К счастью, Java 8 представляет новую функцию, называемую методом по умолчанию.
Теперь мы можем добавить новый метод в интерфейс Swim
.
public interface Swim {
void backStroke();
default void sideStroke() {
System.out.println("Default sidestroke implementation. Can be overridden");
}
}
Теперь все существующие реализации нашего интерфейса могут по-прежнему работать. Но самое главное, они могут реализовать недавно добавленный метод в свое время.
Одна из основных причин этого изменения и одно из его самых больших применений - в структуре Java Collections. Oracle не смог добавить метод foreach
к существующему интерфейсу Iterable, не нарушая при этом весь существующий код, который реализовал Iterable. Добавляя методы по умолчанию, существующая реализация Iterable наследует реализацию по умолчанию.
Класс, абстрактный класс и метод интерфейса
Реализации в классах, включая абстрактные объявления, имеют приоритет над всеми значениями по умолчанию интерфейса.
- Метод абстрактного класса имеет приоритет над методом интерфейса по умолчанию .
public interface Swim {
default void backStroke() {
System.out.println("Swim.backStroke");
}
}
public abstract class AbstractSwimmer implements Swim {
public void backStroke() {
System.out.println("AbstractSwimmer.backStroke");
}
}
public class FooSwimmer extends AbstractSwimmer {
}
Следующее утверждение
new FooSwimmer().backStroke();
Будет производить
AbstractSwimmer.backStroke
- Метод класса имеет приоритет над методом интерфейса по умолчанию
public interface Swim {
default void backStroke() {
System.out.println("Swim.backStroke");
}
}
public abstract class AbstractSwimmer implements Swim {
}
public class FooSwimmer extends AbstractSwimmer {
public void backStroke() {
System.out.println("FooSwimmer.backStroke");
}
}
Следующее утверждение
new FooSwimmer().backStroke();
Будет производить
FooSwimmer.backStroke
Метод множественного наследования по умолчанию метода по умолчанию
Рассмотрим следующий пример:
public interface A {
default void foo() { System.out.println("A.foo"); }
}
public interface B {
default void foo() { System.out.println("B.foo"); }
}
Вот два интерфейса, объявляющих метод foo
default
с той же сигнатурой.
Если вы попытаетесь extend
эти интерфейсы в новом интерфейсе, вам нужно сделать выбор из двух, потому что Java заставляет вас явно разрешить это столкновение.
Во-первых , вы можете объявить метод foo
с той же сигнатурой, что и abstract
, что переопределит поведение A
и B
public interface ABExtendsAbstract extends A, B {
@Override
void foo();
}
И когда вы будете implement
ABExtendsAbstract
в class
вам придется обеспечить реализацию foo
:
public class ABExtendsAbstractImpl implements ABExtendsAbstract {
@Override
public void foo() { System.out.println("ABImpl.foo"); }
}
Или, во- вторых , вы можете обеспечить полностью новую реализацию по default
. Вы также можете повторно использовать код методов A
и B
foo
путем доступа к переопределенным методам по умолчанию из класса реализации .
public interface ABExtends extends A, B {
@Override
default void foo() { System.out.println("ABExtends.foo"); }
}
И когда вы будете implement
ABExtends
в class
вам not
придется выполнять foo
реализацию:
public class ABExtendsImpl implements ABExtends {}