JavaRush /Курси /JAVA 25 SELF /Інтерфейси зі стандартної бібліотеки: Comparable, Seriali...

Інтерфейси зі стандартної бібліотеки: Comparable, Serializable тощо.

JAVA 25 SELF
Рівень 21 , Лекція 4
Відкрита

1. Інтерфейс Comparable<T>

Ви коли-небудь сортували список чисел або рядків? Звісно, так! А тепер уявіть, що у вас є список власних об’єктів — наприклад, список студентів, товарів або котиків. Як Java зрозуміє, у якому порядку їх сортувати? Саме для цього й існує інтерфейс Comparable<T>.

Цей інтерфейс визначає «природний порядок» об’єктів — порядок, природний для цього типу даних. Наприклад, для чисел — за зростанням, для рядків — за абеткою, для студентів — за прізвищем або віком (на ваш вибір).

Як улаштовано Comparable

Інтерфейс дуже простий: у ньому лише один метод:

public interface Comparable<T> {
    int compareTo(T o);
}

Метод compareTo має повертати:

  • від’ємне число, якщо поточний об’єкт «менший», ніж інший;
  • 0, якщо «дорівнює»;
  • додатне число, якщо «більший» за інший.

Приклад: сортуємо студентів за віком

Додамо клас Student і реалізуємо для нього інтерфейс Comparable<Student>:

public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Гетери для прикладу
    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public int compareTo(Student other) {
        // Сортуємо за віком (за зростанням)
        return Integer.compare(this.age, other.age);
    }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

Тепер ви можете легко відсортувати масив або список студентів:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("Василь", 20));
        students.add(new Student("Петро", 18));
        students.add(new Student("Марія", 22));

        Collections.sort(students); // Працює завдяки Comparable!

        System.out.println("Відсортовані студенти:");
        for (Student s : students) {
            System.out.println(s);
        }
    }
}

Результат:

Петро (18)
Василь (20)
Марія (22)

Важливий нюанс

Якщо ви реалізуєте Comparable, намагайтеся, щоб compareTo працював узгоджено з equals. Тобто якщо a.compareTo(b) == 0, то a.equals(b) має бути true. Інакше сортування й колекції можуть поводитися непередбачувано, а у вас з’явиться привід для філософських роздумів про сенс життя програміста.

2. Інтерфейс Serializable

Серіалізація — це здатність об’єкта перетворитися на послідовність байтів (наприклад, щоб зберегти себе у файл або надіслати через мережу), а потім відновитися назад. Уявіть, що ви хочете зберегти стан своєї гри або надіслати об’єкт на сервер — без серіалізації не обійтися.

У Java для цього є маркерний інтерфейс Serializable. Маркерний — тобто він не містить методів, а просто позначає клас як придатний до серіалізації.

import java.io.Serializable;

public class Student implements Serializable {
    private String name;
    private int age;

    // ... інший код
}

Як серіалізувати об’єкт

Для серіалізації та десеріалізації використовують класи ObjectOutputStream і ObjectInputStream. Приклад — зберігаємо об’єкт у файл і читаємо назад:

import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        Student s = new Student("Катя", 19);

        // Зберігаємо об’єкт у файл
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.dat"))) {
            out.writeObject(s);
        }

        // Читаємо об’єкт із файлу
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"))) {
            Student loaded = (Student) in.readObject();
            System.out.println("Завантажено: " + loaded);
        }
    }
}

Примітка: Усі поля об’єкта (і вкладених об’єктів) теж мають бути придатними до серіалізації, інакше станеться помилка.

Навіщо потрібен маркерний інтерфейс

Інтерфейс Serializable не вимагає реалізовувати методи — він просто повідомляє JVM: «цей об’єкт можна серіалізувати». Якщо ви забудете його реалізувати, спроба серіалізації призведе до винятку NotSerializableException.

3. Інші важливі інтерфейси стандартної бібліотеки

Інтерфейс Cloneable

Ще один маркерний інтерфейс. Його завдання — дати зрозуміти JVM, що об’єкт можна клонувати за допомогою методу Object.clone(). Без нього спроба викликати clone() викине виняток.

Однак клонування в Java — тема з підводними каменями. Клонування за замовчуванням поверхневе (shallow copy), і часто замість цього краще писати власні методи копіювання.

public class Student implements Cloneable {
    private String name;
    private int age;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Інтерфейс AutoCloseable

Цей інтерфейс містить лише один метод close(). Будь-який клас, який його реалізує, можна використовувати в конструкції try-with-resources — для автоматичного закриття ресурсів (наприклад, файлів, потоків):

public class MyResource implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("Ресурс закрито!");
    }
}

public class Main {
    public static void main(String[] args) {
        try (MyResource res = new MyResource()) {
            System.out.println("Працюємо з ресурсом");
        }
        // Тут res.close() буде викликано автоматично
    }
}

Інтерфейс Iterable<T>

