Об’єктно-орієнтоване програмування в Java: база, яку варто опанувати початківцям в ІТ

8 Вересня 2025

Цей підхід використовує багато популярних мов програмування, зокрема Java. Але початківці не завжди розуміють, як реалізується ООП в Java та які переваги це дає в процесі написання коду. За порадами експертів NIX пропонуємо розібрати основні принципи ООП в Java та познайомитися з best practices, щоб власноруч створювати якісний код.

Об’єктно-орієнтоване програмування в Java: база, яку варто опанувати початківцям в ІТ

Що таке об’єктно-орієнтоване програмування

Об’єктно-орієнтоване програмування — це підхід до програми не як набору команд, а як цілісної моделі з об’єктів, які взаємодіють. Самі об’єкти є реалізацією класів. Клас можна порівняти з кресленням будинку, а об’єкт — із будинком за цим кресленням. За одним шаблоном можна звести однакові за структурою будівлі, але різні в деталях: оздоблення, колір, меблі тощо. Ці принципи ООП для Java є базою — все обертається навколо класів та об’єктів.

Класи та відповідно об’єкти мають дані (стан, поля) й поведінку (методи). Наприклад, для інтернет-магазину можна створити клас Product, додати поля name і price і прописати метод applyDiscount(), який застосовує знижку. В окремих об’єктах різним чином будуть реалізовані дані та поведінка: у одного продукту одні назва, ціна й знижка, у іншого — інші. Це важливий момент для розуміння реалізації ООП в джава. Втім, детальніше про об’єкти та класи — нижче.

Навіщо це все?

  • Для віддзеркалення предметної області. З об’єктно-орієнтованим програмуванням код є не абстрактним, а зрозумілим. Ви бачите, як, наприклад, User оформив Order на Product. Тобто код говорить мовою бізнесу. До того ж дані й поведінка знаходяться поруч. Тому розробникам простіше працювати.
  • Для масштабування продукту. У коді на Java згідно з ООП ви можете повторно використовувати класи — це називається спадкування. Воно працює разом із композицією, що зрештою дозволяє збирати нові поведінки із наявних блоків. Плюс ви оминаєте непотрібне дублювання коду.
  • Для інкапсуляції. Кожен об’єкт можна відв’язати й приховати від інших частин програми. Тобто у вас є контроль доступу до станів. Через це інші частини коду не можуть змінити змінні. Тож менше випадкових зламаних інваріантів, а еволюція коду стає більш передбачуваною.

Переваги та недоліки ООП в Java

  • Спрощення підтримки й розвитку проєктів. Код стає читабельнішим. З одного боку, як зазначено, ви чітко розумієте предметність елементів та зв’язок із бізнес-логікою. З іншого, та сама інкапсуляція призводить до логічного розділення коду на модулі.
  • Можливість підміни реалізацій. В ООП існує поліморфізм — один інтерфейс можна використати для різних реалізацій. Це надає Dependency Injection, DI: клас не створює залежності, а отримує їх ззовні. Це робить код менш зв’язаним і більш тестованим (бо легко підставляти моки та фейки).
  • ООП-екосистема. Java будувалась навколо цього підходу до програмування. Тому бібліотеки й фреймворки, інструменти та JVM вже заточені під використання перерахованих принципів. Завдяки цьому абстракції виглядають природними, а патерни проєктування покривають типові задачі.

Однак досвідченні Java-розробники відзначають і недоліки об’єктно-орієнтованого програмування:

  • Надмірне проєктування. Початківці часом захоплюються створенням абстракцій та ієрархії класів «на всі випадки». Інколи такий підхід призводить до появи абстрактних фабрик фабрик! Це забирає багато часу розробника, ускладнює код, уповільнює рефакторинг і онбординг на проєкті.
  • Уповільнення. Java природно вимагає витрат часу на абстракції, віртуальні виклики, роботу збирачів сміття, сканування класів при холодному старті, обробку об’єктних графів тощо. Для більшості завдань це не критично, але в деяких випадках процедурний код трохи швидший.
  • Недоречність у певних випадках. Об’єктно-орієнтоване програмування несе в собі певну церемоніальність. Але для простих задач, на кшталт нескладних обчислень чи скриптів, це зайве. В таких випадках ООП в джава буде непотрібним, і краще його замінити тим же процедурним стилем (про це — нижче).

