Java 8. default и static методы в интерфейсах.
1)default методы в интерфейсах :
Потом появилась Java 8 c default методами, т.е. методами по умолчанию. Естесственно,
default методы по умолчанию public, потому public писать НЕ надо.
Если у метода в интерфейсе есть default, то метод ДОЛЖЕН иметь реализацию.
public interface Interface8 < default void print(String str)< System.out.println("str1 = " + str); >>
Но класс который имплементит данный метод может иметь реализацию, а может и не иметь
public class Main implements Interface8 < public static void main(String[] args) < System.out.println("============================="); Interface8 interface8 = new Main(); interface8.print("test java 8"); >>
Тут будет выведено на консоль :
str1 = test java 8
Если же метод print будет реализован в классе Main, то будет выполнен метод print данного
класса, а не интерфейса :
public class Main implements Interface8 < public static void main(String[] args) < System.out.println("============================="); Interface8 interface8 = new Main(); interface8.print("test java 8"); >@Override public void print(String str) < System.out.println("MainClass " + str); >>
Тут будет выведено на консоль :
MainClass test java 8
1. Если класс реализует более 2 интерфейса, которые содержат одинаковые методы по умолчанию,
то сам класс должен содержать данный метод.
public interface Interface8 < default void print(String str)< System.out.println("str1 = " + str); >> public interface Interface81 < default void print(String str)< System.out.println("str81 = " + str); >> public class Main implements Interface8, Interface81 < public static void main(String[] args) < System.out.println("============================="); Interface8 interface8 = new Main(); interface8.print("test java 8"); >>
Вот так компилироваться не будет.
А вот так будет(мы просто добавили метод print в класс Main):
public interface Interface8 < default void print(String str)< System.out.println("str1 = " + str); >> public interface Interface81 < default void print(String str)< System.out.println("str81 = " + str); >> public class Main implements Interface8, Interface81 < public static void main(String[] args) < System.out.println("============================="); Interface8 interface8 = new Main(); interface8.print("test java 8"); >@Override public void print(String str) < System.out.println("MainClass " + str); >>
Вывод на консоль :
MainClass test java 8
2) static методы в интерфейсах:
Мы их не можем переопределять в классах, реализующих интерфейс
public interface Interface8 < default void print(String str)< System.out.println("str1 = " + str); >static void readData() < System.out.println("readData"); >> public class Main implements Interface8 < public static void main(String[] args) < System.out.println("============================="); Main m = new Main(); m.print("-"); Interface81.read(); >>
Вызвать статический метод read, как и любюой статический метод — ИмяИнтрефеса.Имя метода
Выводы:
а)Статический метод в интерфейсе принадлежит только интерфейсу. Вызов ИмяИнтрефеса.Имя метода
б)Класс, который реализует интерфейс НЕ может переопределить статический метод интерфейса
в)Если создать объект класса, то нельзя вызвать статический метод интерфейса
Defaut method в Java
Default-методы появились Java 8. В это статье рассказывается, что это такое, зачем появилось, и как ими пользоваться.
Default-метод — это метод, который реализуется прямо в интерфейсе, его помечают ключевым словом default.
Читайте также про Модификаторы доступа
Пример использования
Допустим, у нас есть интерфейс Animal:
public interface Animal
Есть классы 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()
Впрочем, его можно и переопределить в каком-либо из классов, например в Fish:
public class Fish implements Animal < @Override public String move() < return "swim"; >@Override public String sleep() < return "fish sleeps"; >>
Убедимся, что рыба спит по-своему:
@Test public void whenFishSleep_thenOnItsOwn()
Как наследуются 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()
Причины появления default-методов
Наверно уже понятно, что default-методы упрощают рефакторинг — а именно, добавление новых методов.
До Java 8 все методы в интерфейсах были абстрактными. К чему это вело?
К тому, что при добавлении нового метода в интерфейс приходилось править все классы, реализующие интерфейс — реализовывать метод в этих классах. Это было неудобно. А в Java 8 (в классы ядра) захотели ввести новые методы в старые интерфейсы. Так что ввели ключевое слово default и эти методы сделали default. Например, в интерфейсе java.lang.Iterable появились новые default-методы forEach() и spliterator():
public interface Iterable < Iteratoriterator(); default void forEach(Consumer action) < Objects.requireNonNull(action); for (T t : this) < action.accept(t); >> default Spliterator spliterator() < return Spliterators.spliteratorUnknownSize(iterator(), 0); >>
Поскольку огромное число классов реализуют Iterable , без default-методов дополнить этот интерфейс было бы практически невозможно.
Итог
Мы рассмотрели, что такое default метод в Java. Код примеров доступен на GitHub.
Дефолтные методы интерфейсов
Думаю, что не ошибусь, если скажу, что завершая базовый курс изучения Java каждый слушатель начинает готовиться к будущим собеседованиям. Читает статьи, форумы в поисках советов, а кто-то возможно уже и сходил на свое первое собеседование в этой профессии.
Среди прочих рекомендаций и описаний возможных вопросов на интервью часто встречаются темы нововведений в различных версиях Java 5, 8, 11 и т.д. Да и в лекциях преподаватель периодически обращает внимание, что тот или иной класс или метод появились с такой-то версии. И здесь задаешься вопросом — к чему мне обращать внимание и запоминать что и когда было введено в язык, если учусь я по последней версии, в которую включены все возможные функции.
Частично ответ на этот вопрос озвучивал Валерий в лекции про дату и время. С большой долей вероятности на входе в профессию (на нашей первой работе программистом) мы столкнемся с проектами, написанными на прежних версиях Java. В этом случае понимание возможностей или, что возможно лучше, ограничений функционала будет хорошим подспорьем в первой работе.
Но есть и второй момент. Следует понимать, что в базовом курсе мы учились именно основам Java. Разбирались в принципах ООП, синтаксиса и работе базовых инструментов языка. А часть популярных в обсуждениях нововведений зачастую предназначена для помощи программисту, упрощения и удобства работы. Например лямбда выражения и Stream, изучение которых предполагается в следующем курсе. И такая последовательность изучения верна, т.к. программист должен понимать, что кроется за упрощенным выражением.
На своем первом собеседовании я столкнулся с вопросом “для чего нужны дефолтные методы в интерфейсах?”. И ответ, как не сложно догадаться, можно построить из вопроса. Однако, в лекции об интерфейсах о такой возможности не упоминалось, относится она к нововведениям в Java 8, поэтому предлагаю рассмотреть эту тему подробнее.
Итак, как я уже сказал ранее, суть вытекает из названия темы. Дефолтные методы или методы по умолчанию позволяют включать в интерфейс не только абстрактные методы, но и методы с реализацией. Отличительной особенностью является то, что эти методы не требуют переопределения и они также доступны классам реализующим интерфейс.
Перейдем к примерам. Для наглядности воспользуемся фрагментами кода из лекции “Интерфейсы” (курс: Java Developer, level 1).
Возьмем три класса, описывающих геометрических фигуры, например, “Segment”, “Circle” и “Square”, и создадим интерфейс, который бы позволял сравнивать их по периметру.
public class Segment < double a; //Длина отрезка Segment(double a) < this.a = a; >> public class Circle < double radius; Circle(double radius) < this.radius = radius; >> public class Square < double a; //Сторона квадрата public Square(double a)< this.a = a; >>
Далее создаем интерфейс, в который включаем метод для сравнения наших объектов по периметру. В качестве параметра в него можно будет передавать объект, реализующий интерфейс ComparePerimeter. А также для исключения ошибок включим в него метод, обязывающий фигуру определить её периметр.
public interface ComparePerimeter < //Метод для определения периметра double perimeter(); //Метод для сравнения фигур по периметру int comparePerimeters(ComparePerimeter figure); >
После включения интерфейса наши объекты будут выглядеть следующим образом.
public class Segment implements ComparePerimeter < double a; //Длина отрезка Segment(double a) < this.a = a; >@Override double perimeter() < return a; >@Override public int comparePerimeters(ComparePerimeter figure) < return Double.compare(perimeter(), figure.perimeter()); >> public class Circle implements ComparePerimeter < double radius; Circle(double radius) < this.radius = radius; >@Override double perimeter() < return 2 * Math.PI * radius; >@Override public int comparePerimeters(ComparePerimeter figure) < return Double.compare(perimeter(), figure.perimeter()); >> public class Square implements ComparePerimeter < double a; public Square(double a)< this.a = a; >@Override public double perimeter() < return 4 * a; >@Override public int comparePerimeters(ComparePerimeter figure) < return Double.compare(perimeter(), figure.perimeter()); >>
Прежде чем перейти к следующему шагу проверим, как работает наш код. Создадим объекты и сравним их периметры.
public static void main(String[] args) < Segment segment = new Segment(5); Circle circle = new Circle(0.5); Square square = new Square(5.0); System.out.println("segment.comparePerimeters(circle) = " + segment.comparePerimeters(circle)); System.out.println("circle.comparePerimeters(square) = " + circle.comparePerimeters(square)); System.out.println("square.comparePerimeters(segment) EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">segment.comparePerimeters(circle) = 1 circle.comparePerimeters(square) = -1 square.comparePerimeters(segment) = 1
Но что мы видим? Если содержимое метода perimeter() для каждого класса уникально, то код метода comparePerimeters(ComparePerimeter figure) в каждом классе повторяется. Вызвано это необходимостью переопределения метода.
И здесь нам на помощь приходит возможность создания дефолтных методов.
Для того, чтобы метод, включенный в интерфейс, стал дефолтным необходимо использовать ключевое слово default в определении метода. Перенесем код метода для сравнения фигур в интерфейс.
public interface ComparePerimeter < double perimeter(); default int comparePerimeters(ComparePerimeter figure)< return Double.compare(perimeter(), figure.perimeter()); >; >
После чего, уберем переопределение метода из классов и убедимся, что система не выдаст нам ошибок.
public class Segment implements ComparePerimeter < double a; //Длина отрезка Segment(double a) < this.a = a; >@Override double perimeter() < return a; >> public class Circle implements ComparePerimeter < double radius; Circle(double radius) < this.radius = radius; >@Override double perimeter() < return 2 * Math.PI * radius; >> public class Square implements ComparePerimeter < double a; public Square(double a)< this.a = a; >@Override public double perimeter() < return 4 * a; >>
Повторим проверку, чтобы подтвердить то, что метод по-прежнему доступен для объектов.
public static void main(String[] args) < Segment segment = new Segment(5); Circle circle = new Circle(0.5); Square square = new Square(5.0); System.out.println("segment.comparePerimeters(circle) = " + segment.comparePerimeters(circle)); System.out.println("circle.comparePerimeters(square) = " + circle.comparePerimeters(square)); System.out.println("square.comparePerimeters(segment) EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">segment.comparePerimeters(circle) = 1 circle.comparePerimeters(square) = -1 square.comparePerimeters(segment) = 1
Итак, мы убедились в том, что в интерфейсы можно включать не только абстрактные методы, но и методы реализующие функции, общие для всех объектов, реализующих интерфейс. Такие методы должны быть определены ключевым словом default , они не требуют переопределения в классах и доступны всем объектам, реализующим данный интерфейс.
А теперь давайте представим, что нам понадобилось выводить на консоль значения периметров наших фигур. И при этом переопределять toString() у классов нет ни времени, ни сил.
Для выхода из такого положения снова воспользуемся дефолтными методами. Просто добавим необходимый метод в интерфейс.
public interface ComparePerimeter < double perimeter(); default int comparePerimeters(ComparePerimeter figure)< return Double.compare(perimeter(), figure.perimeter()); >; default void printPerimeter() < System.out.println("printPerimeter: " + perimeter()); >>
В результате получаем возможность выводить значения на консоль для всех объектов, реализующих интерфейс. Таким образом парой строк мы предоставили новую функцию целой группе объектов.
public static void main(String[] args) < Segment segment = new Segment(5); Circle circle = new Circle(0.5); Square square = new Square(5.0); System.out.println("segment.comparePerimeters(circle) = " + segment.comparePerimeters(circle)); System.out.println("circle.comparePerimeters(square) = " + circle.comparePerimeters(square)); System.out.println("square.comparePerimeters(segment) EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">segment.comparePerimeters(circle) = 1 circle.comparePerimeters(square) = -1 square.comparePerimeters(segment) = 1 printPerimeter: 5.0 printPerimeter: 3.141592653589793 printPerimeter: 20.0
Методы в интерфейсах, определенные ключевым словом “default”, не требуют переопределения, но и не исключают такой возможности. При необходимости мы всегда можем переопределить дефолтный метод.
Для примера добавим в один из классов единицы измерений.
public class Circle implements ComparePerimeter < double radius; Circle(double radius) < this.radius = radius; >@Override public double perimeter() < return 2 * Math.PI * radius; >@Override public void printPerimeter() < System.out.println("printPerimeter: " + perimeter() + " mm"); >>
Вывод на консоль:
segment.comparePerimeters(circle) = 1 circle.comparePerimeters(square) = -1 square.comparePerimeters(segment) = 1 printPerimeter: 5.0 printPerimeter: 3.141592653589793 mm printPerimeter: 20.0
И напоследок рассмотрим ситуацию, в которой два интерфейса содержат дефолтные методы с одинаковым именем.
Создадим второй интерфейс и включим в него дефолтный метод с аналогичным именем.
public interface PrintPerimeter < double perimeter(); default void printPerimeter()< System.out.println("Perimeter: " + perimeter() + "мм"); >>
При попытке реализации двух интерфейсов, включающих дефолтные методы с одинаковыми именами, среда разработки выдаст ошибку и предложит переопределить конфликтующий метод.
public class Square implements ComparePerimeter, PrintPerimeter < double a; public Square(double a)< this.a = a; >@Override public void printPerimeter() < >@Override public double perimeter() < return 4 * a; >>
- Дефолтные методы или методы по умолчанию позволяют включать в интерфейс не только абстрактные методы, но и методы с реализацией;
- Для того, чтобы метод, включенный в интерфейс стал дефолтным необходимо использовать ключевое слово “default” в определении метода;
- Дефолтные методы не требуют переопределения, но и не исключают такой возможности;
- При попытке реализации двух интерфейсов, включающих дефолтные методы с одинаковыми именами, среда разработки выдаст ошибку и предложит переопределить конфликтующий метод.
Минкин Михаил, после окончания базового курса, февраль 2021
Java 8 Default и Static методы

В Java 8 появилась возможность писать методы с реализацией в интерфейсах. Такие методы называются default и static. Особенности работы с ними мы сегодня и рассмотрим.
2. Default методы
Объявляются такие методы следующим образом
interface Human < default void walk ( ) < System . out . println ( "Ну, я пошел. " ) ;
Использовать их можно только после получения экземпляра
Human . walk ( ) ; //ошибка, так делать нельзя
Human human = new Human ( ) < >;
human . walk ( ) ; //а вот так можно
Default методы так же можно переопределять как и обычные методы
class Runner implements Human < public void walk ( ) < System . out . println ( "Я бегу" ) ;
Но есть один нюанс, рассмотрим следующий случай
interface Human < default void walk ( ) < System . out . println ( "Ну, я пошел. " ) ; interface Walkable < default void walk ( ) < System . out . println ( "Ходьба" ) ; class Runner implements Human , Walkable <
В данном случае мы получим ошибку компиляции, ибо нельзя наследовать одновременно несколько default методов, не понятно какой из методов вызывать, если мы напишем следующий код
Runner runner = new Runner ( ) ;
runner . walk ( ) ;
3. Static методы
C ними все проще, чем с default методами. Static методы в интерфейсе работают точно так же как и static методы в классе.
static void walk ( ) < System . out . println ( "Ну, я пошел. " ) ; Human . walk ( ) ;
Наследовать static методы в интерфейсе нельзя, как и static методы в классах.
Так же нельзя вызывать static метод интерфейса из класса наследника
public static void main ( String [ ] args ) < HomoSapiens . walk ( ) ; //ошибка компиляции interface Human < static void walk ( ) < System . out . println ( "Ну, я пошел. " ) ; class HomoSapiens implements Human <
В Java 8 есть еще множество полезных нововведений, которые можно найти тут
Есть возможность записаться на наши курсы по Java. Детальную информацию смотрите у нас на сайте.
- ← Найти максимальный элемент в массиве из сгенерированных чисел
- Заменить каждый элемент массива с нечетным индексом на ноль →
