Интерфейсы
Интерфейс описывает, что объект умеет делать, но не говорит как. Это договор: класс, который его подписал, обязан реализовать все перечисленные методы.
Объявление и реализация
Интерфейс объявляют словом interface, а класс подключает его словом implements. Методы интерфейса по умолчанию открытые (public) и абстрактные, поэтому в реализации их тоже помечают public:
interface Greeter {
String greet(String name);
}
class FriendlyGreeter implements Greeter {
@Override
public String greet(String name) {
return "Привет, " + name + "!";
}
}
void main() {
Greeter greeter = new FriendlyGreeter();
IO.println(greeter.greet("Анна"));
}Результат:
Привет, Анна!Интерфейс как тип
Переменная или параметр типа интерфейса принимает любой объект, который его реализует. Классы при этом могут быть совсем не родственными:
interface Priced {
double price();
}
class Book implements Priced {
@Override
public double price() {
return 15;
}
}
class Delivery implements Priced {
@Override
public double price() {
return 5;
}
}
void main() {
Priced[] cart = { new Book(), new Book(), new Delivery() };
double total = 0;
for (Priced item : cart) {
total += item.price();
}
IO.println("Итого: " + total + " €");
}Результат:
Итого: 35.0 €Книга и доставка не имеют общего родителя, но обе знают свою цену — этого достаточно.
Несколько интерфейсов
Родитель у класса только один, а интерфейсов можно реализовать сколько угодно. Их перечисляют через запятую:
interface Swimmer {
String swim();
}
interface Flyer {
String fly();
}
class Duck implements Swimmer, Flyer {
@Override
public String swim() {
return "Утка плывёт";
}
@Override
public String fly() {
return "Утка летит";
}
}
void main() {
Duck duck = new Duck();
IO.println(duck.swim());
IO.println(duck.fly());
}Результат:
Утка плывёт
Утка летитМетоды по умолчанию
Метод с модификатором default имеет тело прямо в интерфейсе. Классы получают его готовым и могут переопределить при желании. В интерфейсе бывают и статические методы:
interface Greeter {
String name();
default String greet() {
return "Привет, " + name() + "!";
}
static Greeter of(String name) {
return () -> name;
}
}
class Robot implements Greeter {
@Override
public String name() {
return "R2";
}
}
void main() {
IO.println(new Robot().greet());
IO.println(Greeter.of("Олег").greet());
}Результат:
Привет, R2!
Привет, Олег!Запись () -> name — это лямбда, короткий способ реализовать интерфейс с одним абстрактным методом. Лямбды подробно разобраны в главе об обобщениях и лямбдах.
Интерфейс или абстрактный класс
Интерфейс описывает умение («умеет плавать», «имеет цену») и не хранит состояние объекта: полей у него нет, только константы. Абстрактный класс подходит, когда у подклассов есть общие поля и код. Если сомневаетесь, начинайте с интерфейса — класс может реализовать много интерфейсов, но унаследовать только один класс.
Частая ошибка
Методы интерфейса — public. Если в классе забыть это слово, метод станет менее доступным, чем в интерфейсе, и компилятор откажется:
interface Greeter {
String greet(String name);
}
class FriendlyGreeter implements Greeter {
String greet(String name) {
return "Привет, " + name;
}
}
void main() {
}Результат:
Main.java:6: error: greet(String) in Main.FriendlyGreeter cannot implement greet(String) in Greeter
String greet(String name) {
^
attempting to assign weaker access privileges; was publicДобавьте public перед методом в классе.