Основы ООП. Принципы.
Часто встечающиюся вопросы о Java
Основы ООП (Объектно-Ориентированного Программирования)
ООП — это парадигма программирования, основанная на концепции “объектов”, которые содержат данные (поля) и методы для работы с ними. В Java ООП реализуется через 4 основных принципа (инкапсуляция, наследование, полиморфизм, абстракция) и 5 принципов SOLID.
① Инкапсуляция (Encapsulation)
Смысл: Сокрытие внутренней реализации объекта и предоставление безопасного доступа через методы.
Как реализуется:
- Использование private полей.
- Доступ через public геттеры/сеттеры.
Пример:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
private String name; // Поле скрыто
public String getName() { // Геттер
return name;
}
public void setName(String name) { // Сеттер
if (name != null) {
this.name = name;
}
}
}
Зачем?
- Контроль над изменением данных (например, валидация в сеттере).
- Гибкость (можно изменить внутреннюю логику без изменения кода снаружи).
② Наследование (Inheritance)
Смысл: Создание новых классов на основе существующих (переиспользование кода).
Как реализуется:
- Ключевое слово extends.
- Класс-потомок наследует поля и методы родителя.
Пример:
1
2
3
4
5
6
7
8
9
10
11
class Animal {
void eat() {
System.out.println("Eating...");
}
}
class Dog extends Animal { // Dog наследует Animal
void bark() {
System.out.println("Barking...");
}
}
Зачем?
- Уменьшение дублирования кода.
- Иерархическая организация классов.
③ Полиморфизм (Polymorphism)
Смысл: Возможность использовать объекты разных классов через общий интерфейс.
Виды:
- Перегрузка методов (compile-time) — один метод с разными параметрами.
- Переопределение методов (runtime) — дочерний класс меняет поведение метода родителя.
Пример:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Bird {
void fly() {
System.out.println("Flying...");
}
}
class Penguin extends Bird {
@Override
void fly() { // Переопределение метода
System.out.println("Sorry, I can't fly!");
}
}
public class Main {
public static void main(String[] args) {
Bird bird = new Penguin(); // Полиморфизм
bird.fly(); // Вызовется переопределённый метод
}
}
Зачем?
- Гибкость кода (один интерфейс — множество реализаций).
- Поддержка LSP (принцип подстановки Барбары Лисков).
④ Абстракция (Abstraction)
Смысл: Сокрытие сложной реализации и предоставление упрощённого интерфейса.
Как реализуется:
- Абстрактные классы (abstract class).
- Интерфейсы (interface).
Пример:
1
2
3
4
5
6
7
8
9
10
abstract class Vehicle {
abstract void start(); // Абстрактный метод
}
class Car extends Vehicle {
@Override
void start() {
System.out.println("Car started!");
}
}
Зачем?
- Упрощение работы с сложными системами.
- Определение контракта для классов.
Интерфейс vs абстрактный класс, чем отличаются
Оба понятия используются для абстракции в Java, но имеют принципиальные отличия:
1. Основные различия
Характеристика | Интерфейс (interface) | Абстрактный класс (abstract class) |
---|---|---|
Реализация методов | До Java 8: только абстрактные. С Java 8+: default и static методы. | Может содержать абстрактные и реализованные методы. |
Поля | Только public static final (константы). | Любые поля (в т.ч. private, protected). |
Наследование | Класс может реализовывать много интерфейсов. | Класс наследует только один абстрактный класс. |
Конструкторы | Не может иметь конструкторов. | Может иметь конструкторы (для инициализации полей). |
Модификаторы доступа | Методы по умолчанию public. | Методы могут быть protected, private и т.д. |
Когда использовать | Для определения контракта (что объект умеет делать). | Для частичной реализации общей логики. |
2. Примеры
Интерфейс
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Flyable {
void fly(); // Абстрактный метод (не имеет реализации)
default void land() { // default-метод (Java 8+)
System.out.println("Landing...");
}
static boolean isFast(int speed) { // static-метод (Java 8+)
return speed > 1000;
}
}
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("Bird is flying");
}
}
Абстрактный класс
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
abstract class Animal {
protected String name; // Поле с модификатором доступа
public Animal(String name) { // Конструктор
this.name = name;
}
abstract void makeSound(); // Абстрактный метод
void sleep() { // Обычный метод
System.out.println(name + " is sleeping");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
void makeSound() {
System.out.println("Woof!");
}
}
Когда что использовать?
Интерфейс:
- Нужно определить контракт для разных классов (например, Comparable, Serializable).
- Требуется множественное наследование (класс может реализовать несколько интерфейсов).
- Хотите добавить функциональность через default-методы (Java 8+).
Абстрактный класс:
- Есть общая логика для наследников (например, шаблонный метод).
- Нужны нестатические поля с разными модификаторами доступа.
- Требуется конструктор для инициализации полей.
Важные нюансы С Java 8+ интерфейсы стали мощнее:
- default-методы — позволяют добавлять новые методы без ломания существующих реализаций.
1 2 3 4 5
interface Flyable { default void takeOff() { System.out.println("Taking off..."); } }
- static-методы — полезны для утилитных функций.
1 2 3 4 5
interface MathUtils { static int square(int x) { return x * x; } }
С Java 9+ интерфейсы могут иметь:
- private-методы — для разбивки сложных default-методов.
1 2 3 4 5 6 7 8 9 10
interface Logger { default void log(String message) { prepareLog(message); System.out.println(message); } private void prepareLog(String message) { // Вспомогательная логика } }
Главный выбор
- Если нужно определить “что можно делать” → интерфейс.
- Если нужно описать “что есть” и частично “как делать” → абстрактный класс.
Пример комбинирования:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
abstract class Bird implements Flyable {
abstract void buildNest(); // Своя абстракция
@Override
public void fly() {
System.out.println("Flapping wings");
}
}
class Eagle extends Bird {
@Override
void buildNest() {
System.out.println("Building nest on a cliff");
}
}
Можно ли переопределить статический метод в Java?
Короткий ответ: Нет, статический метод нельзя переопределить (override), но можно скрыть (hide).
Разберёмся подробнее, в чём разница между переопределением (override) и сокрытием (hide).
Переопределение (Override) vs Сокрытие (Hide) ① Переопределение (Override)
- Работает только для нестатических (instance) методов.
- Полиморфизм: вызывается метод на основе типа объекта (runtime).
- Требует:
- Одинаковое имя и сигнатура метода.
- Аннотация @Override (необязательно, но рекомендуется).
② Сокрытие (Hide)
- Работает только для статических методов.
- Нет полиморфизма: вызывается метод на основе типа переменной (compile-time).
- Требует:
- Одинаковое имя и сигнатура метода.
- Ключевое слово static.
Почему статические методы нельзя переопределить?
- Статические методы принадлежат классу, а не объекту.
- Они не участвуют в полиморфизме (выбор метода определяется на этапе компиляции, а не выполнения).
- Если бы переопределение было возможно, это нарушило бы принцип раннего связывания (early binding).
Можно ли перегрузить (overload) статический метод?
Да! Перегрузка (overload) — это создание метода с тем же именем, но другой сигнатурой.
Смысл инкапсуляции в ООП
- Сокрытии внутренней реализации объекта от внешнего мира.
- Контролируемом доступе к данным через публичные методы (геттеры/сеттеры).
- Защите данных от некорректного изменения.
Зачем?
- Безопасность — предотвращение прямого доступа к полям класса.
- Гибкость — возможность изменить внутреннюю логику без изменения кода снаружи.
- Контроль — добавление проверок при изменении данных (например, валидация в сеттере).