Интерфейсы в Java 8 потерпели наибольшие изменения. Например, в Java 7 в интерфейсах мы могли объявлять только методы. Но начиная с Java 8 мы можем использовать в интерфейсах стандартные методы (default methods) и статические методы (static methods). Подробнее о нововведениях в интерфейсах читайте под катом
Проектирование интерфейсов — это трудная работа. Если мы хотим добавить дополнительные методы в интерфейсы, то это потребует изменения во всех классах, реализующих этот интерфейс.
Из практики: чем дольше мы поддерживаем код, тем больше интерфейс обрастает классами и наступает такой момент, что обслуживать его будет слишком накладно. Вот почему при проектировании программного обеспечения большинство программистов создают класс с базовой реализацией, а затем наследуют его и переопределяют нужные методы.
Методы по умолчанию (default methods) в интерфейсах Java 8
Для создания метода по умолчанию в интерфейсе, мы должны использовать ключевое слово default
. Рассмотрим на простом примере создание метода по умолчанию.
1 2 3 4 5 6 7 8 9 |
public interface Interface1 { void method1(String str); default void log(String str){ System.out.println("Метод по умолчанию. Логгируем: " + str); print(str); } } |
Обратите внимание, что в представленном выше интерфейсе метод log(String str)
является методом по умолчанию. Теперь, когда какой-либо класс будет реализовывать интерфейс Interface1, не является обязательным обеспечить реализацию методов по умолчанию (в нашем случае — это метод log(String str)
). Функционал с методами по умолчанию очень поможет нам в будущем, когда множество классов уже будут реализовывать этот интерфейс.
А теперь давайте рассмотрим еще один интерфейс:
1 2 3 4 5 6 7 8 9 |
public interface Interface2 { void method2(); default void log(String str){ System.out.println("Метод по умолчанию. Логгируем: " + str); } } |
Мы знаем, что Java не позволяет нам наследоваться от нескольких классов, потому что это приведет к ромбовидной проблеме, где компилятор не может решить, какой метод суперкласса использовать. Теперь же с появлением методов по умолчанию, эта проблема возникнет и для интерфейсов!
Все дело в том, что если класс реализует как Interface
1
, так и Interface2
и не реализовывает общий метод по умолчанию, то компилятор не может решить что выбрать.
Наследование нескольких интерфейсов является неотъемлемой частью Java, поэтому теперь в Java 8 нам нужно следить за тем, чтобы эта проблема не возникала и в интерфейсах. Так что, если класс реализует оба вышеуказанных интерфейса, то он должен будет обеспечить реализацию метода log(String str)
, в противном случае компилятор будет бросать ошибки.
Сейчас давайте посмотрим пример класса, который реализует оба интерфейса и обеспечивает реализацию метода по умолчанию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class MyClass implements Interface1, Interface2 { @Override public void method2() { } @Override public void method1(String str) { } @Override public void log(String str){ System.out.println("Класс MyClass. Логгируем: " + str); Interface1.print("печать в консоль"); } } |
Коротко о главном. Методы по умолчанию в интерфейсах
- Методы по умолчанию помогаю реализовывать интерфейсы без страха нарушить работу других классов.
- Методы по умолчанию в Java 8 позволяют избежать создания служебных классов, так как все необходимые методы могут быть представлены в самих интерфейсах.
- Методы по умолчанию дают свободу классам выбрать метод, который нужно переопределить.
- Одной из основных причин внедрения методов по умолчанию является возможность коллекций (в Java 8, конечно) использовать лямбда-выражения.
- Если какой-либо класс в иерархии имеет метод с той же сигнатурой, то методы по умолчанию становятся неактуальными. Метод по умолчанию не может переопределить метод класса
java.lang.Object
. Аргументация очень проста: это потому, что объект является базовым классом для всех Java-классов. Таким образом, даже если у нас есть методы классаObject
, определенные в качестве методов по умолчанию в интерфейсах, это будет бесполезно, потому что всегда будет использоваться метод класса объекта. Вот почему, чтобы избежать путаницы, мы не можем писать стандартные методы, которые переопределяли бы методы классаObject
.
Статические методы в интерфейсах
Статические методы похожи на методы по умолчанию, за исключением того, что мы не можем переопределить их в классах, реализующих интерфейс. Этот функционал помогает нам избежать нежелательных результатов, которые могут появиться в дочерних классах.
Давайте посмотрим использование статических методов на простом примере:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public interface MyData { default void print(String str) { if (!isNull(str)) System.out.println("Класс MyData. Печатаем строку: " + str); } static boolean isNull(String str) { System.out.println("Статический метод проверки на null"); return str == null ? true : "".equals(str) ? true : false; } } |
Как видим в примере выше, мы использовали тернарный оператор для уменьшения количества кода.
А теперь давайте рассмотрим класс, который реализует интерфейс выше:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class MyDataImpl implements MyData { public boolean isNull(String str) { System.out.println("Проверяем на null"); return str == null ? true : false; } public static void main(String args[]){ MyDataImpl obj = new MyDataImpl(); obj.print(""); obj.isNull("abc"); } } |
Обратите внимание, что i
sNull(String str)
является простым методом класса, и он не переопределяет метод интерфейса. А что будет, если мы добавим аннотацию @Override
к методу isNull()
? Это приведет к ошибке.
Результатом выполнения приведенной выше программы является следующее:
1 2 |
Статический метод проверки на null Проверяем на null |
А теперь давайте изменим в интерфейсе ключевое слово static
на default
. Результат выполнения представлен ниже:
1 2 3 |
Проверяем на null Класс MyData. Печатаем строку: Проверяем на null |
Статические методы видны только для методов интерфейса. Если мы удалим метод isNull()
из класса MyDataImpl
, то уже не сможем использовать его для объекта MyDataImpl
. Однако, как и другие статические методы, мы можем использовать имя класса для доступа к ним.
Например, так писать можно:
1 2 |
// так писать можно boolean result = MyData.isNull("abc"); |
Коротко о главном. Статические методы в интерфейсах
- Статические методы в интерфейсе являются частью интерфейса, мы не можем использовать его для объектов класса реализации.
- Статические методы в интерфейсе хороши для обеспечения вспомогательных методов, например, проверки на null, сортировки коллекций и т.д.
- Статические методы в интерфейсе помогают обеспечивать безопасность, не позволяя классам, которые реализуют интерфейс, переопределить их.
- Мы не можем определить статические методы для методов класса
Object
, потому что получим ошибку компиляции: «This static method cannot hide the instance method from Object«. Это потому, что в Java так делать нельзя 🙂 . То естьObject
является базовым классом для всех классов и мы не можем использовать статический метод и еще такой метод с одинаковой сигнатурой. - Мы можем использовать статические методы интерфейса, чтобы не создавать вспомогательные классы, то есть переместить все статические методы в соответствующий интерфейс. Такой метод легко использовать и быстро находить.
Функциональные интерфейсы
В завершение статьи хотел бы дать краткое введение в функциональные интерфейсы.
Интерфейс с одним абстрактным методом является функциональным интерфейсом.
В Java 8 была введена новая аннотация @FunctionalInterface
для обозначения интерфейса, функциональным интерфейсом. Новая аннотация @FunctionalInterface
добавляется для того, чтобы избежать случайного добавления абстрактных методов в функциональный интерфейс. Она не обязательна, но является хорошей практикой написания кода.
Функциональные интерфейсы — это долгожданная фича Java 8, потому что это позволяет нам использовать лямбда-выражения для создания экземпляра таких интерфейсов. Был добавлен новый пакет java.util.function
с множеством функциональных интерфейсов.
Мы рассмотрим функциональные интерфейсы и лямбда-выражения в будущих постах. Следите за обновлениями раздела Особенности и нововведения Java 8.
Здравствуйте.
Поправьте, пожалуйста.
После изменения в интерфейсе ключевого слово static на default, результат выполнения будет:
1 Проверяем на null
2 Класс MyData. Печатаем строку:
3 Проверяем на null
Да, Вы правы. Спасибо за исправление!