Java 8: стандартные и статические методы интерфейса - похожи, но разные!
Итак, Java 8 официально отсутствует уже почти 4 года, и вместе с ней появилось множество интересных вещей. Некоторые из них были усовершенствованы в языке, например, сортировка параллельных массивов и более эффективное разрешение конфликтов в HashMaps. Новые функции включают в себя столь популярные Lambdas и Method Parameter Reflection (для тех, кто плохо работает). В частности, одной новой функцией является возможность иметь методы интерфейса по умолчанию и статические методы интерфейса (методы статического интерфейса фактически были включены в предыдущие версии Java, но мы включим их для сравнения) . Эта статья призвана объяснить, с чем они связаны, на различных примерах.
1. Введение в методы интерфейса по умолчанию и статические интерфейсы
В EC -1 (пример кода 1, показан ниже) мы видим простое объявление интерфейса. Два метода в интерфейсе выполняют одну и ту же операцию, однако один - по умолчанию, а другой - статический.
//EC-1: Intro to default and static interface methods public interface Foo { //Default method declaration default int sumDefault(int a, int b) { return a + b; //simply adds a + b } //Static method declaration static int sumStatic(int a, int b) { return a + b; //simply adds a + b } }
IntelliJ или любая другая IDE, которую вы настроили для использования Java-8, не должны жаловаться на вышеуказанное. Давайте копнем глубже!
2. Реализация интерфейса Foo.
Если мы попытаемся реализовать интерфейс Foo, как показано в EC-1, не переопределяя какой-либо из методов, наша IDE не будет жаловаться, поскольку знает, что реализация уже предоставлена, как показано в EC-2 ниже.
EC-2: Implementing Foo public class Bar implements Foo { //No implementation required as it is already provided in the interface. IDE remains happy :D. }
3. Призыв
Давайте посмотрим, как мы можем вызывать как стандартные, так и статические методы интерфейса.
EC-3: Invoking default & static methods. //Default int i = new Bar().sumDefault(2, 2); System.out.println("Default: " + i); //Static int i = Foo.sumStatic(2, 2); System.out.println("Static: " + i);
Как показано в EC-3, мы можем просто вызвать метод по умолчанию, как если бы это был любой другой метод экземпляра в классе, т.е. путем вызова конструктора классов new <Constructor>.<method>
, поскольку он уже имеет реализацию.
С другой стороны, поскольку статические методы принадлежат шаблону класса / интерфейса, их можно вызывать так же, как обычно вызывается любой статический метод, т.е. <Class/Interface>.<method>
. Оба примера в EC-3 не содержат ошибок.
4. Переопределение стандартных и статических методов в интерфейсе Foo.
Что произойдет, если мы попытаемся переопределить и по умолчанию, и статический метод?
EC-4: Overriding Default & Static Interface Methods public class Bar implements Foo { @Override public int sumDefault(int a, int b) { return a + b - 1; // adds a + b then subtracts 1 } @Override <- Error public int sumStatic(int a, int b) { return a + b - 1; // adds a + b then subtracts 1 } }
Как показано в EC-4, переопределение метода по умолчанию будет работать без сбоев. К сожалению, мы не можем переопределить статические методы, поскольку Java предотвращает переопределение любого метода с помощью ключевого слова static. Ваша IDE укажет на ошибку.
Давайте посмотрим, как вызывать методы по умолчанию и статические методы интерфейса, которые были переопределены. Мы будем использовать код из EC-4.
5. Вызов замещенных методов
Поскольку статические методы нельзя переопределить, в этом примере мы сосредоточимся только на методах по умолчанию.
EC-5: Invoking default and static interface methods public class Bar implements Foo { @Override public int sumDefault(int a, int b) { return a + b - 1; //Note this overridden version adds a to b then subtracts 1; //The original interface method just adds a to b; } public static void main(String args[]) { Bar bar = new Bar(); int i = bar.sumDefault(2, 2); System.out.println("Default: " + i); } } Output: Default: 3 #QuickMaths
Результат sumDefault
после переопределения - 3 вместо 4. Это показывает, что методы по умолчанию при переопределении будут принимать реализацию класса, в котором они были переопределены, и не сохранят свою базовую реализацию интерфейса.
6. Реализация нескольких интерфейсов.
Все мы реализуем несколько интерфейсов, но что, если два интерфейса содержат одну и ту же сигнатуру метода по умолчанию? Рассмотрим этот новый интерфейс, который мы назовем Goo
.
EC-6 Implementing Foo and Goo public interface Goo { //Exact same method signature as that of Foo default int sumDefault(int a, int b) { return a + b + a; //Adds a + b and then adds a again, unlike Foo that just adds a + b; } } public class Bar implements Foo, Goo { //Which implementation of sumDefault(int a, int b) will this class use, Foo or Goo???? }
EC-6 представляет собой отличный вопрос. Какую реализацию примет класс Bar
? Ответ… Ни один из них! Ваша среда IDE будет жаловаться, что Bar
наследует несвязанные версии метода sumDefault(int a, int b)
. Как разработчику вам придется игнорировать их обоих и реализовать свою собственную версию. В этом случае это похоже на традиционный интерфейс, который требует от вас определения собственной реализации метода.
7. Наконец, реализация иерархии интерфейсов.
В нашем последнем примере, что произошло бы, если бы Goo расширился от Foo, а Bar реализовал Goo, как показано в EC-7 ниже.
EC-7: Implementing a hierarchy of interfaces public interface Foo { default int sumDefault(int a, int b) { return a + b; // Adds a to b } } public interface Goo extends Foo { default int sumDefault(int a, int b) { return a + b - 1; //Adds a + b, then subtracts 1. } } public class Bar implements Goo { public static void main(String args[]) { System.out.println("Default: " + new Bar().sumDefault(2, 2)); } } Output: Default: 3 #MoreQuickMaths
Как видно из EC-7, на выходе всегда получается самая последняя реализация в цепочке. Таким образом, мы можем забыть о реализации, предоставляемой Foo
, поскольку она была заменена реализацией Goo
.
Заключение
Итак, у вас есть небольшое введение в то, как можно использовать методы стандартного и статического интерфейса. На мой взгляд, это довольно глубокое дополнение к миру Java. Один вопрос, который я хотел бы задать: Если интерфейсы теперь имеют возможность определять переопределяемую реализацию для методов, где это оставляет абстрактные классы? Я имею в виду, что абстрактные классы по-прежнему имеют возможность устанавливать переменные экземпляра и определить конструкторы. Однако помимо этого новое ключевое слово default, похоже, сужает это разделение между абстрактными классами и интерфейсом, позволяя интерфейсам определять свою собственную реализацию методов, которые могут быть переопределены ниже по течению.
Как и в случае с большинством новых функций, для него будут хорошие варианты использования, как отмечает Pankaj из JournalDev: Методы интерфейса Java по умолчанию помогут нам в удалении базовых классов реализации, мы можем предоставить реализацию по умолчанию и классы реализации. можно выбрать, какой из них переопределить. , Я ценю дополнительную гибкость, которая у нас теперь есть.
Спасибо за прочтение!