Поиск…


Вступление

Метод по умолчанию, введенный в 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 {}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow