Между классами в Java есть три основных отношения. Наследование это связь «является» (IS A): потомок наследует поля и методы родителя, например
Truck extends Car. Композиция и агрегирование это связь «является частью» (HAS A): объект содержит другие объекты. Они различаются строгостью: при композиции часть принадлежит только одному целому (двигатель у машины), при агрегировании часть может входить в несколько целых (пассажиры в машине).
Привет! Сегодня мы подробно рассмотрим еще один принцип объектно-ориентированного программирования (ООП), наследование. Заодно изучим другие типы отношений между классами, композицию и агрегирование.
Кратко
- Между классами есть три основных отношения: наследование, композиция и агрегирование.
- Наследование это связь «является» (IS A): потомок наследует поля и методы родителя (например,
Truck extends Car).
- Главная выгода наследования это повторное использование кода: общее выносят в родителя.
- Композиция и агрегирование это связь «является частью» (HAS A): объект содержит другие объекты.
- Композиция это строгая связь: часть не может принадлежать другому объекту (двигатель у машины).
- Агрегирование это свободная связь: объект может входить в несколько других (пассажиры, студент в кружках).
Эта тема не будет сложной: ты уже много раз сталкивался с наследованием и его примерами в прошлых лекциях. Сегодня главным будет закрепить твои знания, подробнее рассмотреть механизм наследования и еще раз пробежаться по примерам :)
Итак, поехали!
Наследование в Java и его преимущества
Как ты наверняка помнишь,
наследование (inheritance) это механизм, который позволяет описать новый класс на основе существующего (родительского). При этом свойства и функциональность родительского класса заимствуются новым классом.
Давай вспомним пример наследования из предыдущих лекций:
public class Car {
private String model;
private int maxSpeed;
private int yearOfManufacture;
public Car(String model, int maxSpeed, int yearOfManufacture) {
this.model = model;
this.maxSpeed = maxSpeed;
this.yearOfManufacture = yearOfManufacture;
}
public void gas() {
}
public void brake() {
}
}
public class Truck extends Car {
public Truck(String model, int maxSpeed, int yearOfManufacture) {
super(model, maxSpeed, yearOfManufacture);
}
}
public class Sedan extends Car {
public Sedan(String model, int maxSpeed, int yearOfManufacture) {
super(model, maxSpeed, yearOfManufacture);
}
}
Есть некая программа, в рамках которой мы работаем с различными типами автомобилей. Даже если ты не автолюбитель, наверняка знаешь, что типов этих самых автомобилей на свете великое множество :)
Поэтому общие свойства автомобилей выделяем в общий класс-родитель,
Car. А что общего у всех автомобилей вне зависимости от типа?
У любой машины есть год выпуска, название модели и максимальная скорость. Эти свойства выносим в поля
model,
maxSpeed,
yearOfManufacture.
Что касается поведения, любая машина может газовать и тормозить :) Это поведение мы определяем в методах
gas() и
brake().
Какие выгоды это нам дает? Прежде всего, сокращение объема кода.
Конечно, можем обойтись и без родительского класса. Но поскольку каждая машина должна уметь газовать и тормозить, нам придется создавать методы
gas() и
brake() в классе
Truck, в классе
Sedan, в классе
F1Car, в классе
Sportcar и во всех остальных классах машин.
Представь, сколько лишнего кода мы при этом напишем. Не забывай и о полях model, maxSpeed и yearOfManufacture: если откажемся от родительского класса, будем создавать их в каждом из классов-машин!
![Без родительского класса общие поля и методы пришлось бы дублировать в каждом классе]()
Когда у нас наберется пара десятков классов-машин, объем повторяющегося кода станет действительно серьезным.
Вынесение общих полей и методов (еще говорят «состояния» и «поведения») в класс-родитель позволит нам сэкономить кучу времени и места.
Если же у какого-то типа есть свойства или методы, уникальные только для него и отсутствующие у других типов машин, не беда. Их всегда можно создать в классе-потомке, отдельно от всех остальных.
public class F1Car extends Car {
public void pitStop() {
}
public static void main(String[] args) {
F1Car formula1Car = new F1Car();
formula1Car.gas();
formula1Car.pitStop();
formula1Car.brake();
}
}
Возьмем случай с гоночными машинами Формулы-1. У них, в отличие от «сородичей», есть уникальное поведение: время от времени они заезжают на пит-стоп.
Нам это не мешает. Общее поведение мы уже описали в родительском классе
Car, а специфическое поведение классов-потомков можем добавить внутри классов.
![Уникальное поведение потомка (пит-стоп у F1Car) добавляют в сам класс-потомок]()
Это касается и полей: если у дочернего класса есть уникальные свойства, спокойно объявляем эти поля внутри него и не переживаем :)
Возможность повторного использования кода это главное преимущество наследования.
Для программиста очень важно не писать лишний объем кода. Ты не раз столкнешься с этим в работе. Как именно вызывается конструктор родителя через
super(), подробно разобрано в статье
Конструкторы базовых классов.
Пожалуйста, запомни еще одну крайне важную вещь:
в Java нет множественного наследования. Каждый класс наследуется только от одного класса. О причинах этого подробнее поговорим в будущих лекциях, пока просто запомни.
Этим Java, кстати, отличается от некоторых других ООП-языков. Например, в С++ множественное наследование есть.
С наследованием все более-менее ясно, идем дальше.
Композиция и агрегирование
Классы и объекты могут быть связаны друг с другом. Наследование описывает связь «является» (или по-английски «IS A»). Лев является Животным. Такое отношение легко выразить с помощью наследования, где
Animal будет родительским классом, а
Lion потомком.
Однако не все связи отношения в мире описываются таким образом. К примеру, клавиатура определенно как-то связана с компьютером, но
она не является компьютером. Руки как-то связаны с человеком, но они не являются человеком.
В этих случаях в его основе лежит другой тип отношения: не «является», а «является частью» («HAS A»). Рука не является человеком, но является частью человека. Клавиатура не является компьютером, но является частью компьютера.
Отношения HAS A можно описать в коде, используя механизмы
композиции и
агрегирования. Разница между ними заключается в «строгости» этих связей.
Приведем простой пример:
У нас есть наш
Car, машина.
У каждой машины есть двигатель.
Кроме того, у каждой машины есть пассажиры внутри.
В чем же принципиальная разница между полями
Engine engine и
Passenger [] passengers? Если у машины внутри сидит пассажир
А, это не значит, что в ней не могут находиться пассажиры
B и
C.
Одна машина может соответствовать нескольким пассажирам. Кроме того, если всех пассажиров высадить из машины, она продолжит спокойно функционировать.
Связь между классом
Car и массивом пассажиров
Passenger [] passengers менее строгая. Она называется
агрегацией.
Есть неплохая статья на эту тему:
Отношения между классами (объектами).
В ней приведен еще один хороший пример агрегации. Допустим, у нас есть класс
Student, обозначающий студента, и класс
StudentsGroup (группа студентов). Студент может входить и в клуб любителей физики, и в студенческий фан-клуб «Звездных войн» или команду КВН.
Композиция это более строгий тип связи. При использовании композиции объект не только является частью какого-то объекта, но и не может принадлежать другому объекту того же типа.
Самый простой пример это двигатель автомобиля. Двигатель является частью автомобиля, но не может быть частью другого автомобиля.
Как видишь, их связь гораздо более строгая, чем у
Car и
Passengers.
![Композиция это строгая связь часть-целое, агрегирование более свободная]()
Все три отношения между классами удобно свести в таблицу:
| Отношение | Тип связи | Строгость | Пример |
| Наследование | «является» (IS A) | потомок это разновидность родителя | Truck это Car; Lion это Animal |
| Композиция | «является частью» (HAS A) | строгая: часть принадлежит только одному целому | двигатель у машины |
| Агрегирование | «является частью» (HAS A) | свободная: часть может входить в несколько целых | пассажиры в машине; студент в кружках |
Больше о наследовании можно почитать в
документации Oracle (Inheritance).
Читайте также
Вопросы и ответы
Какие бывают отношения между классами в Java?
Три основных: наследование (связь «является», IS A), композиция и агрегирование (обе связь «является частью», HAS A). Композиция и агрегирование различаются строгостью.
Что такое наследование в Java?
Это механизм, позволяющий описать новый класс на основе существующего (родительского), заимствуя его поля и методы. Например,
Truck extends Car. Главная выгода это повторное использование кода.
Чем композиция отличается от агрегирования?
И то, и другое это связь «является частью» (HAS A). При композиции часть принадлежит только одному объекту и не может быть частью другого (двигатель у машины). При агрегировании связь свободнее: объект может входить в несколько других (пассажиры, студент в кружках).
Что такое связь IS A и HAS A?
IS A («является») это наследование: потомок это разновидность родителя (лев это животное). HAS A («является частью») это композиция или агрегирование: один объект содержит другой (у машины есть двигатель).
Есть ли в Java множественное наследование?
Нет. Каждый класс наследуется только от одного класса. В этом Java отличается, например, от C++, где множественное наследование есть.
Когда использовать наследование, а когда композицию?
Наследование, когда один класс это разновидность другого (IS A). Композицию или агрегирование, когда один объект содержит другой как часть (HAS A), например у машины есть двигатель и пассажиры.
кодаслов, но в результате выходит нагромождение бессмысленных определений