CodeGym /Kursy /JAVA 25 SELF /Record z metodami

Record z metodami

JAVA 25 SELF
Poziom 22 , Lekcja 3
Dostępny

1. Rozszerzanie klasy record: dodatkowe metody

Czy można dodać metody do record?

Oczywiście! Record to jak mieszkanie po generalnym remoncie: ściany i podłoga są już gotowe, nie można ich zmieniać, ale nikt nie zabrania ustawić meble po swojemu. Wewnątrz record można deklarować zwykłe metody, metody statyczne, a nawet przechowywać stałe. To oznacza, że logiki biznesowej nie trzeba wynosić do osobnych klas „pomocniczych” – można ją elegancko wbudować bezpośrednio w sam record.

Przykład: metoda obliczająca odległość między punktami

Załóżmy, że mamy record dla punktu na płaszczyźnie:


public record Point(int x, int y) {
    // Dodatkowa metoda
    public double distanceTo(Point other) {
        int dx = this.x - other.x;
        int dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}

Teraz można zrobić tak:


Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
System.out.println(p1.distanceTo(p2)); // 5.0

Jak widać, klasę record można „wzbogacać” własnymi metodami – i to jest bardzo wygodne!

Przykład: metoda statyczna


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

    public static Rectangle square(int size) {
        return new Rectangle(size, size);
    }
}

Teraz można utworzyć „kwadrat” jednym wywołaniem:


Rectangle r = Rectangle.square(5);
System.out.println(r.area()); // 25

2. Kompaktowy konstruktor i walidacja danych

Po co „kompaktowy” konstruktor?

Kanoniczny konstruktor record powstaje automatycznie i przypisuje parametry do pól. Czasem jednak chcemy dodać sprawdzanie danych wejściowych (na przykład zabronić ujemnych współrzędnych).

W zwykłej klasie napisalibyśmy:


public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        if (x < 0 || y < 0) throw new IllegalArgumentException();
        this.x = x;
        this.y = y;
    }
    // ...
}

W klasie record można zadeklarować kompaktowy konstruktor – bez powtarzania listy parametrów i bez jawnego przypisywania pól (robi to za nas kompilator).

Składnia kompaktowego konstruktora


public record Point(int x, int y) {
    public Point {
        if (x < 0 || y < 0) {
            throw new IllegalArgumentException("Coordinates must be non-negative");
        }
        // Nie trzeba pisać: this.x = x; this.y = y;
    }
}
  • Parametry konstruktora automatycznie odpowiadają komponentom record.
  • Przypisanie this.x = x i this.y = y wykonuje kompilator automatycznie po zakończeniu ciała konstruktora (po pomyślnym wyjściu).
  • Jeśli zostanie rzucony wyjątek, obiekt nie zostanie utworzony.

Przykład z walidacją


Point p1 = new Point(3, 5); // OK
Point p2 = new Point(-1, 2); // Rzuci IllegalArgumentException!

Czy można zadeklarować „zwykły” konstruktor?

Tak, można! Jeśli trzeba utworzyć konstruktor z inną listą parametrów lub dodatkowymi krokami, zadeklaruj go jawnie:


public record Range(int from, int to) {
    public Range(int size) {
        this(0, size); // wywołuje konstruktor kanoniczny
    }
}

3. Ograniczenia klas record

W przeciwieństwie do zwykłych klas, record ma szereg ograniczeń. Warto o tym pamiętać, aby nie dziwić się błędom kompilacji.

Tylko komponenty – żadnych dodatkowych niestatycznych pól

W record nie można deklarować nowych pól niestatycznych:


public record Person(String name, int age) {
    // int id; // Błąd kompilacji! Nie można dodawać pól niestatycznych.
}

Można deklarować pola i metody statyczne:


public record Person(String name, int age) {
    public static final String SPECIES = "Homo sapiens";
}

Record jest zawsze final

Klasa record nie może być klasą bazową (nie można po niej dziedziczyć) i sama nie może jawnie dziedziczyć po innej klasie (poza niejawnym dziedziczeniem po java.lang.Record). To oznacza, że klasa record jest zawsze „końcową” strukturą.


public record User(String login) { }

// public class Admin extends User {} // Błąd: dziedziczenie po record jest zabronione!

Można implementować interfejsy

Klasa record może implementować interfejsy:


public interface Printable {
    void print();
}

public record Invoice(int amount) implements Printable {
    @Override
    public void print() {
        System.out.println("Suma: " + amount);
    }
}

4. Przykłady: rozbudowane recordy w realnych zadaniach

Przyjrzyjmy się kilku praktycznym przykładom, gdzie dodatkowe metody i kompaktowe konstruktory czynią klasę record naprawdę użyteczną.

Record z metodą obliczeniową


public record Circle(double x, double y, double radius) {
    public double area() {
        return Math.PI * radius * radius;
    }

