Отношения между классами в Java — это способы, которыми один класс связан с другим: наследует его тип, реализует интерфейс, хранит его объект внутри себя или обращается к нему на время выполнения метода. От выбора связи зависит, что произойдет с зависимыми объектами при изменении или удалении главного.
Содержание
Все связи делятся на две большие группы. Отношение «is-a» («является») — когда один тип — частный случай другого: собака является животным. Отношение «has-a» («имеет») — когда один объект хранит другой как свою часть: у машины есть двигатель. Ниже разберем пять конкретных видов связи с кодом на Java и с тем, как их рисуют на диаграмме классов UML.
Сводная таблица видов связей
Связи отличаются силой: от жесткой (наследование, композиция) до самой слабой временной (зависимость). Ключевой практический признак — что стало бы с объектом B, если удалить объект A.
| Связь | Группа | Ключевой признак | Ключевое слово Java | UML-нотация словами |
|---|---|---|---|---|
| Наследование | is-a | подкласс наследует тип и поведение суперкласса | extends |
сплошная линия с полым треугольником к родителю |
| Реализация интерфейса | is-a (контракт) | класс обязуется реализовать методы интерфейса | implements |
пунктирная линия с полым треугольником к интерфейсу |
| Композиция | has-a (сильная) | часть не живет без целого, целое ее и создает | поле-объект, создан внутри | сплошная линия с закрашенным ромбом у целого |
| Агрегация | has-a (слабая) | часть живет независимо, передается извне | поле-объект, передан в конструктор | сплошная линия с полым (незакрашенным) ромбом у целого |
| Ассоциация | связь-ссылка | объекты знают друг о друге, живут независимо | поле-ссылка | сплошная линия, иногда со стрелкой направления |
| Зависимость | использование | объект приходит только в параметр/локально, не хранится | параметр метода | пунктирная линия с открытой стрелкой |
Наследование (is-a)
Наследование — механизм, при котором новый класс (подкласс) получает поля и методы существующего (суперкласса) и может их дополнять или переопределять. Читается как «является»: Dog является Animal.
class Animal {
protected String name;
Animal(String name) {
this.name = name;
}
void eat() {
System.out.println(name + " ест");
}
}
class Dog extends Animal {
Dog(String name) {
super(name); // вызов конструктора родителя
}
void bark() {
System.out.println(name + " лает");
}
}
Объект Dog умеет и то, что задано в Animal, и свое:
Dog dog = new Dog("Рекс");
dog.eat(); // Рекс ест (метод достался от Animal)
dog.bark(); // Рекс лает (собственный метод Dog)
Важное ограничение: в Java нет множественного наследования классов — у класса ровно один прямой суперкласс через extends. Если явного родителя нет, им становится Object. Наследовать поведение от нескольких источников можно только через интерфейсы (ниже).
Реализация интерфейса
Интерфейс — это контракт: набор сигнатур методов без реализации (в современной Java допустимы и default-методы с телом). Класс через implements обязуется предоставить реализацию. Класс может реализовать сразу несколько интерфейсов — так Java обходит запрет множественного наследования классов.
interface Swimmer {
void swim(); // только сигнатура, без тела
}
class Duck extends Animal implements Swimmer {
Duck(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(name + " плывет");
}
}
Различаем:
extendsнаследует готовую реализацию от одного класса;implementsберет только обязательство реализовать методы, но интерфейсов может быть много.Duckодновременно являетсяAnimal(наследование) и являетсяSwimmer(контракт).
Композиция и агрегация (has-a)
Обе связи описывают «целое и часть»: объект хранит другой объект как свое поле. Различаются они временем жизни части.
Различаем композицию и агрегацию: при композиции часть не существует отдельно от целого — целое ее создает и уничтожает вместе с собой. При агрегации часть существует независимо, ее передают в готовом виде, и после удаления целого она продолжает жить.
Композиция — двигатель создается внутри машины и без нее не существует:
class Engine {
void start() {
System.out.println("Двигатель запущен");
}
}
class Car {
// часть создается ВНУТРИ целого -> композиция
private final Engine engine = new Engine();
void start() {
engine.start();
System.out.println("Машина поехала");
}
}
Здесь никто снаружи не передает Engine: он рождается вместе с Car и исчезает, когда Car становится недостижимым для сборщика мусора.
Агрегация — клуб состоит из студентов, но студенты заведены отдельно и живут сами по себе:
import java.util.List;
class Student {
private final String name;
Student(String name) {
this.name = name;
}
String getName() {
return name;
}
}
class Club {
// часть ПЕРЕДАЕТСЯ извне -> агрегация
private final List<Student> members;
Club(List<Student> members) {
this.members = members;
}
int size() {
return members.size();
}
}
Студенты созданы раньше клуба и переживут его роспуск:
Student a = new Student("Аня");
Student b = new Student("Борис");
Club chess = new Club(List.of(a, b));
System.out.println(chess.size()); // 2
// клуб можно распустить, объекты a и b остаются валидными
Оговорка о границе модели: в Java нет деструкторов, и «уничтожение части» — это про логику владения и достижимость объекта для сборщика мусора, а не про ручное освобождение памяти. Композиция и агрегация — способ выразить намерение владения в коде, а не жесткое правило языка.
Ассоциация и зависимость
Эти две связи не про «часть целого», а про то, что один объект знает о другом или пользуется им.
Различаем ассоциацию и зависимость: при ассоциации объект хранит ссылку на другой в поле — связь долгоживущая. При зависимости чужой объект приходит только как параметр метода (или локальная переменная) и нигде не сохраняется — связь существует лишь на время вызова.
Ассоциация — у курса есть закрепленный преподаватель, ссылка хранится в поле:
class Teacher {
final String name;
Teacher(String name) {
this.name = name;
}
}
class Course {
private Teacher teacher; // хранимая ссылка -> ассоциация
void assign(Teacher t) {
this.teacher = t;
}
String teacherName() {
return teacher == null ? "не назначен" : teacher.name;
}
}
И Course, и Teacher создаются и живут независимо; они не часть друг друга, но связаны ссылкой. Агрегация — это, по сути, частный случай ассоциации со смыслом «целое-часть».
Зависимость — самая слабая связь: класс использует другой тип, но не держит его в себе.
class Report {
String asText() {
return "Отчет за сентябрь";
}
}
class ReportPrinter {
// объект приходит только в параметр и не сохраняется -> зависимость
void print(Report report) {
System.out.println(report.asText());
}
}
ReportPrinter не имеет поля Report: как только print завершится, связь пропадет. На UML это самая тонкая линия — пунктир с открытой стрелкой.
Как выбрать связь: короткий алгоритм
- Один тип — частный случай другого («является»)? Наследование (
extends) или реализация интерфейса (implements). - Нужны поведения из нескольких источников? Интерфейсы, а не наследование.
- Один объект — неотъемлемая часть другого и создается внутри него? Композиция.
- Один объект хранит другой, но тот заведен отдельно и живет сам? Агрегация.
- Объекты просто ссылаются друг на друга как равные? Ассоциация.
- Чужой объект нужен лишь на время одного метода? Зависимость (параметр).
Практическое правило проектирования — «предпочитать композицию наследованию»: композиция гибче, потому что не привязывает класс к чужой иерархии типов и позволяет менять поведение через подмену вложенного объекта.
Выводы
- Связи делятся на «is-a» (наследование, реализация интерфейса) и «has-a» (композиция, агрегация), плюс более слабые ассоциация и зависимость.
- В Java нет множественного наследования классов (
extends— один родитель), но интерфейсов черезimplementsможно реализовать сколько угодно. - Композиция и агрегация различаются временем жизни части: в композиции часть создается внутри целого и не живет без него, в агрегации — передается извне и существует независимо.
- Ассоциация хранит ссылку в поле (долгая связь), зависимость получает объект только в параметр метода (связь на время вызова).
- При равных вариантах композицию предпочитают наследованию: меньше жесткой привязки к чужой иерархии.
Где применяется / связь с практикой
Выбор связи между классами — это ежедневная работа при проектировании любого Java-приложения: от модели предметной области до слоя сервисов. Ошибка на этом уровне (например, наследование там, где нужна композиция) позже оборачивается хрупким кодом, который трудно менять. Именно поэтому ООП-связи спрашивают на собеседованиях на позицию Java-разработчика.
Освойте тему на практике
Разобраться с ООП на практике и довести навык до уровня, когда выбор связи очевиден, помогает системное обучение с код-ревью. Основы синтаксиса, классов и объектных связей закладывает курс Java Basic от Otus. Прежде чем записываться, полезно послушать открытые уроки Otus — там разбирают реальные задачи и показывают формат занятий.
FAQ
Агрегация и композиция — это виды ассоциации?
Да. Обе описывают связь «целое-часть» и считаются частными случаями ассоциации. Композиция при этом строже: часть принадлежит только одному целому и разделяет его время жизни.
Можно ли одновременно наследовать класс и реализовать интерфейсы?
Да. Класс объявляют как class A extends B implements C, D: ровно один суперкласс через extends и любое число интерфейсов через implements.
Чем зависимость отличается от ассоциации на практике?
Признак — хранится ли ссылка. Если объект лежит в поле класса, это ассоциация; если приходит только в параметр метода и не сохраняется, это зависимость.



