Кожна нова версія Java відрізняється від попередніх. Ось приклад змін із матеріалу, який ми проходили: до Java 5 у мові не було
enum
’ів.
Так і Java 8 помітно відрізняється від Java 7. Ігнорувати важливі нововведення ми, звісно, не будемо.
Якщо вже в цій лекції говоримо про інтерфейси, розглянемо одне оновлення — дефолтні методи в інтерфейсах.
Ти вже знаєш, що інтерфейс не реалізує поведінку. Його завдання — описати, яку поведінку повинні мати всі об'єкти, що його імплементують.
Але часто розробники стикалися з ситуаціями, коли реалізація якогось методу в усіх класах була однаковою.
Давай розглянемо наш старий приклад з авто:
public interface Car {
public void gas();
public void brake();
}
public class Sedan implements Car {
@Override
public void gas() {
System.out.println("Газ!");
}
@Override
public void brake() {
System.out.println("Гальмо!");
}
}
public class Truck implements Car {
@Override
public void gas() {
System.out.println("Газ!");
}
@Override
public void brake() {
System.out.println("Гальмо!");
}
}
public class F1Car implements Car {
@Override
public void gas() {
System.out.println("Газ!");
}
@Override
public void brake() {
System.out.println("Гальмо!");
}
}
Яка, на твою думку, головна проблема цього коду?
Ти певно звернув увагу, що ми написали купу однакового коду! Ця проблема поширена у програмуванні, і потрібно її уникати.
Інша річ, що до виходу Java 8 особливих варіантів рішення не було. Коли з'явилася ця версія, можна було визначати методи за замовчуванням і реалізовувати їх прямо всередині інтерфейсу!
Ось як це робиться:
public interface Car {
public default void gas() {
System.out.println("Газ!");
}
public default void brake() {
System.out.println("Гальмо!");
}
}
public class Sedan implements Car {
}
public class Truck implements Car {
}
public class F1Car implements Car {
}
Тепер методи gas()
і brake()
, які для всіх авто були однаковими, винесені в інтерфейс, і повторюваний код не потрібен. При цьому методи доступні у кожному з класів!
public class Main {
public static void main(String[] args) {
F1Car f1Car = new F1Car();
Sedan sedan = new Sedan();
Truck truck = new Truck();
truck.gas();
sedan.gas();
f1Car.brake();
}
}
А якщо є 100 класів з методом gas()
, але однакову поведінку мають лише 99 з них? Це все псує, і дефолтний метод у цьому випадку не підходить?
Звісно, ні :) Дефолтні методи інтерфейсів можна перевизначати.
public class UnusualCar implements Car {
@Override
public void gas() {
System.out.println("Ця машина газує іншим способом!");
}
@Override
public void brake() {
System.out.println("Ця машина гальмує іншим способом!");
}
}
Всі інші 99 типів авто будуть реалізувати метод за замовчуванням, а клас UnusualCar
— виняток — не зіпсує загальну картину і спокійно визначить свою поведінку.
Множинне успадкування в інтерфейсах
Як ти вже знаєш, у Java немає множинного успадкування. Причин цього багато, ми докладно розглянемо їх у окремій лекції. В інших мовах, например, в C++, воно навпаки існує. Без множинного успадкування виникає серйозна проблема: в одного і того ж об'єкта може бути низка різних характеристик і «поведінок». Приклад із життя: для наших батьків ми — діти, для викладачів — студенти, для лікарів — пацієнти. У житті ми постаємо в різних ролях і, відповідно, поводимося по-різному: з викладачами ми явно розмовлятимемо не так, як із близькими друзями. Спробуємо перекласти цю ситуацію на код. Уявимо, що у нас є два класи: Ставок та Авіарій. Для ставка потрібні птахи, що плавають, а для авіарія — ті, що літають. Для цього ми створили два базові класи —FlyingBird
и Waterfowl
.
public class Waterfowl {
}
public class FlyingBird {
}
Відповідно, в авіарій будемо відправляти тих птахів, класи яких успадковані від FlyingBird
, а в ставок — тих, хто походить від Waterfowl
.
Ніби все виглядає просто.
Але що робитимемо, якщо треба буде кудись визначити качку?
Вона і плаває, і літає. А множинного успадкування в нас немає.
На щастя, в Java передбачена множинна реалізація інтерфейсів. Якщо успадковуватись від кількох батьків клас не може, реалізувати кілька інтерфейсів — просто!
Наша качка може бути і літаючою, і плаваючою :) Досить зробити FlyingBird
і Waterfowl
не класами, а інтерфейсами, щоб досягти необхідного результату.
public class Duck implements FlyingBird, Waterfowl {
//методи обох інтерфейсів легко поєднуються у одному класі
@Override
public void fly() {
System.out.println("Летимо!");
}
@Override
public void swim() {
System.out.println("Пливемо!");
}
}
Завдяки цьому наша програма зберігає гнучке управління класами, а у поєднанні з реалізацією дефолтних методів наші можливості визначати поведінку об'єктів стають практично безмежними! :)
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