    public double distanceTo(Circle other) {
        double dx = x - other.x;
        double dy = y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}

Record z walidacją


public record Email(String value) {
    public Email {
        if (value == null || !value.contains("@")) {
            throw new IllegalArgumentException("Niepoprawny email: " + value);
        }
    }
}

Teraz nie można utworzyć niepoprawnego adresu e-mail:


Email e1 = new Email("test@example.com"); // OK
Email e2 = new Email("not-an-email"); // Rzuci IllegalArgumentException

Record z dodatkowymi metodami statycznymi


public record Temperature(double celsius) {
    public static Temperature fromFahrenheit(double fahrenheit) {
        return new Temperature((fahrenheit - 32) * 5 / 9);
    }

    public double toFahrenheit() {
        return celsius * 9 / 5 + 32;
    }
}

Użycie:


Temperature t = Temperature.fromFahrenheit(98.6);
System.out.println(t.celsius()); // 37.0
System.out.println(t.toFahrenheit()); // 98.6

5. Kompaktowy konstruktor: niuanse i ograniczenia

Kiedy używać kompaktowego konstruktora?

  • Gdy trzeba zweryfikować poprawność danych (walidacja).
  • Gdy trzeba zmienić wartości przed zapisaniem (np. zaokrąglić liczbę lub zamienić napis na wielkie litery).
  • Gdy chcesz uniknąć powielania listy parametrów.

Cechy działania

  • W kompaktowym konstruktorze nie wolno jawnie przypisywać wartości komponentom (this.x = ...) — spowoduje to błąd kompilacji, ponieważ przypisanie wykonuje kompilator po zakończeniu ciała konstruktora.
  • W kompaktowym konstruktorze nie można zmieniać nazw parametrów — zawsze odpowiadają one nazwom komponentów record.

Przykład: automatyczne zaokrąglanie


public record Money(double amount) {
    public Money {
        amount = Math.round(amount * 100) / 100.0; // Zaokrąglamy do groszy
    }
}

6. Praktyka: rozwijamy aplikację edukacyjną

Załóżmy, że tworzymy operacje bankowe w aplikacji edukacyjnej. Niech będzie record-klasa Transaction, która przechowuje kwotę, nadawcę i odbiorcę.


public record Transaction(String from, String to, double amount) {
    public Transaction {
        if (amount <= 0) throw new IllegalArgumentException("Kwota musi być dodatnia");
        if (from == null || to == null) throw new IllegalArgumentException("Pola nie mogą być null");
    }

    public String description() {
        return String.format("Przelew %.2f od %s do %s", amount, from, to);
    }
}

Użycie:


Transaction t = new Transaction("Alice", "Bob", 150.0);
System.out.println(t.description()); // Przelew 150.00 od Alice do Boba

Próba utworzenia niepoprawnej transakcji spowoduje błąd:


Transaction t2 = new Transaction("Alice", "Bob", -10.0); // IllegalArgumentException

Tabela: co można, a czego nie można w klasie record

Dozwolone w klasie record Zabronione w klasie record
Zwykłe metody Nowe pola niestatyczne
Metody i pola statyczne Dziedziczenie po innych klasach
Implementowanie interfejsów Być klasą bazową dla innych
Kompaktowe i zwykłe konstruktory Modyfikowanie komponentów po utworzeniu
Nadpisywanie metod Używanie setterów

7. Typowe błędy przy pracy z recordami z niestandardowym ciałem

Błąd nr 1: próba dodania pola niestatycznego.
Nowicjusze często próbują dodać do record „jeszcze jedno pole do logiki wewnętrznej” – na przykład licznik lub cache. To się nie uda: kompilator natychmiast zgłosi błąd. Jeśli trzeba przechowywać dodatkowy stan, najprawdopodobniej record nie jest właściwym wyborem.

Błąd nr 2: zapomniana walidacja w kompaktowym konstruktorze.
Jeśli chcesz, aby obiekt zawsze był poprawny, wykonaj sprawdzanie w kompaktowym konstruktorze. Nie zakładaj, że „użytkownik sam nie wprowadzi bzdur”.

Błąd nr 3: próba zmiany komponentu po utworzeniu.
Pola klasy recordfinal – nie można ich zmieniać ani bezpośrednio, ani przez metody. Jeśli potrzebujesz struktury modyfikowalnej – użyj zwykłej klasy.

Błąd nr 4: duplikowanie logiki w metodach i konstruktorze.
Czasem logikę sprawdzania i obliczeń próbuje się dublować i w metodach, i w konstruktorze. Lepiej wykonywać całą walidację w konstruktorze, a metody zostawić dla „czystej” logiki biznesowej.

Błąd nr 5: zapomniano o ograniczeniach dziedziczenia.
Klasa record jest zawsze final – nie można utworzyć jej podklasy. Jeśli projektujesz hierarchię, w której potrzebne są podklasy – użyj zwykłych klas.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION