Як у мові Java підтримує множинне спадкування

Як у мові Java підтримує множинне спадкування



Множинне успадкування в Java та композиція проти успадкування

Якийсь час тому я написав кілька постів про успадкування , інтерфейсі і композиції у Java. У цій статті ми розглянемо множинне спадкування, а потім дізнаємося про переваги композиції порівняно з спадкуванням.

Множинне успадкування в Java

Множинне успадкування - це можливість створення одного класу з кількома суперкласами. На відміну від деяких інших популярних мовно-орієнтованих мов програмування, таких як C++, Java не забезпечує підтримку множинного успадкування у класах . Java не підтримує множинне спадкування в класах, тому що це може призвести до проблемі з діамантами, і замість того, щоб надавати якийсь складний спосіб її вирішення, існують ефективніші способи, за допомогою яких ми можемо досягти того ж результату, що й множинне спадкування.

Алмазна проблема

Щоб легко зрозуміти проблему алмазів, припустимо, що множинне успадкування було підтримано Java. У цьому випадку ми можемо мати ієрархію класів, як показано нижче.

Припустимо, SuperClass - це абстрактний клас, який оголошує деякий метод, а ClassA, ClassB - це конкретні класи.

Як у Java підтримує множинне спадкування?

Одним із ключових аспектів об'єктно-орієнтованого програмування є успадкування. За допомогою успадкування можна розширити функціонал вже існуючих класів за рахунок додавання нового функціоналу або зміни старого. Наприклад, є наступний клас Person, який описує окрему людину:

class Person < String name; public String getName()< return name; >public Person(String name) < this.name=name; >public void display() < System.out.println("Name: " + name); >>

І, можливо, згодом ми захочемо додати ще один клас, який описує співробітник підприємства - клас Employee.Так як цей клас реалізує той же функціонал, що і клас Person, оскільки співробітник - це також і людина, то було б раціонально зробити клас Employee похідним (спадкоємцем, підкласом) від класу Person, який, своєю чергою, називається базовим класом, батьком або суперкласом:

class Employee extends Person < public Employee(String name)< super(name); // якщо базовий клас визначає конструктор // похідний клас повинен його викликати >>

Щоб оголосити один клас спадкоємцем від іншого, треба використовувати після імені класу-спадкоємця ключове слово extends , після якого йде ім'я базового класу. .

Якщо в базовому класі визначені конструктори, то в конструкторі похідного класи необхідно викликати один із конструкторів базового класу за допомогою ключового слова super . є виклик super(name) представлятиме виклик конструктора класу Person.

При виклику конструктора після слова super в дужках йде перерахування аргументів, що передаються При цьому виклик конструктора базового класу повинен йти на самому початку в конструкторі похідного класу.

Причому навіть якщо похідний клас жодної іншої роботи не виробляє в конструкторі, як у прикладі вище, однаково необхідно викликати конструктор базового класу.

public class Program < public static void main(String[] args) < Person tom = new Person("Tom"); tom.display(); Employee sam = New Employee("Sam"); sam.display(); >> class Person < String name; public String getName()< return name; >public Person(String name) < this.name=name; >public void display() < System.out.println("Name: " + name); >> class Employee extends Person < public Employee(String name)< super(name); // якщо базовий клас визначає конструктор // то похідний клас має його викликати >>

Похідний клас має доступ до всіх методів та полів базового класу (навіть якщо базовий клас знаходиться в іншому пакеті), крім тих, що визначені з модифікатором private. При цьому похідний клас також може додавати свої поля та методи:

public class Program < public static void main(String[] args) < Employee sam = new Employee("Sam", "Microsoft"); sam.display(); // Sam sam.work(); // Sam works in Microsoft >> class Person < String name; public String getName()< return name; >public Person(String name) < this.name=name; >public void display() < System.out.println("Name: " + name); >> class Employee extends Person < String company; public Employee(String name, String company) < super(name); this.company=company; >public void work() < System.out.printf("%s works in %s \n", getName(), company); >>

У разі клас Employee додає поле company, яке зберігає місце роботи співробітника, і навіть метод work.

Перевизначення методів

Похідний клас може визначати свої методи, а може перевизначати методи, успадковані від базового класу. Наприклад, перевизначимо в класі Employee метод display:

public class Program < public static void main(String[] args) < Employee sam = new Employee("Sam", "Microsoft"); sam.display(); // Sam // Works in Microsoft >> class Person < String name; public String getName()< return name; >public Person(String name) < this.name=name; >public void display() < System.out.println("Name: " + name); >> class Employee extends Person < String company; public Employee(String name, String company) < super(name); this.company=company; >@Override public void display() < System.out.printf("Name: %s \n", getName()); System.out.printf("Works in %s \n", company); >>

Перед методом, що перевизначається, вказується анотація @Override . Ця інструкція в принципі необов'язкова.

При перевизначенні методу він повинен мати рівень доступу не менше, ніж рівень доступу до базового класу. Наприклад, якщо у базовому класі метод має модифікатор public, то й у похідному класі метод повинен мати модифікатор public.

Однак у цьому випадку бачимо, що частина методу display в Employee повторює дії з методу display базового класу. Тому ми можемо скоротити клас Employee:

class Employee extends Person < String company; public Employee(String name, String company) < super(name); this.company=company; >@Override public void display() < super.display(); System.out.printf("Works in %s \n", company); >>

За допомогою ключового слова super ми можемо також звернутися до реалізації методів базового класу.

Заборона наслідування

Хоча успадкування є дуже цікавим і ефективним механізмом, але в деяких ситуаціях його застосування може бути небажаним. І в цьому випадку можна заборонити наслідування за допомогою ключового слова final. Наприклад:

public final class Person

Якби клас Person був визначений таким чином, то наступний код був би помилковим і не спрацював, тому що ми тим самим заборонили успадкування:

class Employee extends Person

Крім заборони успадкування, можна також заборонити перевизначення окремих методів.Наприклад, у прикладі вище перевизначено метод display(), заборонимо його перевизначення:

І тут клас Employee зможе перевизначити метод display.

Динамічна диспетчеризація методів

Наслідування та можливість перевизначення методів відкривають нам великі можливості. Насамперед ми можемо передати змінній суперкласу посилання на об'єкт підкласу:

Person sam = New Employee("Sam", "Oracle");

Оскільки Employee успадковується від Person, об'єкт Employee є водночас і об'єктом Person. Грубо кажучи, будь-який працівник підприємства одночасно є людиною.

Однак, незважаючи на те, що змінна представляє об'єкт Person, віртуальна машина бачить, що насправді вона вказує на об'єкт Employee. Тому при викликі методів цього об'єкта буде викликатися та версія методу, яка визначена в класі Employee, а не в Person. Наприклад:

public class Program < public static void main(String[] args) < Person tom = new Person("Tom"); tom.display(); Person sam = New Employee("Sam", "Oracle"); sam.display(); >> class Person < String name; public String getName() < return name; >public Person(String name) < this.name=name; >public void display() < System.out.printf("Person %s \n", name); >> class Employee extends Person < String company; public Employee(String name, String company) < super(name); this.company = company; >@Override public void display() < System.out.printf("Employee %s works in %s \n", super.getName(), company); >>

Консольний висновок цієї програми:

Person Tom Employee Sam works in Oracle

При виклику перевизначеного методу віртуальна машина динамічно знаходить і викликає саме ту версію методу, що у підкласі. Цей процес ще називається dynamic method lookup або динамічний пошук методу або динамічна диспетчеризація методів.

Посібник з наслідування в Java

Простіше кажучи, Java клас може успадковувати інший клас і кілька інтерфейсів, а інтерфейс може успадковувати інші інтерфейси.

У цій статті ми почнемо з необхідності успадкування та перейдемо до того, як успадкування працює з класами та інтерфейсами.

Потім ми розглянемо, як імена змінних/методів та модифікатори доступу впливають на успадковані члени.

І наприкінці ми побачимо, що означає наслідувати тип.

2. Потреба у спадщині

Уявіть, що ви є виробником автомобілів і пропонуєте своїм клієнтам кілька моделей автомобілів. Незважаючи на те, що різні моделі автомобілів можуть пропонувати різні функції, такі як люк на даху або куленепробивні вікна, всі вони включатимуть загальні компоненти та функції, такі як двигун та колеса.

Має сенс створювати базовий дизайн та розширювати його для створення своїх спеціалізованих версій, а не проектувати кожну модель автомобіля окремо з нуля.

Аналогічно з наслідуванням ми можемо створити клас з базовими функціями та поведінкою та створити його спеціалізовані версії, створивши класи, які успадковують цей базовий клас. Так само інтерфейси можуть розширювати існуючі інтерфейси.

Ми помітимо використання кількох термінів для позначення типу, що успадковується іншим типом, а саме:

  • базовий тип також називається супер або батьківським типом
  • похідний тип називається розширеним, підтипом або дочірнім типом

3. Спадкування класів

3.1. Розширення класу

Клас може успадковувати інший клас та визначати додаткові члени.

Почнемо з визначення базового класу Car:


public
class
Car



int
wheels;


String
model;


void
start()



// Check essential parts


>


>

Клас ArmoredCar може успадковувати членів класу Car, використовуючи ключове слово extends у своєму оголошенні :


public
class
ArmoredCar
extends
Car



int
bulletProofWindows;


void
remoteStartCar()



// Цей автомобіль може бути здійснений за допомогою remote control


>


>

Тепер ми можемо сказати, що клас ArmoredCar є підкласом Car, а останній є надкласом ArmoredCar.

Класи в Java підтримують поодиноке успадкування ; Клас ArmoredCar не може розширювати кілька класів.

Також зауважте, що за відсутності ключового слова extends клас неявно успадковує клас java.lang.Object .

Клас підкласу успадковує нестатичні захищені та відкриті члени від класу суперкласу. Крім того, члени з доступом за замовчуванням (package-private) успадковуються, якщо два класи знаходяться в одному пакеті.

З іншого боку, приватні та статичні члени класу не успадковуються.

3.2. Доступ до батьківських елементів із дочірнього класу

Щоб отримати доступ до успадкованих властивостей або методів, ми можемо просто використовувати їх безпосередньо:


public
class
ArmoredCar
extends
Car



public
String
registerModel()



return
model;


>


>

Зверніть увагу, що нам не потрібне посилання на суперклас для доступу до його членів.

4. Спадкування інтерфейсу

4.1. Реалізація кількох інтерфейсів

Хоча класи можуть успадковувати лише один клас, вони можуть реалізовувати кілька інтерфейсів.

Уявіть собі, що ArmoredCar, який ми визначили в попередньому розділі, потрібний для супершпигуна. Тому компанія-виробник автомобілів подумала про додавання функцій польоту та плавання:


public
interface
Floatable



void
floatOnWater();


>

public
interface
Flyable



void
fly();


>

public
class
ArmoredCar
extends
Car
implements
Floatable,
Flyable


public
void
floatOnWater()



System.out.println("I can float!");


>



public
void
fly()



System.out.println("I can fly!");


>


>

У наведеному вище прикладі ми помітили використання ключового слова "реалізує" для успадкування від інтерфейсу.

4.2. Проблеми з множинним успадкуванням

Java допускає множинне наслідування з використанням інтерфейсів.

До Java 7 це було проблемою.Інтерфейси могли визначати лише абстрактні методи, тобто методи без будь-якої реалізації. Тому, якщо клас реалізовував кілька інтерфейсів з тією самою сигнатурою методу, це було проблемою. Зрештою реалізуючий клас мав реалізувати лише один метод.

Давайте подивимося, як це просте рівняння змінилося з введенням в інтерфейси методів за замовчуванням Java 8.

Починаючи з Java 8, інтерфейси могли визначати за промовчанням для своїх методів (інтерфейс, як і раніше, може визначати абстрактні методи). Це означає, що якщо клас реалізує кілька інтерфейсів, які визначають методи з однією і тією самою сигнатурою, дочірній клас успадкує окремі реалізації. Це звучить складно та не дозволено.

Java забороняє успадкування кількох реалізацій тих самих методів, визначених окремих інтерфейсах.


public
interface
Floatable



default
void
repair()



System.out.println("Repairing Floatable object");


>


>

public
interface
Flyable



default
void
repair()



System.out.println("Repairing Flyable object");


>


>

public
class
ArmoredCar
extends
Car
implements
Floatable,
Flyable



// this won't compile


>

Якщо ми дійсно хочемо реалізувати обидва інтерфейси, нам доведеться перевизначити метод repair().

Якщо інтерфейси в попередніх прикладах визначають змінні з однаковими іменами, скажімо, duration, ми не зможемо отримати до них доступу, якщо перед ім'ям змінної не буде вказано ім'я інтерфейсу:


public
interface
Floatable



int
duration
=
10;


>

public
interface
Flyable



int
duration
=
20;


>

public
class
ArmoredCar
extends
Car
implements
Floatable,
Flyable




public
void
aMethod()



System.out.println(duration);
// won't compile


System.out.println(Floatable.duration);
// outputs 10


System.out.println(Flyable.duration);
// outputs 20


>


>

4.3. Інтерфейси, що розширюють інші інтерфейси

Інтерфейс може розширювати кілька інтерфейсів. Ось приклад:


public
interface
Floatable



void
floatOnWater();


>

interface
interface
Flyable



void
fly();


>

public
interface
SpaceTraveller
extends
Floatable,
Flyable



void
remoteControl();


>

Інтерфейс успадковує інші інтерфейси за допомогою ключового слова extends .

5. Спадкування типу

Коли клас успадковує інший клас або інтерфейси, крім успадкування їх членів, він також успадковує їх тип.

Це дуже потужна концепція, яка дозволяє розробникам програмувати інтерфейс (базовий клас чи інтерфейс) , а не програмування їх реалізації.

Наприклад, уявіть умову, при якій організація веде список автомобілів, що належать її співробітникам. Звичайно, у всіх співробітників можуть бути різні моделі автомобілів.


public
class
Employee



private
String
name;


private
Car
car;



// standard constructor


>

Оскільки всі похідні класи Car успадковують тип Car, на екземпляри похідних класів можна посилатися за допомогою змінної класу Car:


Employee
e1
=
new
Employee("Shreya",
new
ArmoredCar());


Employee
e2
=
new
Employee("Paul",
new
SpaceCar());


Employee
e3
=
new
Employee("Pavni",
new
BMW());

6. Приховані члени класу

6.1.

Що станеться, якщо і в суперкласі, і в підкласі буде визначено змінну або метод з однаковим ім'ям ?Не хвилюйся; ми все ще можемо отримати доступ до них обом.

Ключове слово this відноситься до екземпляра, в якому воно використовується.


public
class
ArmoredCar
extends
Car



private
String
model;


public
String
getAValue()



return
super.model;
// returns значення modelу, визначене в class base Car


// return this.model; // will return value of model defined in ArmoredCar


// return model; // will return value of model defined in ArmoredCar


>


>

Багато розробників використовують ключові слова this і super, щоб явно вказати, на яку змінну чи метод вони посилаються. Однак використання їх з усіма членами може зробити наш код захаращеним.

6.2. Приховані статичні члени

Що відбувається, коли наш базовий клас та підкласи визначають статичні змінні та методи з однаковими іменами ? Чи можемо ми отримати доступ до статичного члена з базового класу у похідному класі так само, як ми робимо це для змінних екземплярів?

Розберемося на прикладі:


public
class
Car



public
static
String
msg()



return
"Car";


>


>

public
class
ArmoredCar
extends
Car



public
static
String
msg()



return
super.msg();
// this won't compile.


>


>

Ні, ми не можемо. Статичні члени належать класу, а чи не екземплярам. Тому ми можемо використовувати нестатичне ключове слово super в msg() .

Оскільки статичні члени належать до класу, ми можемо змінити попередній виклик наступним чином:

Розглянемо наступний приклад, у якому і базовий клас, і похідний клас визначають статичний метод msg() з тією самою сигнатурою:


public
class
Car



public
static
String
msg()



return
"Car";


>


>

public
class
ArmoredCar
extends
Car



public
static
String
msg()



return
"ArmoredCar";


>


>

Ось як ми можемо їх назвати:


Car
first
=
new
ArmoredCar();


ArmoredCar
second
=
new
ArmoredCar();

Для попереднього коду first.msg() виведе "Car", а second.msg() виведе "ArmoredCar". Статичне повідомлення, яке викликається, залежить від типу змінної, яка використовується для посилання на екземпляр ArmoredCar.

7. Висновок

У цій статті ми розглянули ключовий аспект Java - успадкування.

Ми побачили, як Java підтримує одиночне успадкування з класами та множинне успадкування з інтерфейсами, та обговорили тонкощі того, як цей механізм працює у мові.

Як завжди, повний вихідний код прикладів доступний на GitHub.

Схожі статті

  • Що краще гумові чи поліуретанові колеса
  • Чи можна мед при відмові від солодкого
  • Що таке Струмовіддача акумулятора
  • Чи можна їздити на зимовій гумі з шипами що випали
  • Чим прибрати стоматит на мові
  • Де знайти інформацію про максимальну оперативну пам'ять що підтримується
  • Димові шашки від комарів
  • Як називаються помилки в мові
  • Недавні статті

  • Чому взуття скрипить при ходьбі
  • Коли день народження у стрічці
  • Чи можна кішці їсти сіль
  • Варіанти планування ділянки 15 соток прямокутної форми
  • Що означає півмісяця знак
  • Рейсмусовий верстат для чого
  • У якому віці парують свиней
  • У чому полягає принцип нарахування та у яких випадках він застосовується