Цей інтерфейс дає змогу вашому об’єктові бути «перебираним» у циклі for-each. Містить лише один метод iterator(), який повертає об’єкт Iterator<T>.

public class MyList implements Iterable<String> {
    // ... внутрішнє сховище

    @Override
    public java.util.Iterator<String> iterator() {
        // Повертаємо ітератор для перебору елементів
        return ...;
    }
}

Усі стандартні колекції (ArrayList, HashSet тощо) реалізують Iterable, тому їх можна перебирати в for-each.

Інтерфейс Comparator<T>

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

import java.util.Comparator;

Comparator<Student> byName = new Comparator<Student>() {
    @Override
    public int compare(Student a, Student b) {
        return a.getName().compareTo(b.getName());
    }
};

У сучасній Java це зазвичай роблять через лямбда-вирази:

Comparator<Student> byName = (a, b) -> a.getName().compareTo(b.getName());

Observer, EventListener

Ці інтерфейси використовують для побудови патернів «спостерігач» і «слухач подій» — коли один об’єкт реагує на події, що відбуваються в іншому. Наприклад, у графічних інтерфейсах (Swing, JavaFX) обробники кнопок реалізують інтерфейс ActionListener.

4. Практика: реалізуємо Comparable і серіалізуємо об’єкт

Приклад 1. Comparable для власного класу

Створіть клас Book, який можна сортувати за роком видання:

public class Book implements Comparable<Book> {
    private String title;
    private int year;

    public Book(String title, int year) {
        this.title = title;
        this.year = year;
    }

    @Override
    public int compareTo(Book other) {
        return Integer.compare(this.year, other.year);
    }

    @Override
    public String toString() {
        return title + " (" + year + ")";
    }
}
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Book> books = Arrays.asList(
            new Book("Java для чайників", 2018),
            new Book("Війна і мир", 1869),
            new Book("Гаррі Поттер", 1997)
        );
        Collections.sort(books);
        System.out.println(books);
    }
}

Результат:

[Війна і мир (1869), Гаррі Поттер (1997), Java для чайників (2018)]

Приклад 2. Серіалізація об’єкта

import java.io.*;

public class Book implements Serializable {
    private String title;
    private int year;
    // ... конструктор, гетери, toString

    public Book(String title, int year) {
        this.title = title;
        this.year = year;
    }

    @Override
    public String toString() {
        return title + " (" + year + ")";
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Book book = new Book("Java для чайників", 2018);

        // Зберігаємо об’єкт у файл
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.dat"))) {
            out.writeObject(book);
        }

        // Читаємо об’єкт із файлу
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.dat"))) {
            Book loaded = (Book) in.readObject();
            System.out.println("Завантажено: " + loaded);
        }
    }
}

5. Таблиця: основні інтерфейси стандартної бібліотеки

Інтерфейс Призначення Ключові методи Приклад використання
Comparable<T>
Природне впорядкування об’єктів
int compareTo(T o)
Сортування списків
Comparator<T>
Користувацьке порівняння об’єктів
int compare(T a, T b)
Сортування за різними правилами
Serializable
Серіалізація об’єктів — (маркерний) Збереження/завантаження об’єктів
Cloneable
Клонування об’єктів — (маркерний) Створення копій об’єктів
AutoCloseable
Автоматичне закриття ресурсів
void close()
try-with-resources
Iterable<T>
Перебирання елементів у колекціях
Iterator<T> iterator()
цикл for-each
Observer / EventListener Реакція на події
update(), actionPerformed
Обробка подій в UI, патерни

6. Типові помилки під час роботи зі стандартними інтерфейсами

Помилка № 1: Інтерфейс не реалізовано, але потрібна функціональність.
Наприклад, забули реалізувати Serializable, а намагаєтеся серіалізувати об’єкт — отримаєте NotSerializableException. Аналогічно з Cloneable і викликом clone().

Помилка № 2: Порушення контракту Comparable та equals.
Якщо a.compareTo(b) == 0, а не виконується a.equals(b), колекції можуть поводитися дивно. Наприклад, TreeSet може «втрачати» об’єкти.

Помилка № 3: Поверхневе копіювання під час клонування.
Метод clone() за замовчуванням копіює лише «верхній шар» об’єкта. Якщо у вас є поля-посилання на інші об’єкти, вони не копіюються глибоко. Це може призвести до дивних багів.

Помилка № 4: Ігнорування try-with-resources.
Якщо клас реалізує AutoCloseable, але ви не використовуєте його в try-with-resources, ризикуєте забути закрити ресурс — і отримати витік пам’яті або блокування файлу.

Помилка № 5: Неправильна реалізація compareTo або compare.
Якщо повертати лише 0 або 1, а не від’ємне/нульове/додатне число, сортування працюватиме некоректно.

1
Опитування
Прокачані інтерфейси, рівень 21, лекція 4
Недоступний
Прокачані інтерфейси
Прокачані інтерфейси та функціональні інтерфейси
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