Кожна нова версія Java відрізняється від попередніх. Ось приклад змін із матеріалу, який ми проходили: до Java 5 у мові не було enum’ів.
Дефолтні методи в інтерфейсах - 1
Так і 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("Пливемо!");
   }
}
Завдяки цьому наша програма зберігає гнучке управління класами, а у поєднанні з реалізацією дефолтних методів наші можливості визначати поведінку об'єктів стають практично безмежними! :)