Класи та об’єкти в Java

Клас та об’єкт є головними сутностями Java. Про кожне з цих понять треба розповісти окремо та з прикладами застосування об’єктно-орієнтованого програмування.

Клас у Java

Клас — це базовий шаблон. «Креслення» визначає властивості та поведінку об’єктів. У вивченні об’єктно-орієнтованого програмування в Java все починається з класів — навіть метод main() знаходиться всередині класу.

Базовий приклад кода за моделлю ООП — нижче. Тут ми створюємо клас Dog для опису собаки, додаємо поля для прізвиська й віку, а також метод bark() для опису того, як ця тварина гавкає:

public class Dog {

    String name;

    int age;

    void bark() {

        System.out.println(name + ” каже: Гав-гав!”);

    }

}

Обмеження доступу

Класи дозволяють впроваджувати в код принципи ООП в Java, про які написано вище. Наприклад, в класі легко приховати дані від зовнішнього втручання. Для цього треба задати поля private та надати доступ через геттери та сеттери.

public class BankAccount {

    private double balance;

    public double getBalance() {

        return balance;

    }

    public void deposit(double amount) {

        if (amount > 0) balance += amount;

    }

}

Наслідування

За стандартами об’єктно-орієнтованого програмування на основі одних класів можна створювати інші класи — це є наслідування. Наприклад, є базовий клас опису тварини:

public class Animal {

    void eat() {

        System.out.println(“Я їм”);

    }

}

public class Cat extends Animal {

    void meow() {

        System.out.println(“Мяу!”);

    }

}

А потім можна його наслідувати для нових:

Cat cat = new Cat();

cat.eat();  // успадковано з Animal

cat.meow(); // свій метод

Поліморфізм

Класи відмінно підходять для поліморфізму, коли через один інтерфейс ви створюєте різні реалізації. Це норма застосування ООП в Java. Спочатку прописуєте в коді щось на кшталт такого.

public class Animal {

    void makeSound() {

        System.out.println(“Тварина видає звук”);

    }

}

public class Dog extends Animal {

    void makeSound() {

        System.out.println(“Гав!”);

    }

}

public class Cat extends Animal {

    void makeSound() {

        System.out.println(“Мяу!”);

    }

}

Після цього можна працювати з об’єктами через спільний тип (тут це Animal), але не знаючи точного підтипу:

Animal a = new Dog();

a.makeSound(); // Гав!

a = new Cat();

a.makeSound(); // Мяу!

Абстракція

Класи необхідні й для абстракцій і приховування складності. Наприклад, не можна напряму створити абстрактний клас Shape, але можна задати метод getArea(), який будуть реалізовувати всі форми:

public abstract class Shape {

    abstract double getArea();

}

public class Circle extends Shape {

    double radius;

    public Circle(double r) { radius = r; }

    double getArea() {

        return Math.PI * radius * radius;

    }

}

Об’єкт у Java

Об’єктами називають конкретні екземпляри реалізації класу. Якщо є клас Car, то об’єктами будуть окремі машини car1 та car2, зроблені за шаблоном, але різні за наповненням. Відрізнятися будуть поля для опису стану (атрибути або змінні) та методи (поведінка об’єкта). Для створення екземпляра використовують оператор new.

Car car1 = new Car();

Він викликає конструктор потрібного класу, виділяє пам’ять для об’єкта та повертає посилання на нього. Якщо повернутися до прикладу із собаками, код виглядав би так:

public class Main {

    public static void main(String[] args) {

        Dog dog1 = new Dog();

        dog1.name = “Рекс”;

        dog1.age = 4;

        Dog dog2 = new Dog();

        dog2.name = “Лакі”;

        dog2.age = 2;

        dog1.bark(); // Рекс каже: Гав!

        dog2.bark(); // Лакі каже: Гав!

    }

}

Як бачимо, кожен об’єкт має свої стан і поведінку, враховуючи конкретні контексти.

Прояви об’єктно-орієнтованого програмування в об’єктах

В об’єктах ключові принципи ООП для Java проявляються також повною мірою. Почати з інкапсуляції. У коді нижче кожен об’єкт має окремий баланс з обмеженим доступом.

public class BankAccount {

    private double balance;

    public void deposit(double amount) {

        if (amount > 0) balance += amount;

    }

    public double getBalance() {

        return balance;

    }

}

Наслідування ж виглядає ще простіше:

Animal a = new Dog();

a.makeSound(); // Поліморфна поведінка

Щодо поліморфізму, то різні об’єкти можуть по-різному реагувати на одні методи:

List<Animal> zoo = List.of(new Dog(), new Cat(), new Cow());

for (Animal a : zoo) {

    a.makeSound(); // Кожен видає свій звук

}

А завдяки абстракції ви можете ховати внутрішню реалізацію та працювати лише з інтерфейсом:

Shape s = new Circle(3.0);

System.out.println(s.getArea()); // Не важливо, як саме обчислюється площа

Важливо розуміти різницю між класами та об’єктами ще й у питанні пам’яті. Шаблон її не займає, а екземпляр завжди має виділену пам’ять. Втім, нюансів у роботі з об’єктами багато. Наприклад, початківцям потрібно буде розібратися із життєвим циклом об’єктів, їх масивами, конструкторами, передачею екземплярів в методи тощо.

Методи та параметри методів у Java

Класи й об’єкти — основа Java. Та лише ними об’єктно-орієнтоване програмування не обмежується. Існує багато сутностей, але в базовому гайді слід виділити методи та їх параметри. Це є ключовим для розуміння структури програм за принципами ООП.

Метод — це блок коду, який належить класу та прописує дію об’єкта. У метода можуть бути параметри (вхідні значення) та тип повернення (вихідні дані). Можуть відрізнятися й самі методи: екземплярні та статичні, приватні й публічні тощо. Методи є центровими для ООП в Java, адже будь-яка програма зрештою виконує якісь дії. Тобто ці блоки організовують бізнес-логіку, і їх можна порівняти з функціями в інших мовах.

Для методів у Java є шаблон структури:

[модифікатори доступу] [тип повернення] [назваМетоду]([параметри]) {

    // тіло методу

    return щось; // якщо потрібно

}

Найпростіший метод може виглядати наступним чином. Тут public є модифікатором доступу (конкретно цей говорить, що метод доступний всім), int вказує на тип повернення, а sum виступає назвою методу. Ну, а (int a, int b) та return a + b; — це список параметрів та результат виконання відповідно:

public int sum(int a, int b) {

    return a + b;

}

Щодо до виклику методу в об’єкті, в нашому випадку знадобиться наступний код:

public class MathUtils {

    public int sum(int a, int b) {

        return a + b;

    }

}

public class Main {

    public static void main(String[] args) {

        MathUtils math = new MathUtils();

        int result = math.sum(3, 4);

        System.out.println(result); // 7

    }

}

Без параметрів:

public void sayHello() {

    System.out.println(“Привіт!”);

}

Один параметр:

public void greet(String name) {

    System.out.println(“Привіт, ” + name + “!”);

}

Кілька параметрів:

public double average(int x, int y, int z) {

    return (x + y + z) / 3.0;

}

Типи повернення в Java також різняться: від відсутності повернення (позначають void) до різних значень для різних задач. Наприклад, це можуть бути int, String, boolean, double та об’єкти:

public void showMessage() {

    System.out.println(“Привіт, світ!”);

}

public int getNumber() {

    return 42;

}

У цьому випадку параметри передаються за значення. Для примітивних типів (int, boolean, double) надається копія, а для об’єктів — копія посилання на об’єкт. Через це зміни всередині об’єкта видно назовні. Погляньмо на приклад коду з об’єктом:

public void rename(Dog d) {

    d.name = “Шарик”;

}

Dog dog = new Dog();

dog.name = “Барбос”;

rename(dog);

System.out.println(dog.name); // Шарик

