CodeGym /Kursy /JAVA 25 SELF /Różnice między record i class, ograniczenia record

Różnice między record i class, ograniczenia record

JAVA 25 SELF
Poziom 22, Lekcja 4
Dostępny

1. Porównanie record i class: jakie są najważniejsze różnice?

W Javie mamy dwa podstawowe sposoby opisywania własnych typów danych: poprzez zwykłe klasy (class) oraz klasy record (record). Na pierwszy rzut oka oba warianty pozwalają przechowywać i przetwarzać dane. Ale jeśli zajrzymy głębiej, różnic jest więcej, niż może się wydawać!

Tabela różnic: class vs record

Charakterystyka Zwykła klasa (class) Klasa record (record)
Mutowalność Dowolna: pola mogą być final lub nie Niemutowalny: wszystkie pola są final
Dziedziczenie Można dziedziczyć (extends), domyślnie nie jest final Zawsze final, nie może być nadklasą
Pola Dowolne: statyczne, niestatyczne, final lub nie-final, dowolne typy Tylko komponenty record (private final), plus pola statyczne
Gettery/settery Sami piszemy (albo generujemy za pomocą Lomboka) Automatycznie tworzone są gettery (nazwa pola jako nazwa metody), setterów brak
equals/hashCode/toString Zwykle piszemy ręcznie/generujemy (equals, hashCode, toString) Generowane automatycznie dla wszystkich komponentów
Konstruktory Dowolne, w dowolnej liczbie Jeden główny (po wszystkich komponentach), można dodać konstruktor kompaktowy
Interfejsy Można implementować Można implementować
Dodatkowe metody Dowolne Można dodawać, ale tylko metody (nie pola)
Użycie w kolekcjach Można, ale trzeba poprawnie zaimplementować equals/hashCode Idealnie nadaje się na klucze/wartości, wszystko już jest zaimplementowane

Przykład poglądowy

Zwykła klasa:


public class Person {
    private final String name;
    private final int age;

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

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) { /* ... */ }
    @Override
    public int hashCode() { /* ... */ }
    @Override
    public String toString() { /* ... */ }
}

Klasa record:


public record Person(String name, int age) { }

I to wszystko! Jedna linijka kodu — i otrzymujemy to samo (a nawet lepiej). I żadnego ryzyka, że zapomnimy zaimplementować coś istotnego.

2. Ograniczenia klas record

Klasy record to nie tylko „krótka składnia”, lecz odrębna koncepcja z własnymi, rygorystycznymi zasadami. Przyjrzyjmy się im bliżej.

Record zawsze final

Klasa record z definicji jest zawsze final. To znaczy, że nie możesz utworzyć podklasy od record:


public record Point(int x, int y) { }

// public class ColoredPoint extends Point { } // Błąd kompilacji!

Jeśli musisz rozszerzać zachowanie, użyj zwykłych klas albo kompozycji (umieść record wewnątrz klasy).

Record nie może być nadklasą

Klasa record nie może być rodzicem dla innych klas, zawsze jest final. To logiczne: gdyby było to możliwe, ktoś mógłby dodać pole mutowalne — i cała koncepcja „niemutowalnych danych” by upadła.

Tylko pola final (komponenty)

Wszystkie komponenty record deklaruje się w nagłówku i są domyślnie private final. Nie możesz dodać niestatycznych pól w ciele record:


public record User(String login, String email) {
    // int counter; // Błąd! Nie można dodawać pól niestatycznych
    static int totalUsers = 0; // Można, to pole statyczne
}

Brak setterów

Klasa record nie może mieć setterów dla komponentów. Każda próba dodania metody w rodzaju setX(int x) będzie bezsensowna: nie da się zmienić wartości pola po utworzeniu obiektu.


public record Point(int x, int y) {
    // public void setX(int x) { this.x = x; } // Błąd: nie można zmienić pola final
}

Brak pustego konstruktora

Klasa record ma zawsze tylko główny konstruktor przyjmujący wartości dla wszystkich komponentów. Nie można utworzyć record bez podania wszystkich danych:


Point p = new Point(1, 2);  // OK
// Point p = new Point();   // Błąd: brak konstruktora bez parametrów

Brak niestatycznych inicjalizatorów

Klasa record nie może zawierać niestatycznych inicjalizatorów (tych, które zapisuje się w klamrach poza metodami):


public record User(String login) {
    // { /* ... */ } // Błąd: niestatyczne inicjalizatory są zabronione
}

Ograniczenia dziedziczenia

Klasa record nie może jawnie dziedziczyć po innej klasie (poza java.lang.Record, która jest dla nas ukryta jako klasa bazowa wszystkich recordów). Ale implementacja interfejsów — jak najbardziej!


public interface Printable {
    void print();
}

public record Book(String title) implements Printable {
    @Override
    public void print() {
        System.out.println("Drukujemy książkę: " + title);
    }
}

Nie nadaje się do złożonej logiki biznesowej

record dotyczy danych, a nie zachowania. Jeśli obiekt ma złożoną logikę, stan mutowalny, „cykl życia” lub mnóstwo zależności — record tu nie pomoże. Lepiej użyć zwykłej klasy.

