Post

Основы ООП. Принципы.

Часто встечающиюся вопросы о 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) — это создание метода с тем же именем, но другой сигнатурой.


Смысл инкапсуляции в ООП

  • Сокрытии внутренней реализации объекта от внешнего мира.
  • Контролируемом доступе к данным через публичные методы (геттеры/сеттеры).
  • Защите данных от некорректного изменения.

Зачем?

  • Безопасность — предотвращение прямого доступа к полям класса.
  • Гибкость — возможность изменить внутреннюю логику без изменения кода снаружи.
  • Контроль — добавление проверок при изменении данных (например, валидация в сеттере).
This post is licensed under CC BY 4.0 by the author.