На відміну від C++ або Python, які теж побудовані на ООП, Java не дозволяє створювати параметри за замовчуванням. Але для цього можна створити перевантаження:

public void greet() {

    greet(“друже”);

}

public void greet(String name) {

    System.out.println(“Привіт, ” + name + “!”);

}

Основні принципи об’єктно-орієнтованого програмування в Java

Тут головне: зрозуміти, як цей підхід працює та чим відрізняється від інших ООП-мов програмування, на кшталт C#, C++, Python, Ruby.

  • Майже чисте ООП. У Java все, окрім примітивів, є об’єктами. Не тільки класи, масиви, інтерфейси, а навіть GUI-компоненти, потоки і колекції є екземплярами. Це утримує баланс між продуктивністю (бо примітиви швидше) та структурою. Приклад іншого: у Ruby все є об’єктами, а в C++ можна обійтись і без них.
  • Сувора типізація. Java не дозволяє додавати змінні різного типу, як у Python чи JavaScript. Все має чітко визначений тип, який задля зниження помилок перевіряється під час компіляції. З одного боку, так безпечніше. З іншого, через такі прояви об’єктно-орієнтованого програмування Java є менш гнучкою.
  • Відсутність множинного спадкування. В Java один клас може успадкуватися лише від одного класу — на відміну від C++, де це поширена практика. Такий підхід робить Джаву не дуже гнучкою, але спрощує ієрархію класів. Втім, за необхідності множинне спадкування можна організувати через інтерфейси.
  • Чітке розділення відповідальності. Якщо це не клас, то це — інтерфейс. Така догма є важливою для Java. В інших ООП-мовах можуть бути інші конструкції. Наприклад, Python та Ruby мають міксини, вставні поведінки, а в C++ використовуються шаблони для реалізації деяких рис дженериків і міксинів.
  • Організація через класи. Вище було зазначено: методи мають бути всередині класів. Доволі структурований і зрозумілий підхід з ООП! Та це сильно відрізняється, наприклад, від C++, де функції можуть існувати поза класами, та Python, яка дозволяє писати буквально все в глобальному просторі.
  • Певні обмеження дженериків. В Java є узагальнені типи — дженерики. Але вони працюють інакше, ніж у C++. Наприклад, після компіляції типи стираються. Немає й повноцінного поліморфізму для дженериків. Це підвищує зручність, але частина потужності шаблонів із C++ втрачається.
  • Контрольована рефлексія. Зазвичай в Java ООП дозволяє перевіряти та змінювати класи під час виконання — завдяки підтримці reflection API. Проте це потребує дозволів та має ряд обмежень. Для порівняння: у Python ви можете змінювати будь-що в будь-який момент, що значно підвищує гнучкість розробки.
  • Автоматичне управління пам’яттю. Невіддільною частиною JVM є збирачі сміття — Garbage Collectors, скорочено GC. Вони самі видаляють непотрібні об’єкти. Це економить час і зменшує витрати на помилки з пам’яттю. У C++, наприклад, треба вручну звільняти пам’ять (хоча так і більше контролю).
  • Кросплатформність. Принципи ООП в Java реалізовані так, аби вона не була прив’язана до операційної системи. Завдяки згаданій JVM, віртуальній машині, код компілюється у байткод. Тому ви отримуєте повну платформну незалежність — це зручно для сучасного світу та дуже ефективно.

Об’єктно-орієнтоване програмування vs процедурне програмування в Java

Хоча в джаві все зав’язано на класи та немає вільних функцій, це не виключає можливості застосування інших підходів — зокрема процедурного. Це модель, в якій програма складається з функцій, і процес виглядає як послідовне виконання інструкцій над даними. Відтворити це у Java фактично неможливо. Але можна писати у процедурному стилі з використанням static-методів без об’єктів взагалі. Це антипатерн для Джави, але інколи він має сенс. Доказ — наступний приклад:

public class Calculator {

    public static int add(int a, int b) {

        return a + b;

    }

}