3. Kiedy warto używać klas record?

  • DTO (Data Transfer Object): do przekazywania niemutowalnych danych między warstwami aplikacji, serwisami, mikroserwisami czy kontrolerami REST (np. w odpowiedziach JSON).
  • Value Object: obiekty, które są definiowane wyłącznie przez swoje wartości.
  • Klucze i wartości w kolekcjach: gdy istotna jest poprawna implementacja equals i hashCode (np. do użycia w HashMap lub Set).
  • Wyniki obliczeń: gdy trzeba zwrócić z metody kilka wartości naraz (np. record Pair<T, U>(T first, U second)).

Przykład: DTO dla kontrolera REST


public record UserDto(String login, String email) { }

Teraz można bezpiecznie zwracać obiekt tego typu z kontrolera bez obaw, że ktoś zmieni jego pola.

Przykład: klucz dla HashMap


public record Point(int x, int y) { }

Map<Point, String> pointNames = new HashMap<>();
pointNames.put(new Point(1, 2), "A");
pointNames.put(new Point(3, 4), "B");

// Wszystko działa poprawnie: equals i hashCode są już zaimplementowane!

4. Kiedy NIE należy używać klas record

  • Stan mutowalny: jeśli choć jedno pole ma się zmieniać po utworzeniu obiektu.
  • Złożona logika: jeśli obiekt ma złożone zachowanie, wiele metod, zagnieżdżone obiekty o stanie mutowalnym.
  • Dziedziczenie: jeśli wymagana jest hierarchia klas, klasy abstrakcyjne, nadpisywanie metod.
  • Encje logiki biznesowej: np. obiekty, które żyją w bazie danych i mają unikalny identyfikator.

Przykład: kiedy potrzebna jest zwykła klasa


public class Account {
    private String id;
    private int balance;

    public Account(String id, int balance) {
        this.id = id;
        this.balance = balance;
    }

    public void deposit(int amount) { balance += amount; }
    public void withdraw(int amount) { balance -= amount; }
    // getters, setters, equals, hashCode, toString...
}

Tutaj wyraźnie widać, że stan obiektu się zmienia — record nie pasuje.

5. Praktyczne przykłady: wybór między record a class

Przykład 1: record — wybór idealny


public record Rectangle(int width, int height) {
    public int area() {
        return width * height;
    }
}
  • Prostokąt jest określony tylko przez szerokość i wysokość.
  • Nie ma potrzeby zmiany tych wartości po utworzeniu.
  • Można dodać użyteczną metodę area().
  • Resztę Java zrobi za ciebie.

Przykład 2: class — najlepszy wariant


public class MutableRectangle {
    private int width;
    private int height;

    public MutableRectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }

    public int area() { return width * height; }
}

Trzeba zmieniać wymiary prostokąta po utworzeniu? Użyj zwykłej klasy.

6. Typowe błędy przy pracy z klasami record

Błąd nr 1: próba dodania pola niestatycznego.
Klasa record nie pozwala deklarować niestatycznych pól poza listą komponentów. Jeśli spróbujesz — kompilator zgłosi błąd. Na przykład:


public record City(String name) {
    // int population; // Błąd!
}

Błąd nr 2: chęć dodania settera.
Record nie obsługuje setterów dla komponentów. Każda próba zmiany wartości pola po utworzeniu obiektu kończy się błędem kompilacji.

Błąd nr 3: próba dziedziczenia record lub po record.
Record jest zawsze final. Nie można dziedziczyć po record, a record nie może dziedziczyć innej klasy (poza ukrytą java.lang.Record).

Błąd nr 4: używanie record dla obiektów mutowalnych.
Jeśli planujesz zmieniać stan obiektu po utworzeniu — record nie jest dla ciebie! Użyj zwykłej klasy.

Błąd nr 5: zapomniano o ograniczeniach konstruktora.
Klasa record musi mieć konstruktor przyjmujący wartości dla wszystkich komponentów. Nie ma konstruktora bez parametrów!

1
Zadanie
JAVA 25 SELF, poziom 22, lekcja 4
Niedostępne
Karta firmy z zagnieżdżonym adresem 🏢
Karta firmy z zagnieżdżonym adresem 🏢
1
Zadanie
JAVA 25 SELF, poziom 22, lekcja 4
Niedostępne
Sprawdzenie zgodności współrzędnych na mapie 🗺️
Sprawdzenie zgodności współrzędnych na mapie 🗺️
1
Zadanie
JAVA 25 SELF, poziom 22, lekcja 4
Niedostępne
Drukowanie zawartości dokumentu 📄
Drukowanie zawartości dokumentu 📄
1
Zadanie
JAVA 25 SELF, poziom 22, lekcja 4
Niedostępne
Porównanie modyfikowalnych i niemodyfikowalnych profili użytkowników 🧑‍🤝‍🧑
Porównanie modyfikowalnych i niemodyfikowalnych profili użytkowników 🧑‍🤝‍🧑
1
Ankieta/quiz
Klasy record, poziom 22, lekcja 4
Niedostępny
Klasy record
Klasy record
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION