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 record są final – 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.
GO TO FULL VERSION