Ключовим є static. Дані передаються відкрито між функціями, а не ховаються в об’єкті. Повторне використання можливе через копіювання функцій або передавання даних. Також при процедурному програмуванні елементи знаходяться в одному глобальному середовищі, і складно щось змінити та масштабувати. Через очевидні недоліки на фоні ООП Java розробники застосовують інший підхід рідко. Він виправданий, наприклад, лише для маленьких скриптів і утиліт та алгоритмів пошуку й сортування даних. Тобто там, де модель об’єктно-орієнтоване програмування втрачає ефективність і сенс.

10 кращих практик написання ООП-коду в Java

  1. Використовуйте інкапсуляцію. Не варто відкривати поля напряму, через public, — робіть поля private. Краще застосовувати get та set. Це дозволить завжди контролювати доступ до даних, додавати валідацію та зберігати стабільність коду при зміні внутрішньої структури.
  2. Уникайте зайвих статичних методів. Для static є чітка сфера використання — для утиліт, на кшталт Math.sqrt(), та констант. В інших випадках статичні методи не потрібні, бо порушують принципи ООП: прив’язують логіку до класу (а не об’єкту), не підтримують поліморфізм та ускладнюють тестування.
  3. Працюйте на рівні інтерфейсів. Новачки нерідко починають писати на рівні реалізацій. Наприклад, ArrayList<String> names = new ArrayList<>(); замість List<String> names = new ArrayList<>();. Краще інтерфейси — так код гнучкіший і зручніший для тестів, і можна змінити реалізацію.
  4. Уникайте God Object. У початківців є спокуса створити об’єкт чи клас, який виконує все одразу. Наприклад, рахує знижку на товар, зберігає дані й надсилає листа. Але це заскладна структура. Краще дотримуватися принципу SRP, сінгл-відповідальності: кожен клас/об’єкт відповідає за одну дію.
  5. Робіть пріоритет на композицію. Спадкування є гарним згідно з правилами ООП. Але тут з’являється тісна залежність, що ускладнює підтримку коду та продукту. Тому за можливості краще використовувати композицію. Вона додає гнучкості, коли ви можете замінити компонент без модифікацій ієрархії.
  6. Не забувайте фіналити. Коли є можливість, варто додавати final до всіх блоків. Тоді ніхто не зможе наслідувати клас, перевизначати метод або змінити змінну після присвоєння. Це створює додатковий захист від помилок, бо код на Java за моделлю ООП стає більш передбачуваним.
  7. Спрощуйте класи. Це про те правило, що один клас — одна відповідальність, але під іншим кутом. За вимогами ООП у джава код має бути насамперед зрозумілим! Якщо у ньому сотні рядків, треба виправити це. Наприклад, розбити на підкласи, допоміжні об’єкти або сервіси й утиліти.
  8. Пишіть самодокументований код. Поширена проблема початківців: вони ніби економлять символи у назвах. А потім вимушені додавати пояснення, що ж відбувається в коді. Це грубе порушення стандартів ООП в Java. Назви мають бути очевидними — одразу та для всіх.
  9. Не заглушайте винятки. Часто першою реакцією розробників при появі винятків стає заглушка. Але для реалізації об’єктно-орієнтованого програмування у Java це неправильно. Винятки є цілком натуральною частиною логіки. Тому треба або обробляти їх правильним чином, або передавати далі.
  10. Тестуйте свій код. Насамперед треба писати юніт-тести для логіки. Але цей пункт краще розповсюджувати на все, що ви робите у коді. Все має бути тестованими — через інтерфейси, малі класи, DI тощо. Варто вивчити та використовувати такі інструменти, як JUnit або Mockito.

Не поспішайте вивчати складні концепції, відточуйте базові навички

Засвоївши вищеперераховані практики, можна створювати цілком стабільні системи, та й загалом розуміти філософію мови, об’єктну модель як таку, зробити свій код структурованим, читабельним, уникати типових помилок. Знаючи базові принципи об’єктно-орієнтованого програмування, ви також зможете пізніше перейти до SOLID, DRY чи KISS, до правил чистого коду й використання паттернів проєктування. Для початківців — це дійсно важливі навички, варті того, щоб вказати їх у резюме. До того ж, яку б мову програмування ви не обрали, ці практики є загальними для всіх.