Java — язык, в котором почти весь код живёт внутри классов. Поэтому объектно-ориентированное программирование (ООП) тут не «один из стилей», а основа, с которой вы работаете каждый день. Разберём по шагам, из чего оно состоит и зачем каждая часть нужна.
Зачем вообще ООП
Когда программа растёт, главная проблема — не «как написать логику», а «как не запутаться». ООП предлагает простую идею: сгруппировать данные и действия над ними в один объект и спрятать детали внутри. Снаружи виден небольшой набор операций, а как именно всё устроено — личное дело объекта.
Короткая формула: класс — это чертёж, объект — конкретный предмет по этому чертежу.
Классы и объекты
Класс описывает, какие у предмета есть данные (поля) и что он умеет (методы). Объект — конкретный экземпляр, созданный по классу через new.
public class Account {
private long id; // поле: данные объекта
private long balance; // баланс в копейках
public void deposit(long amount) { // метод: действие
balance += amount;
}
public long getBalance() {
return balance;
}
}
Account acc = new Account(); // создали объект
acc.deposit(500);
System.out.println(acc.getBalance()); // 500
Каждый объект хранит свои собственные значения полей: два Account — это два независимых баланса.
Конструкторы
Конструктор — особый метод, который вызывается при создании объекта. Он нужен, чтобы объект сразу появлялся в корректном состоянии, а не пустым.
public class Account {
private final long id;
private long balance;
public Account(long id, long balance) { // конструктор
this.id = id;
this.balance = balance;
}
}
Account acc = new Account(1, 1000); // id и баланс заданы сразу
Если конструктор не написать, Java добавит пустой конструктор без аргументов. Как только вы объявили свой — пустой больше не создаётся автоматически. Слово this тут означает «текущий объект» и помогает отличить поле от параметра с тем же именем.
Инкапсуляция и модификаторы доступа
Инкапсуляция — это сокрытие внутренностей объекта. Поля делают private, а доступ дают через методы. Так объект сам контролирует свои данные и не даёт привести себя в недопустимое состояние.
public class Account {
private long balance;
public void withdraw(long amount) {
if (amount > balance) {
throw new IllegalArgumentException("Недостаточно средств");
}
balance -= amount; // снять больше, чем есть, нельзя
}
}
Модификаторы доступа управляют видимостью:
private— только внутри своего класса.- (без модификатора) — внутри того же пакета (package-private).
protected— пакет плюс классы-наследники.public— отовсюду.
Практическое правило: делайте всё максимально закрытым и открывайте ровно то, что действительно нужно снаружи.
Наследование
Наследование (extends) позволяет одному классу взять поля и методы другого и добавить своё. Базовый класс называют родителем (суперклассом), производный — наследником (подклассом).
public class Animal {
public String sound() {
return "...";
}
}
public class Dog extends Animal {
@Override
public String sound() { // переопределяем поведение родителя
return "Гав";
}
}
Аннотация @Override не обязательна, но желательна: она проверяет, что вы действительно переопределяете метод родителя, а не создаёте новый по ошибке (например, из-за опечатки в имени).
Наследованием не стоит злоупотреблять: оно жёстко связывает классы. Часто гибче не наследоваться, а держать другой объект внутри как поле (это называют композицией).
Интерфейсы
Интерфейс описывает, что объект умеет, не говоря как. Это контракт: список методов без тела. Класс обещает его выполнить через implements.
public interface Notifier {
void send(String message); // только сигнатура, без реализации
}
public class EmailNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("Письмо: " + message);
}
}
Один класс может реализовать несколько интерфейсов — в отличие от наследования, где родитель только один. Это главный способ давать классам общие «способности», не выстраивая жёсткую иерархию.
default-методы
Интерфейс может дать методу готовую реализацию через default. Тогда все, кто реализует интерфейс, получают её бесплатно и при желании переопределяют.
public interface Notifier {
void send(String message);
default void sendAll(List<String> messages) { // готовая реализация
messages.forEach(this::send);
}
}
default придумали, чтобы можно было добавлять методы в существующие интерфейсы, не ломая код всех, кто их уже реализовал.
Абстрактные классы
Абстрактный класс (abstract) — нечто среднее между обычным классом и интерфейсом. Его нельзя создать через new, он служит общей заготовкой: часть методов реализована, часть оставлена наследникам.
public abstract class Shape {
public abstract double area(); // нет тела — реализует наследник
public String describe() { // общий код для всех фигур
return "Площадь: " + area();
}
}
public class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
Когда выбирать что: интерфейс — когда нужен только контракт и общие способности (наследовать можно много интерфейсов); абстрактный класс — когда у наследников есть общее состояние (поля) и общий готовый код.
Полиморфизм
Полиморфизм означает «много форм»: переменная типа родителя или интерфейса может ссылаться на объект любого наследника, и вызовется его собственная версия метода. Вызывающий код не знает и не должен знать, какой именно объект перед ним.
List<Animal> animals = List.of(new Dog(), new Animal());
for (Animal a : animals) {
System.out.println(a.sound()); // "Гав", затем "..."
}
Это и есть сила ООП: добавили новый класс Cat extends Animal — и цикл выше работает с ним без единой правки. Какой метод вызвать, Java решает в момент выполнения по фактическому типу объекта.
record — краткий неизменяемый класс данных
Часто класс нужен просто чтобы хранить набор значений. Раньше для этого приходилось вручную писать поля, конструктор, геттеры, equals, hashCode, toString. record делает всё это за вас одной строкой.
public record Point(int x, int y) {}
Point p = new Point(3, 4);
System.out.println(p.x()); // 3 — геттер называется как поле
System.out.println(p); // Point[x=3, y=4] — готовый toString
Point p2 = new Point(3, 4);
System.out.println(p.equals(p2)); // true — сравнение по значениям
Поля record неизменяемы (final): после создания значения не меняются. Это делает record идеальным для передачи данных — DTO, ключей в Map, результатов методов. В record можно добавить и проверки в конструкторе:
public record Point(int x, int y) {
public Point { // компактный конструктор
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Координаты неотрицательны");
}
}
}
enum — перечисление
enum задаёт фиксированный набор именованных значений. Это надёжнее, чем хранить строки или числа: компилятор не даст использовать значение, которого нет в списке.
public enum Status {
NEW, PAID, SHIPPED, CANCELLED
}
Status s = Status.PAID;
if (s == Status.PAID) {
System.out.println("Заказ оплачен");
}
enum — это полноценный класс: ему можно добавить поля, конструктор и методы, чтобы привязать к каждому значению дополнительные данные.
public enum Planet {
EARTH(9.8), MARS(3.7); // у каждого значения своя гравитация
private final double gravity;
Planet(double gravity) { // конструктор enum
this.gravity = gravity;
}
public double gravity() {
return gravity;
}
}
Коротко
- Класс — чертёж (поля + методы), объект — экземпляр по чертежу, создаётся через
new; конструктор задаёт стартовое состояние. - Инкапсуляция: поля
private, доступ через методы; держите всё максимально закрытым. - Наследование (
extends) — взять и расширить другой класс; родитель один. Не злоупотребляйте, часто лучше композиция. - Интерфейс (
implements) — контракт «что умеет»; их можно реализовать много, естьdefault-методы с готовым телом. - Абстрактный класс — заготовка с общим кодом и состоянием, нельзя создать напрямую.
- Полиморфизм — один вызов работает с любым наследником; новые типы добавляются без правки старого кода.
- record — краткий неизменяемый класс данных (готовые геттеры,
equals,hashCode,toString); enum — фиксированный набор значений, при необходимости с полями и методами.
Что почитать дальше
- Синтаксис и типы данных — переменные, примитивы,
varи базовые конструкции языка. - Коллекции —
List,Set,Mapи как хранить группы объектов. - Дженерики — типобезопасные классы и методы, на которых построены коллекции.