Початок подорожі в Java для новачків

pic

Фото від Yannick Pipke на Unsplash

Що таке Java?

Java — це мова програмування, яка широко використовується для створення додатків, що працюють на різних пристроях (комп'ютерах, телефонах, вбудованих системах). Вона відома своєю портабельністю ("напиши один раз, запускай скрізь").

Чому Java?

  • Надійна та безпечна
  • Працює на більшості пристроїв та операційних систем
  • Використовується в корпоративних системах, мобільних додатках, іграх та веб-сервісах

Практичні застосування Java включають:

  • Розробка веб-додатків: Створення серверних додатків.
  • Мобільні додатки: Підтримка Android додатків.
  • Великі дані: Використання фреймворків, таких як Hadoop.
  • IoT: Java популярна для смарт-пристроїв.

Основні концепції

Основні характеристики Java

  • Об'єктно-орієнтована (Object-Oriented): Все обертається навколо "об'єктів" та "класів".
  • Незалежність від платформи (Platform-Independent): Програми на Java працюють на Java Virtual Machine (JVM), що робить їх кросплатформенними.
  • Простота синтаксису (Simple Syntax): Легко читати та розуміти порівняно з іншими мовами.

Блоки побудови Java

  • Класи та об'єкти (Classes and Objects): Клас (Class) — це як шаблон (наприклад, "Автомобіль"), за допомогою якого можна створювати об'єкти. Об'єкт (Object) — це екземпляр класу (наприклад, "мійАвтомобіль"). Класи визначають стан (state) об'єктів (тобто їхні атрибути та характеристики) через оголошення полів, а також їхню поведінку (behaviour) (тобто дії, які можуть виконувати об'єкти) через оголошення методів (methods).
class Car {  
 String color; // оголошення поля  
 void drive() { // оголошення методу  
 System.out.println("Car is driving");  
 }  
}  

public class Main {  
 public static void main(String[] args) {  
 Car myCar = new Car(); // створення об'єкта  
 myCar.color = "Red";  
 myCar.drive();  
 }  
}
  • Змінні (Variables): Контейнери для зберігання даних.
int age = 25; // Ціле число  
String name = "John"; // Текст

Основні блоки побудови Java

  • JVM (Java Virtual Machine): Виконує програми на Java.
  • JRE (Java Runtime Environment): Надає бібліотеки для запуску програм на Java.
  • JDK (Java Development Kit): Інструменти для розробки програм на Java.

Базова програма на Java

  • Приклад "Hello, World!"
public class Main {  
 public static void main(String[] args) {  
 System.out.println("Hello, World!");  
 }  
}
  • public class: Визначає клас.
  • main метод (method): Точка входу програми. JVM починає виконувати код цього методу, коли ви запускаєте свій додаток.
  • System.out.println: Виводить текст на екран.

Що таке змінні?

Змінна (Variable) — це як контейнер з міткою, в якому можна зберігати дані для подальшого використання. Уявіть її як коробку з назвою для зберігання інформації. Наприклад, змінна age може зберігати ваш вік:

int age = 30;

Оголошення та ініціалізація змінної

Щоб використовувати змінну в Java, потрібно:

  1. Оголосити її тип (Declare its type): Які дані вона буде зберігати.
  2. Дати їй ім'я (Give it a name): Щоб ви могли звертатися до неї пізніше.
    Ці перші два кроки називаються оголошенням змінної (declaration).
  3. Опційно, присвоїти їй початкове значення (assign it an initial value).
    Це називається ініціалізацією змінної (initialisation). Оголошення та ініціалізація часто йдуть разом в одному рядку.
type variableName = value;  

// Наприклад  
int number = 10;  
// Оголошує змінну 'number' типу 'int' та присвоює їй значення 10.

Присвоєння значення та звертання до змінної

Ви можете пізніше присвоїти змінній інше значення (присвоєння (assignment)) або використовувати змінну для отримання її значення (звертання (reference)), наприклад, якщо хочете вивести її значення на екран.

number = 25; // присвоєння  
System.out.println("The value of number is " + number); // звертання

Типи даних в Java

Типи даних визначають, які дані може зберігати змінна. Java має дві основні категорії:

1.

Примітивні типи даних (Primitive Data Types)

Це базові типи для простих значень.

+---------+--------------------------+--------------------------+---------+  
| Тип     | Опис                     | Приклад                  | Розмір  |  
+---------+--------------------------+--------------------------+---------+  
| int     | Цілі числа               | int age = 25;            | 4 байти |  
| double  | Дробові числа             | double price = 19.99;    | 8 байт  |  
| char    | Один символ               | char grade = 'A';        | 2 байти |  
| boolean | Логічні значення (True/False) | boolean isDone = true; | 1 біт  |  
| long    | Дуже великі цілі числа    | long stars = 123456789L; | 8 байт  |  
| float   | Менші дробові числа       | float pi = 3.14f;        | 4 байти |  
| byte    | Дуже малі цілі числа      | byte small = 120;        | 1 байт  |  
| short   | Малі цілі числа           | short temp = -32000;     | 2 байти |  
+---------+--------------------------+--------------------------+---------+

2. Типи даних за посиланням (Reference Data Types)

Ці типи використовуються для складніших об'єктів, таких як рядки (strings), масиви (arrays) або класи (classes).

String name = "John Doe";

Приклади використання змінних

Зверніть увагу на такі прості правила при створенні імен змінних.

  • Має починатися з маленької літери (або з $ чи _, але краще уникати цих варіантів).
  • Не може містити пробілів або спеціальних символів.
  • Імена регістрозалежні (case-sensitive): myAge відрізняється від myage.
  • Використовуйте змістовні імена для зрозумілості.
int count = 10; // Хороше описове ім'я  
int c = 10; // Погане ім'я! Не описове  

int apples = 5; // Зберігає цілі числа  
double price = 3.99; // Зберігає дробові числа  

String greeting = "Hello, World!"; // Зберігає текст  

boolean isLoggedIn = false; // Зберігає значення true/false  

char initial = 'J'; // Зберігає одну літеру

Змінні можуть зберігати деталі компанії, такі як назва, реєстраційний номер, дата заснування, річний оборот і активний статус. У наступних фрагментах коду вкажіть різні типи використання змінних (оголошення (declaration), ініціалізація (initialisation), присвоєння (assignment) і звертання (reference)).

// Деталі компанії  
String companyName;  
String registrationNumber;  
companyName = "Tech Innovations Ltd";  
registrationNumber = "12345678";  
String incorporationDate = "2020-05-15"; // Збережено як рядок для простоти  
double annualTurnover = 2500000.50; // Річний оборот у GBP  
boolean isActive = true; // Чи є компанія активною  

// Виведення інформації про компанію  
System.out.println("Company Name: " + companyName);  
System.out.println("Registration Number: " + registrationNumber);  
System.out.println("Incorporation Date: " + incorporationDate);  
annualTurnover = 0.0;  
System.out.println("Annual Turnover: £" + annualTurnover);  
isActive = false;  
System.out.println("Active: " + isActive);

Вступ до масивів (Introduction to Arrays)

Масив (Array) в Java — це колекція коробок, де можна зберігати кілька значень одного типу під одним ім'ям. Замість того, щоб створювати окремі змінні для кожного значення, можна використовувати масив для ефективного управління ними.

Технічно кажучи, масив — це список фіксованого розміру, що зберігає нуль (пустий масив) або більше значень одного типу даних. Наприклад:

  • Список імен студентів.
  • Щоденні температури за тиждень.
  • Сет цін на продукти в магазині.

Чому використовувати масиви?

  1. Групування пов'язаних значень (Group related values together): Зберігайте кілька елементів без створення окремих змінних.
  2. Легше керувати (Easier to manage): Швидко отримувати доступ, оновлювати або обробляти дані.
  3. Ефективно (Efficient): Зменшує надмірність у вашому коді.

Оголошення та ініціалізація масиву

  1. Оголосіть масив (Declare an array): Вкажіть тип даних, які він буде зберігати.
    2.
    текст перекладу
    Ініціалізація масиву: Встановіть розмір (кількість елементів) і надайте початкові значення.
int[] numbers = new int[5]; // Створює масив для 5 цілих чисел зі значенням 0  
int[] numbers = {10, 20, 30, 40, 50}; // Масив з 5 заздалегідь заданими значеннями

Доступ до елементів масиву

Ви можете отримати доступ до окремих елементів масиву, використовуючи їх індекс в квадратних дужках. Індекси масиву починаються з 0.

int[] numbers = {10, 20, 30, 40, 50};  
System.out.println(numbers[0]); // Виведе: 10 (перший елемент)  
System.out.println(numbers[2]); // Виведе: 30 (третій елемент)

Оновлення значень масиву

Ви можете змінювати значення елемента, використовуючи його індекс в дужках і присвоюючи нове значення, як звичайній змінній.

int[] numbers = {10, 20, 30, 40, 50};  
numbers[1] = 25; // Змінює другий елемент (індекс 1) на 25  
System.out.println(numbers[1]); // Виведе: 25

Довжина масиву та цикли для проходу масивом

Щоб обробити всі елементи масиву, використовуйте цикл, званий цикл для кожного елемента (for-each loop).

int[] numbers = {10, 20, 30, 40, 50};  
for (int i : numbers) {  
 System.out.println(numbers[i]);  
}

Якщо вам потрібен більший контроль над процесом циклу, ви можете отримати загальну кількість елементів масиву за допомогою властивості length, і потім пройтись по кожному індексу від 0 до загальної кількості елементів.

int[] numbers = {10, 20, 30, 40, 50};  
for (int i = 0; i < numbers.length; i++) { // `numbers.length` дає розмір масиву  
 System.out.println(numbers[i]);  
}

Типи масивів

Одновимірні масиви
Простий список значень.

String[] fruits = {"Apple", "Banana", "Cherry"};

Багатовимірні масиви
Уявіть таблицю з рядками та стовпцями (наприклад, матриця).

int[][] matrix = {  
 {1, 2, 3},   
 {4, 5, 6}  
};  
System.out.println(matrix[1][2]); // Виведе: 6 (другий рядок, третій стовпець)

Потік керування в Java

Потік керування в Java визначає порядок виконання інструкцій в програмі. Java надає інструменти, як умовні оператори, цикли та оператор switch, для контролю послідовності дій залежно від певних умов або для повторення дій.

Умовні оператори

Умовні оператори дозволяють програмі приймати рішення на основі умов.

Оператор if

Оператор if перевіряє, чи є умова істинною, і виконує код відповідно до цього.

boolean isActive = true;  

if (isActive) {  
 System.out.println("Компанія активна.");  
}

Оператор if-else

Надає два шляхи: один, якщо умова істинна, і інший, якщо хибна.

double turnover = 50000;  

if (turnover > 100000) {  
 System.out.println("Велика компанія.");  
} else {  
 System.out.println("Мала або середня компанія.");  
}

Оператор if-else-if

Цей оператор дозволяє перевіряти кілька умов послідовно. Він оцінює кожну умову по черзі, поки одна з них не стане істинною, після чого пропускає решту. Якщо жодна умова не є істинною, опційний блок else може обробити випадок за замовчуванням.
текст перекладу
Пам'ятайте, що іноді можна (і бажано) використовувати оператор switch замість кількох вкладених операторів if.

double turnover = 750_000;  

if (turnover < 250_000) {  
 System.out.println("Мікро компанія.");  
} else if (turnover <= 500_000) {  
 System.out.println("Мала компанія.");  
} else if (turnover <= 1_000_000) {  
 System.out.println("Середня компанія.");  
} else {  
 System.out.println("Велика компанія.");  
}

Оператор switch

Оператор switch вибирає дії в залежності від значення змінної.

String companyType = "Limited";  

switch (companyType) {  
 case "LLP":  
 System.out.println("Це партнерство.");  
 break;  
 case "Limited":  
 System.out.println("Це обмежена компанія.");  
 break;  
 case "Public":  
 System.out.println("Це публічна компанія.");  
 break;  
 default:  
 System.out.println("Невідомий тип компанії.");  
}

Вираз switch

Поки оператор switch використовується для вибору між кількома діями в залежності від значення змінної, вираз switch вибирає між кількома можливими значеннями, які можуть бути повернуті в залежності від значення змінної.

Використовуйте вираз switch, коли:

  • Потрібно оцінити змінну з кількома можливими значеннями.
  • Хочете уникнути написання багатьох операторів if-else if, щоб призначити значення.
  • Хочете чистий і простий спосіб призначити або повернути одне з багатьох можливих значень.
public class Main {  
 public static void main(String[] args) {  
 String companyType = "Public";  

 String message = switch (companyType) {  
 case "LLP" -> "Це партнерство.";  
 case "Limited" -> "Це обмежена компанія.";  
 case "Public" -> "Це публічна компанія.";  
 default -> "Невідомий тип компанії.";  
 };  
 System.out.println(message);  
 }  
}

Вирази switch також можуть обробляти складнішу логіку, наприклад, кілька значень, що відповідають одному випадку.

String companyType = "Limited";  

switch (companyType) {  
 case "LLP":  
 System.out.println("Це партнерство.");  
 break;  
 case "Limited", "Private", "Ltd":  
 System.out.println("Це приватна компанія.");  
 break;  
 case "Public", "PLC":  
 System.out.println("Це публічна компанія.");  
 break;  
 default:  
 System.out.println("Невідомий тип компанії.");  
}

Перевірка типів за допомогою switch

Java дозволяє використовувати перевірку шаблонів (pattern matching), що означає, що ви можете перевіряти тип змінної і використовувати його як визначений тип безпосередньо в операторі switch.

Object data = 100;  

String result = switch (data) {  
 case String s -> "Це рядок зі значенням: " + s;  
 case Integer i -> "Це ціле число зі значенням: " + i;  
 default -> "Невідомий тип.";  
};  

System.out.println(result);

Цикли

Цикли повторюють блок коду кілька разів.

Цикл for

Цикл for використовується, коли ви знаєте, скільки разів потрібно повторити дію.

String[] regNumbers = {"12345678", "87654321", "34567890"};  

for (int i = 0; i < regNumbers.length; i++) {  
 System.out.println("Номер реєстрації компанії: " + regNumbers[i]);  
}

Цикл while

Цикл while повторюється, поки задана умова є істинною. Він перевіряє умову перед виконанням циклу. Якщо умова хибна, тіло циклу може ніколи не виконатись.

int count = 1;  

while (count <= 3) {  
 System.out.println("Обробка запису " + count);  
 count++;  
}

Цикл do-while

Як і цикл while, цикл do-while повторюється, поки задана умова є істинною. Однак він завжди виконує тіло циклу хоча б один раз перед перевіркою умови. Навіть якщо умова хибна, тіло циклу виконується хоча б один раз.

int turnover = 0;  

do {  
 System.out.println("Обробка обігу: " + turnover);  
 turnover -= 50000;  
} while (turnover > 100000);

Розширений цикл for (for-each Loop)

Цей цикл спрощує перебір елементів масиву чи колекції в Java.
текст перекладу
На відміну від традиційного циклу for, він не потребує керування змінною індексу.

String[] companies = {"Company A", "Company B", "Company C"};  

// Традиційний цикл for  
for (int i = 0; i < companies.length; i++) {  
 System.out.println(companies[i]);  
}  

// Розширений цикл for  
for (String company : companies) {  
 System.out.println(company);  
}

Об'єктно-орієнтоване програмування в Java

Java побудована на концепції об'єктно-орієнтованого програмування (OOP), що допомагає організувати код, моделюючи реальні об'єкти як об'єкти.

Об'єкти та класи

Клас — це шаблон для створення об'єктів. Об'єкт представляє конкретний екземпляр класу з його станом (поля) та поведінкою (методи).

// Оголошення класу  
public class Company {  
 String name; // Атрибут (дані)  
 String registrationNumber;  
 double turnover;  

 // Метод (поведінка)  
 public void displayDetails() {  
 System.out.println("Назва компанії: " + name);  
 System.out.println("Номер реєстрації: " + registrationNumber);  
 System.out.println("Оборот: £" + turnover);  
 }  
}

Класи використовуються для створення об'єктів через інстанціацію, використовуючи оператор new. Ви можете доступатися до стану та викликати поведінку, використовуючи оператор ..

public class Main {  
 public static void main(String[] args) {  
 Company company1 = new Company(); // Створення об'єкта  
 company1.name = "Tech Innovations Ltd";  
 company1.registrationNumber = "12345678";  
 company1.turnover = 500000.00;  

 company1.displayDetails(); // Виклик методу на об'єкті  
 }  
}

Конструктори

У Java конструктор — це спеціальний метод, який використовується для створення (або “конструювання”) об'єкта. Уявіть, що це процес реєстрації нової компанії в реєстрі компаній — так само, як ви повинні заповнити дані компанії при реєстрації, конструктор налаштовує початкові властивості об'єкта, коли він створюється.

При створенні об'єкта в Java, зазвичай потрібно надавати значення для певних атрибутів. Конструктор дозволяє вказати ці значення, щоб об'єкт починав із правильними, осмисленими даними.

public class Company {  
 String name;  
 int yearEstablished;  

 // Конструктор  
 public Company(String name, int yearEstablished) {  
 this.name = name;  
 this.yearEstablished = yearEstablished;  
 }  
}

Тепер, коли ви оголосили конструктор, клас можна інстанціювати лише після надання всієї необхідної інформації.

public class Main {  
 public static void main(String[] args) {  
 Company company = new Company("Tech Innovators Ltd", 2022);  
 System.out.println("Назва компанії: " + company.name);  
 System.out.println("Рік заснування: " + company.yearEstablished);  
 }  
}

Принципи OOP

1. Інкапсуляція

Інкапсуляція захищає дані, обмежуючи прямий доступ до них та надаючи контрольовані способи їх зміни через методи доступу (getters) та методи встановлення (setters).

public class Company {  
 private String name; // Приватний для запобігання прямому доступу  

 // Метод доступу  
 public String getName() {  
 return name;  
 }  

 // Метод встановлення  
 public void setName(String newName) {  
 if (newName != null && !newName.isEmpty()) {  
 name = newName;  
 }  
 }  
}

Завдяки інкапсуляції, ви безпечно отримуєте доступ та змінюєте дані.

Company company = new Company();  
company.setName("Secure Data Ltd");  
System.out.println("Назва компанії: " + company.getName());

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

Наслідування дозволяє одному класу успадковувати стан та поведінку від іншого.
Наприклад, LimitedCompany може успадковувати від загального Company.

public class Company {  
 String name;  
 String registrationNumber;  
}  

public class LimitedCompany extends Company {  
 double shares; // Додатковий атрибут  
}

3. Поліморфізм

Поліморфізм дозволяє використовувати один інтерфейс для взаємодії з різними типами об'єктів.
текст перекладу
Різні типи об'єктів можуть поводитись по-різному, навіть якщо їх просять виконати одну й ту саму дію.

public class Company {  
 public void displayType() {  
 System.out.println("Generic Company");  
 }  
}  

public class LimitedCompany extends Company {  
 public void displayType() {  
 System.out.println("Limited Company");  
 }  
}  

public class Main {  
 public static void main(String[] args) {  
 Company company = new LimitedCompany(); // Поліморфізм (Polymorphism)  
 company.displayType(); // Викликає перевизначений метод  
 }  
}

Типи посилань і null посилання

Усі класи та масиви в Java є типами посилань. Типи посилань зберігають посилання (адреси) на об'єкти, а не самі дані. Наприклад:

String companyName = "Tech Innovators Ltd";

У цьому випадку:

  • companyName — це посилання на об'єкт типу String, що містить значення "Tech Innovators Ltd".
  • Змінна не утримує саму строку, а вказує на те, де вона зберігається в пам'яті.

Для типів посилань null є спеціальним ключовим словом, яке позначає "відсутність посилання". Це означає, що змінна посилання не вказує на жоден об'єкт і представляє відсутність значення. Це як сказати: "Ця змінна існує, але наразі не має значущих даних". Наприклад:

String companyName = null; // Назва компанії ще не призначена

Це означає, що companyName оголошена та ініціалізована правильно, але не вказує на жоден реальний об'єкт типу String.

Спроба використати посилання, яке дорівнює null, без належних перевірок може призвести до NullPointerException.

String companyName = null;  
System.out.println(companyName.length()); // Помилка, якщо companyName дорівнює null

Тому перед використанням змінної потрібно перевірити, чи не дорівнює вона null, щоб уникнути помилок.

String companyName = null;  

// Перевірка, чи є змінна null  
if (companyName == null) {  
 System.out.println("Назва компанії не встановлена.");  
} else {  
 System.out.println(companyName.length());  
}

Обробка винятків

У програмуванні можуть траплятися помилки. Програма може зіткнутися з проблемами, такими як відсутність даних, некоректний ввід або збої в системі. Ці проблеми називаються винятками, і Java надає спосіб обробляти винятки, щоб програма не завершилась аварійно.

Прикладом, з яким ви вже знайомі, є спроба доступу до стану або поведінки об'єкта в змінній, яка наразі має значення null.

Що таке винятки?

Виняток — це проблема, яка виникає під час виконання програми. Наприклад:

  1. Файл не існує, коли ви намагаєтесь його відкрити.
  2. Користувач вводить літери замість чисел у формі.
  3. Не існує компанії з шуканим ідентифікаційним номером.

Чому важливо обробляти винятки?

Без обробки винятків програма може припинити свою роботу при виникненні помилки. Обробляючи винятки, ви можете:

  1. Запобігти аварійному завершенню програми.
  2. Показати зручні для користувача повідомлення про помилки.
  3. Логувати проблеми для їх подальшого виправлення розробниками.

Як працюють винятки в Java?

  1. Блок try: Код, який може спричинити виняток, поміщається в блок try.
  2. Блок catch: Обробляє виняток, якщо він стався.
    3.
    текст перекладу
    Finally Block (Optional): Виконується код незалежно від того, чи стався виняток (наприклад, закриття з'єднання з базою даних).
try {  
 // Код, який може викликати виняток  
} catch (ExceptionType e) {  
 // Код для обробки винятку  
} finally {  
 // Код, що завжди виконується (необов'язково)  
}

Деякі поширені винятки, з якими ви можете зустрітися при розробці на Java:

  • ArithmeticException: Ділення на нуль або математичні помилки.
  • NullPointerException: Спроба доступу до чогось, що не існує.
  • NumberFormatException: Спроба перетворення некоректних рядків на числа.
  • IOException: Проблеми з ввід/вивід (наприклад, файл не знайдено).
import java.util.Scanner;  

public class CompaniesApp {  
 public static void main(String[] args) {  
 Scanner scanner = new Scanner(System.in);  

 try {  
 // Запит на річний оборот  
 System.out.print("Enter annual turnover: ");  
 String input = scanner.nextLine();  

 // Перетворення вводу в число типу double  
 double turnover = Double.parseDouble(input);  
 System.out.println("Annual Turnover: £" + turnover);  
 } catch (NumberFormatException e) {  
 // Обробка некоректного вводу  
 System.out.println("Error: Please enter a valid number for turnover.");  
 }  
 }  
}
public class CompaniesApp {  
 public static void main(String[] args) {  
 double totalRevenue = 50000;  
 double numberOfEmployees = 0; // Помилка: нуль працівників  

 try {  
 // Обчислення доходу на одного працівника  
 double revenuePerEmployee = totalRevenue / numberOfEmployees;  
 System.out.println("Revenue per employee: £" + revenuePerEmployee);  
 } catch (ArithmeticException e) {  
 // Обробка ділення на нуль  
 System.out.println("Error: Cannot divide by zero.");  
 }  
 }  
}

Колекції Java

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

Чому нам потрібні колекції замість масивів?

Масиви:

  1. Фіксовані за розміром: Після створення масиву ви не можете динамічно додавати чи видаляти елементи.
  2. Обмежені функціональністю: Масиви не мають вбудованих інструментів для пошуку, сортування чи інших поширених операцій.
  3. Негнучкі: Масиви підходять для простих даних, але колекції можуть обробляти більш складні та динамічні потреби в даних.

Уявіть, що ви зберігаєте список імен клієнтів. Що станеться, якщо вам потрібно додати нове ім'я?

String[] customers = {"Alice", "Bob", "Charlie"};  
// Ви не можете просто додати "David", не створивши новий масив.

Колекції вирішують ці проблеми, надаючи динамічні структури даних з багатими можливостями.

Що таке колекції?

Колекція — це як розумний контейнер, який може:

  • Динамічно змінювати розмір.
  • Обробляти дублікати (або запобігати їм).
  • Легко виконувати пошук, сортування та групування.

Фреймворк колекцій Java включає класи, такі як ArrayList, HashSet і HashMap, для вирішення різних задач.

Є три основні типи колекцій.

  • List
    Динамічна, впорядкована колекція, де елементи можуть бути дубльовані.
    Уявіть це як список покупок, де елементи зберігаються в послідовності.
    текст перекладу
    Приклад: ArrayList.
ArrayList products = new ArrayList<>();  
products.add("Laptop");  
products.add("Smartphone");  
products.add("Tablet");  
products.remove("Tablet"); // Видалення елемента динамічно
import java.util.ArrayList;  
import java.util.List;  

public class ListExample {  
 public static void main(String[] args) {  
 List companies = new ArrayList<>();  

 // Додавання елементів  
 companies.add("Tech Innovators Ltd");  
 companies.add("Future Solutions Inc");  
 companies.add("Alpha Holdings");  

 // Отримання елемента за індексом  
 System.out.println("Перша компанія: " + companies.get(0));  

 // Прохід по списку  
 System.out.println("Список компаній:");  
 for (String company : companies) {  
 System.out.println(company);  
 }  

 // Перевірка наявності елемента  
 boolean hasAlpha = companies.contains("Alpha Holdings");  
 System.out.println("Чи містить Alpha Holdings? " + hasAlpha);  

 // Видалення елемента  
 companies.remove("Future Solutions Inc");  
 System.out.println("Список компаній після видалення: " + companies);  
 }  
}
  • Set
    Невпорядкована колекція, яка не дозволяє дублювання.
    Уявіть це як колекцію унікальних тегів.
    Приклад: HashSet.
HashSet emails = new HashSet<>();  
emails.add("[email protected]");  
emails.add("[email protected]");  
emails.add("[email protected]"); // Дублікати ігноруються  
System.out.println(emails); // Виведення: [[email protected], [email protected]]
import java.util.HashSet;  
import java.util.Set;  

public class SetExample {  
 public static void main(String[] args) {  
 Set regNumbers = new HashSet<>();  

 // Додавання елементів  
 regNumbers.add("12345678");  
 regNumbers.add("87654321");  
 regNumbers.add("12345678"); // Дублікати не будуть додані  

 // Перевірка кількості елементів  
 System.out.println("Загальна кількість унікальних реєстраційних номерів: " + regNumbers.size());  

 // Перевірка наявності елемента  
 boolean hasNumber = regNumbers.contains("12345678");  
 System.out.println("Чи містить '12345678'? " + hasNumber);  

 // Прохід по колекції  
 System.out.println("Реєстраційні номери:");  
 for (String reg : regNumbers) {  
 System.out.println(reg);  
 }  
 }  
}
  • Map
    Зберігає пари ключ-значення, як словник.
    текст перекладу
    Приклад: HashMap.
import java.util.HashMap;  

HashMap prices = new HashMap<>();  
prices.put("Laptop", 999.99);  
prices.put("Smartphone", 699.99);  

// Отримання ціни за назвою продукту  
System.out.println("Ціна ноутбука: £" + prices.get("Laptop"));
import java.util.HashMap;  
import java.util.Map;  

public class MapExample {  
 public static void main(String[] args) {  
 Map companyRegMap = new HashMap<>();  

 // Додавання пар ключ-значення  
 companyRegMap.put("Tech Innovators Ltd", "12345678");  
 companyRegMap.put("Future Solutions Inc", "87654321");  

 // Отримання значення за ключем  
 String regNumber = companyRegMap.get("Tech Innovators Ltd");  
 System.out.println("Реєстраційний номер для Tech Innovators Ltd: " + regNumber);  

 // Перевірка наявності ключа або значення  
 boolean hasCompany = companyRegMap.containsKey("Future Solutions Inc");  
 System.out.println("Map містить Future Solutions Inc? " + hasCompany);  

 // Прохід по елементах  
 System.out.println("Компанія - Реєстраційний номер:");  
 for (Map.Entry entry : companyRegMap.entrySet()) {  
 System.out.println(entry.getKey() + " - " + entry.getValue());  
 }  

 // Видалення пари ключ-значення  
 companyRegMap.remove("Future Solutions Inc");  
 System.out.println("Map після видалення: " + companyRegMap);  
 }  
}

Операції з колекціями

Ось підсумок основних операцій, які можна виконувати з колекціями.

+------------+----------------------------------------------------------+----------------------------------------+  
| Колекція   | Загальні операції                                      | Опис                                   |  
+------------+----------------------------------------------------------+----------------------------------------+  
| List       | add(item), get(index), remove(item), contains(item)     | Упорядкована колекція, дозволяє дублювання. |  
| Set        | add(item), remove(item), contains(item), size()         | Без дублікатів, невпорядкована.          |  
| Map        | put(key, value), get(key), remove(key), containsKey(key)| Пари ключ-значення для швидкого пошуку. |  
+------------+----------------------------------------------------------+----------------------------------------+

Колекції також надають більш складний функціонал.
текст перекладу
Ось приклад, що стосується списків.

import java.util.ArrayList;  
import java.util.Collections;  
import java.util.List;  
import java.util.stream.Collectors;  

public class ListAdvancedExample {  
 public static void main(String[] args) {  
 List services = new ArrayList<>();  
 services.add("Accounting");  
 services.add("Tax Consulting");  
 services.add("Business Strategy");  
 services.add("Legal Advice");  

 // Сортування списку  
 Collections.sort(services);  
 System.out.println("Відсортовані послуги: " + services);  

 // Фільтрація послуг, які містять "Tax"  
 List filtered = services.stream()  
 .filter(service -> service.contains("Tax"))  
 .collect(Collectors.toList());  
 System.out.println("Фільтровані послуги: " + filtered);  

 // Знаходження першого елементу  
 System.out.println("Перша послуга: " + services.get(0));  
 }  
}

Ключові концепції:

  • Collections.sort(services): Сортує список в алфавітному порядку.
  • stream().filter(condition).collect(): Фільтрує елементи, що відповідають умові.
  • get(0) отримує перший елемент у списку.

Наступний приклад демонструє операції з наборами.

import java.util.HashSet;  
import java.util.Set;  

public class SetOperationsExample {  
 public static void main(String[] args) {  
 Set oldRegNumbers = new HashSet<>();  
 oldRegNumbers.add("12345678");  
 oldRegNumbers.add("87654321");  
 oldRegNumbers.add("98765432");  

 Set newRegNumbers = new HashSet<>();  
 newRegNumbers.add("98765432");  
 newRegNumbers.add("11112222");  
 newRegNumbers.add("12345678");  

 // Об'єднання: поєднання всіх унікальних елементів  
 Set union = new HashSet<>(oldRegNumbers);  
 union.addAll(newRegNumbers);  
 System.out.println("Об'єднання: " + union);  

 // Перехрестя: спільні елементи  
 Set intersection = new HashSet<>(oldRegNumbers);  
 intersection.retainAll(newRegNumbers);  
 System.out.println("Перехрестя: " + intersection);  

 // Різниця: елементи, що є в oldRegNumbers, але відсутні в newRegNumbers  
 Set difference = new HashSet<>(oldRegNumbers);  
 difference.removeAll(newRegNumbers);  
 System.out.println("Різниця: " + difference);  
 }  
}

Ключові концепції:

  • addAll(): Об'єднує два набори без дублікатів (об'єднання).
  • retainAll(): Залишає лише спільні елементи (перехрестя).
  • removeAll(): Знаходить унікальні елементи в одному наборі (різниця).

І на останок приклад з просунутими операціями на картах.

import java.util.HashMap;  
import java.util.Map;  

public class MapAdvancedExample {  
 public static void main(String[] args) {  
 Map revenueByCompany = new HashMap<>();  
 revenueByCompany.put("Tech Innovators Ltd", 1_000_000);  
 revenueByCompany.put("Future Solutions Inc", 500_000);  
 revenueByCompany.put("Alpha Holdings", 750_000);  

 // Збільшення доходу на 10% для кожної компанії  
 revenueByCompany.replaceAll((company, revenue) -> revenue + (revenue / 10));  
 System.out.println("Оновлені доходи: " + revenueByCompany);  

 // Злиття доходу для компанії  
 revenueByCompany.merge("Alpha Holdings", 250_000, Integer::sum);  
 System.out.println("Новий дохід Alpha Holdings: " + revenueByCompany.get("Alpha Holdings"));  

 // Ітерація з лямбда-виразом  
 revenueByCompany.forEach((company, revenue) -> {  
 System.out.println(company + " має дохід " + revenue);  
 });  
 }  
}

Ключові концепції:

  • replaceAll(): Оновлює всі значення на основі обчислення (оновлення).
  • merge(): Додає або оновлює ключ за допомогою функції поєднання (злиття).
  • forEach(): Ітерує через пари ключ-значення лаконічно.

Коли і яку колекцію використовувати

+------------------------------------------+-----------+  
| Сценарій                                | Використовувати |  
+------------------------------------------+-----------+  
| Вам часто потрібно додавати або видаляти елементи | ArrayList |  
| Вам потрібні унікальні елементи          | HashSet |  
| Вам потрібні пари ключ-значення          | HashMap |  

текст перекладу
| У вас є фіксована кількість елементів | Array |  
+------------------------------------------+-----------+

## Потоки вводу/виводу та робота з файлами

У Java **потоки** використовуються для читання даних з джерел (наприклад, файли, клавіатура) та запису даних в призначення (наприклад, файли, екрани).

- **Потік вводу (Input Stream)**: Читає дані в програму з зовнішнього джерела.
- **Потік виводу (Output Stream)**: Відправляє дані з програми до зовнішнього призначення.

## Загальні класи вводу/виводу в Java

1.
текст перекладу
**File** — Представляє шляхи до файлів і каталогів.  
2. **FileReader / FileWriter** — Читання/запис файлів на основі символів.  
3. **BufferedReader / BufferedWriter** — Ефективне читання/записування з буферизацією.  
4. **FileInputStream / FileOutputStream** — Читання/запис бінарних даних.  
5. **Scanner** — Легке читання вводу з файлів або консолі.

## Читання з файлу

Давайте прочитаємо файл, що містить список назв компаній, по одній на рядок.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReadExample {
public static void main(String[] args) {
String filePath = "companies.txt"; // Припустимо, що цей файл існує і містить назви компаній.

try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
System.out.println("Читання назв компаній з файлу:");
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.err.println("Помилка при читанні файлу: " + e.getMessage());
}
}
}
```

Пояснення

  • BufferedReader ефективно читає рядки з файлу.
  • Конструкція try-with-resources автоматично закриває файл після завершення роботи.

Запис в файл

Запишемо список назв компаній у файл.

import java.io.BufferedWriter;  
import java.io.FileWriter;  
import java.io.IOException;  

public class FileWriteExample {  
 public static void main(String[] args) {  
 String filePath = "new_companies.txt";  

 try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {  
 writer.write("Tech Innovators Ltd");  
 writer.newLine();  
 writer.write("Future Solutions Inc");  
 writer.newLine();  
 writer.write("Alpha Holdings");  
 System.out.println("Назви компаній записано у файл.");  
 } catch (IOException e) {  
 System.err.println("Помилка при записі у файл: " + e.getMessage());  
 }  
 }  
}

Пояснення

  • BufferedWriter ефективно записує текст і використовує newLine(), щоб створювати нові рядки.
  • FileWriter вказує шлях до файлу і може перезаписувати або додавати дані.

Читання вводу з консолі

Інтерактивні програми часто вимагають вводу від користувача.

import java.util.Scanner;  

public class ConsoleInputExample {  
 public static void main(String[] args) {  
 Scanner scanner = new Scanner(System.in);  

 System.out.print("Введіть назву вашої компанії: ");  
 String companyName = scanner.nextLine();  
 System.out.println("Компанія зареєстрована: " + companyName);  

 scanner.close();  
 }  
}

Пояснення

  • Scanner читає ввід користувача з консолі за допомогою nextLine(), щоб отримати повні рядки.

Читання та запис бінарних даних

Використовуйте FileInputStream і FileOutputStream для роботи з бінарними даними, такими як зображення чи серіалізовані файли.

import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  

public class BinaryFileCopyExample {  
 public static void main(String[] args) {  
 String inputFile = "sourceImage.jpg"; // Припустимо, що цей файл існує.

текст перекладу
String outputFile = "copyImage.jpg";  

 try (FileInputStream inputStream = new FileInputStream(inputFile);  
 FileOutputStream outputStream = new FileOutputStream(outputFile)) {  

 byte[] buffer = new byte[1024];  
 int bytesRead;  
 while ((bytesRead = inputStream.read(buffer)) != -1) {  
 outputStream.write(buffer, 0, bytesRead);  
 }  
 System.out.println("Файл успішно скопійовано.");  
 } catch (IOException e) {  
 System.err.println("Помилка при копіюванні файлу: " + e.getMessage());  
 }  
 }  
}

Пояснення

  • Бінарний ввід/вивід використовує FileInputStream і FileOutputStream.
  • Дані читаються в буфер і записуються в новий файл.

Управління файлами за допомогою класу File

import java.io.File;  

public class FileCheckExample {  
 public static void main(String[] args) {  
 File file = new File("companies.txt");  

 if (file.exists()) {  
 System.out.println("Файл існує: " + file.getAbsolutePath());  
 } else {  
 System.out.println("Файл не знайдений.");  
 }  
 }  
}

Кращі практики для роботи зі стрімами

  • Використовуйте try-with-resources: Автоматично закриває ресурси, запобігаючи витокам пам'яті.
  • Перевіряйте на IOException: Завжди обробляйте помилки вводу/виводу належним чином.
  • Вибирайте правильний клас:
    Використовуйте Reader/Writer для текстових файлів.
    Використовуйте InputStream/OutputStream для бінарних даних.

Вступ до JUnit

JUnit — це популярний фреймворк для написання та виконання модульних тестів (unit tests) у Java.
текст перекладу
Це допомагає розробникам перевіряти, чи працюють окремі частини коду (методи) так, як очікується.

Чому тестувати з JUnit?

  • Надійність: Перевірка того, чи працює код так, як очікується.
  • Підтримуваність: Виявлення змін, які ламають функціональність.
  • Ефективність: Автоматизація повторюваних тестувальних завдань.

Налаштування JUnit

  • Додайте JUnit до вашого проєкту
    Більшість проєктів використовують інструменти збірки, такі як Maven або Gradle, для керування залежностями.
  • Maven
    Додайте наступне до вашого pom.xml:

 org.junit.jupiter  
 junit-jupiter  
 5.10.0  
 test  

  • Gradle
    Додайте це до вашого build.gradle:
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'

Основні концепції

Анотації — Спеціальні маркери, що вказують на поведінку, пов'язану з тестами.

  • @Test – Позначає метод як тест.
  • @BeforeEach – Код, який виконується перед кожним тестом.
  • @AfterEach – Код, який виконується після кожного тесту.
  • @BeforeAll / @AfterAll – Код, який виконується один раз перед/після всіх тестів.

Асерти — Методи для перевірки очікуваних результатів.

  • assertEquals(expected, actual)
  • assertTrue(condition)
  • assertNotNull(object)

Приклад 1: Написання простого тесту

Перевіримо метод, який обчислює ПДВ для рахунків компанії.

Код для тестування:

public class InvoiceService {  
 public double calculateVAT(double amount, double vatRate) {  
 return amount * vatRate / 100;  
 }  
}

Клас тесту:

import static org.junit.jupiter.api.Assertions.*;  
import org.junit.jupiter.api.Test;  

public class InvoiceServiceTest {  
 @Test  
 public void testCalculateVAT() {  
 InvoiceService service = new InvoiceService();  
 double vat = service.calculateVAT(1000, 20); // 20% ПДВ на £1000  
 assertEquals(200, vat, 0.001, "Обчислення ПДВ не вірне");  
 }  
}

Тут виклик assertEquals(200, vat, 0.001) перевіряє, чи є фактичний результат (vat) близьким до 200 з точністю до 0.001.

Приклад 2: Тестування на виключення

Припустимо, ми додаємо перевірку в calculateVAT, щоб викидати виключення для від'ємних значень.

Оновлений метод:

public double calculateVAT(double amount, double vatRate) {  
 if (amount < 0) {  
 throw new IllegalArgumentException("Сума не може бути від'ємною");  
 }  
 return amount * vatRate / 100;  
}

Тестування на виключення:

import static org.junit.jupiter.api.Assertions.*;  
import org.junit.jupiter.api.Test;  

public class InvoiceServiceTest {  
 @Test  
 public void testCalculateVATWithNegativeAmount() {  
 InvoiceService service = new InvoiceService();  
 assertThrows(IllegalArgumentException.class, () -> {  
 service.calculateVAT(-1000, 20);  
 }, "Очікувалося виключення для від'ємної суми");  
 }  
}

Приклад 3: Використання методів налаштування

Можливо, вам потрібно налаштувати спільні об'єкти перед кожним тестом. Ці об'єкти повинні бути в новому стані перед кожним тестом.

import org.junit.jupiter.api.BeforeEach;  
import org.junit.jupiter.api.Test;  

import static org.junit.jupiter.api.Assertions.assertEquals;  

public class InvoiceServiceTest {  
 private InvoiceService service;  

 @BeforeEach  
 public void setUp() {  
 service = new InvoiceService();  
 }  

 @Test  
 public void testCalculateVAT() {  
 double vat = service.calculateVAT(500, 15);  
 assertEquals(75, vat, "Обчислення ПДВ не вірне для 15%");  
 }  
}

Поради для написання хороших тестів

  • Використовуйте описові назви тестів: Чітко описуйте очікувану поведінку (наприклад, testCalculateVATWithZeroRate).
  • Дотримуйтеся шаблону AAA: Arrange (налаштування даних), Act (виклик методу), Assert (перевірка результату).
  • Тестуйте крайні випадки: Враховуйте граничні значення, null-значення та неправильні вхідні дані.

Вступ до Mocking

Mocking є важливою частиною тестування складних систем через симуляцію реальних об'єктів із спрощеною поведінкою.

Що таке Mocking?

Mocking — це створення фальшивих об'єктів, які імітують поведінку реальних об'єктів.
текст перекладу
Це дозволяє вам:

  • Ізолювати одиницю тестування.
  • Тестувати код без залежності від зовнішніх систем (наприклад, баз даних, API).

Приклад сценарію: Тестування сервісу, який залежить від бази даних.

Популярні фреймворки для мокінгу в Java

  • Mockito: Найбільш використовуваний фреймворк.
  • EasyMock: Інший популярний вибір.
  • PowerMock: Додає функціональність до Mockito.

Для цього посібника ми використовуватимемо Mockito.

Налаштування Mockito

Maven


 org.mockito  
 mockito-core  
 5.2.0  
 test  

Gradle

testImplementation 'org.mockito:mockito-core:5.2.0'

Основні концепції мокінгу

  1. Mock (Мок) — Створення мок-об'єкта.
  2. Stub (Шаблон) — Визначення поведінки для мок-об'єкта.
  3. Verify (Перевірка) — Перевірка, чи були викликані методи на моку.

Приклад: Мокінг залежності

Уявімо, що сервіс залежить від репозиторію для доступу до даних.

Код для тестування:

public class CustomerService {  
 private CustomerRepository repository;  

 public CustomerService(CustomerRepository repository) {  
 this.repository = repository;  
 }  

 public String findCustomerNameById(int id) {  
 Customer customer = repository.findById(id);  
 return customer != null ? customer.getName() : "Unknown Customer";  
 }  
}

Залежність для мокінгу:

public interface CustomerRepository {  
 Customer findById(int id);  
}

Клас тесту:

import static org.mockito.Mockito.*;  
import static org.junit.jupiter.api.Assertions.*;  

import org.junit.jupiter.api.BeforeEach;  
import org.junit.jupiter.api.Test;  

public class CustomerServiceTest {  
 private CustomerRepository mockRepository;  
 private CustomerService service;  

 @BeforeEach  
 public void setUp() {  
 mockRepository = mock(CustomerRepository.class); // Створення мок-об'єкта  
 service = new CustomerService(mockRepository);  
 }  

 @Test  
 public void testFindCustomerNameById() {  
 Customer mockCustomer = new Customer("Alice"); // Мок клієнта  
 when(mockRepository.findById(1)).thenReturn(mockCustomer); // Шаблон поведінки  

 String name = service.findCustomerNameById(1);  
 assertEquals("Alice", name, "Ім'я клієнта повинно бути Alice");  

 verify(mockRepository).findById(1); // Перевірка виклику методу  
 }  

 @Test  
 public void testFindCustomerNameForUnknownId() {  
 when(mockRepository.findById(2)).thenReturn(null); // Шаблон для невідомого клієнта  

 String name = service.findCustomerNameById(2);  
 assertEquals("Unknown Customer", name, "Має повернути 'Unknown Customer' для невірного ID");  

 verify(mockRepository).findById(2); // Перевірка виклику методу  
 }  
}

Пояснення

  • mock(CustomerRepository.class) створює мок-об'єкт репозиторію.
  • when(mockRepository.findById(1)).thenReturn(mockCustomer) задає шаблон поведінки для конкретного вхідного значення.
  • verify(mockRepository).findById(1) перевіряє, чи був викликаний метод.

Приклад: Обробка виключень з моками

@Test  
public void testExceptionThrown() {  
 when(mockRepository.findById(3)).thenThrow(new RuntimeException("Database error"));  

 assertThrows(RuntimeException.class, () -> service.findCustomerNameById(3),   
 "Має викидати RuntimeException для помилки бази даних");  

 verify(mockRepository).findById(3); // Перевірка взаємодії  
}




Перекладено з: [A Java Primer for New Starters](https://medium.com/@fedmest/a-java-primer-for-new-starters-469d28a34664)

Leave a Reply

Your email address will not be published. Required fields are marked *