1. «DTO-siniflər» problemi: bizə record-siniflər niyə lazımdır?
Düzünü deyək: bu cür sinfi nə qədər dəfə yazmısınız?
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Point{" + "x=" + x + ", y=" + y + '}';
}
}
Guya çətin deyil, amma sətirləri sayın! İndi təsəvvür edin ki, sizdə belə 10 sinif var və hər birində 5–6 sahə. Hətta IDE belə bu şablon kodu generasiya etməkdən yorulur. Və əgər yeni sahə əlavə etmək qərarına gəlsəniz — konstruktoru, getter-i, equals, hashCode, toString metodlarını dəyişməli olacaqsınız... Darıxdırıcı, rutin və səhv mənbəyi.
Belə siniflərə DTO (Data Transfer Object) və ya Value Object deyilir. Onlar sadəcə məlumat saxlayır — vəssalam. Amma şablon kod səbəbindən onları dəstəkləmək çətindir.
Əgər sizə elə gəlirsə ki, bu problem deyil, sadəcə belə 50 sinfi birdən dəyişməli olacağınız vaxtı gözləyin. Onda record-siniflərini xüsusi rəğbətlə xatırlayacaqsınız!
2. Record-a giriş: sintaksis və Java 16+-ın sehrləri
Java 16 ilə hər şey dəyişdi. Yeni sinif növü — record yarandı. Onlar sadəcə verilənlər dəstini saxlamaq lazım olan hallar üçün xüsusi yaradılıb. Sintaksis — digər dillərdəki kortej kimidir.
Record necə elan edilir?
public record Point(int x, int y) { }
Və... vəssalam! Siz indicə iki sahəsi, konstruktoru, getter-ləri, equals, hashCode və toString olan dəyişməz sinif yaratdınız. Artıq yazısız.
Java «qapağın altında» nə edir?
- Sahələr x və y private final olur.
- Getter-lər avtomatik yaradılır: int x() və int y().
- Konstruktor: public Point(int x, int y).
- equals/hashCode: bütün sahələri dəyərə görə müqayisə edir.
- toString: "Point[x=1, y=2]" kimi sətri qaytarır.
Belə demək olar ki, record — «steroidli DTO»dur: daha az kod, daha çox zəmanət, daha az xəta.
Dəyişməzlik (immutability)
Record-sinifinin bütün sahələri avtomatik final olur. Obyekt yaradıldıqdan sonra onu dəyişmək olmaz — bunu kompilyator təmin edir.
Əgər setter əlavə etməyə və ya sahəni final etməməyə çalışsanız — kompilyator sizi saxlayacaq. Sizin rahatlığınıza belə qayğı nadir hallarda rast gəlinir!
3. Record-sinifindən istifadə nümunəsi
Adi sinif (çox kod):
public class Client {
private final String name;
private final int id;
public Client(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() { return name; }
public int getId() { return id; }
// equals, hashCode, toString ...
}
Record sinfi (bir sətir!):
public record Client(String name, int id) { }
İstifadə:
public class Main {
public static void main(String[] args) {
Client client = new Client("İvan", 123);
System.out.println(client.name()); // İvan
System.out.println(client.id()); // 123
System.out.println(client); // Client[name=İvan, id=123]
}
}
Diqqət edin:
- Sahələrə giriş metodlarının adı sahələrin öz adı kimidir: name(), id().
- setName() və ya setId() yoxdur — obyekt yaradıldıqdan sonra dəyişdirilə bilməz.
4. Record-siniflərinin üstünlükləri: daha az kod, daha az səhv
Daha az kod — daha çox xoşbəxtlik
40 sətir yazmağa nə ehtiyac var, bir sətirlə də etmək olar! Record-sinifləri vaxtı və əsəbləri qoruyur, xüsusən çoxlu DTO və value obyektləri olan böyük layihələrdə.
Dəyişməzlik “kontrakt” üzrə
- Record-sinifləri həmişə final və dəyişməzdir.
- Obyekti saxtalaşdırmaq və ya təsadüfən dəyişmək mümkün deyil.
- Obyektin vəziyyətinin gözlənilməz yerdə dəyişməsindən doğan « qəribə » xətalar yoxdur.
- Çoxaxınlı proqramlarda təhlükəsiz istifadə oluna bilər (bütün sahələr də immutable olduqda).
equals/hashCode/toString-un avtomatik generasiyası
Müqayisə, hash hesablama və səliqəli çıxış metodlarını əllə yazmağa ehtiyac yoxdur. Hər şey avtomatik və düzgün edilir.
Client c1 = new Client("Anna", 42);
Client c2 = new Client("Anna", 42);
System.out.println(c1.equals(c2)); // true
System.out.println(c1.hashCode() == c2.hashCode()); // true
System.out.println(c1); // Client[name=Anna, id=42]
Kolleksiyalar və açarlar üçün ideal
Record-obyektləri HashMap-də açar, HashSet-də element və s. kimi istifadə etmək olar — hər şey düzgün işləyəcək, çünki equals və hashCode bütün sahələri nəzərə alır.
import java.util.HashMap;
import java.util.Map;
Map<Client, String> clients = new HashMap<>();
clients.put(new Client("Anna", 42), "VIP");
System.out.println(clients.get(new Client("Anna", 42))); // VIP
Məlumatların açıq təsviri
Record-sinfisinin sintaksisi dərhal hansı məlumatların saxlandığını və obyektin dəyişməz olduğunu göstərir. Bu, kodu digər tərtibatçılar üçün (və altı ay sonra sizin üçün) daha anlaşılan edir.
5. Cədvəl: adi sinif və record sinfinin müqayisəsi
| Adi sinif | Record sinfi | |
|---|---|---|
| Sintaksis | Çox kod | Bir sətir |
| Dəyişməzlik | Əl ilə təmin edilməlidir | Kompilyator tərəfindən təmin olunur |
| Metodların avtomatik generasiyası | Yoxdur | Bəli (equals, hashCode, toString) |
| Sahə əlavə etmək | Bəli | Yalnız record komponentləri |
| İrsiyyət | İrs almaq olar | Həmişə final, irs almaq olmaz |
| Kolleksiyalarda istifadə | Metodları düzgün reallaşdırmaq lazımdır | «Qutudan çıxan kimi» işləyir |
6. Tədris tətbiqini inkişaf etdirək: record sinfi ilə nümunə
Tutaq ki, tədris bank tətbiqinizdə hesab əməliyyatlarını saxlamaq lazımdır: tarix, məbləğ, əməliyyatın tipi (məsələn, «mədaxil» və ya «çıxarış»).
Java 16-dan əvvəl:
public class Transaction {
private final LocalDate date;
private final double amount;
private final String type;
public Transaction(LocalDate date, double amount, String type) {
this.date = date;
this.amount = amount;
this.type = type;
}
public LocalDate getDate() { return date; }
public double getAmount() { return amount; }
public String getType() { return type; }
// equals, hashCode, toString ...
}
Java 16 və record ilə:
import java.time.LocalDate;
public record Transaction(LocalDate date, double amount, String type) { }
İstifadə:
Transaction t = new Transaction(LocalDate.now(), 100.0, "deposit");
System.out.println(t); // Transaction[date=2024-06-01, amount=100.0, type=deposit]
System.out.println(t.amount()); // 100.0
7. Vizualizasiya: record nə yaradır
«Açılmış» record-sinfinə baxaq (təxmini kompilyatorun yaradacağı budur):
public final class Point extends java.lang.Record {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int x() { return x; }
public int y() { return y; }
@Override
public boolean equals(Object o) { /* sahələr üzrə müqayisə */ }
@Override
public int hashCode() { /* sahələr üzrə hesablanma */ }
@Override
public String toString() { /* səliqəli çıxış */ }
}
Qısa olaraq: record nə vaxt istifadə olunmalıdır
- Dəyişməz verilənlər dəstinə malik obyekt lazım olanda.
- Əl ilə yazmadan «dürüst» equals/hashCode/toString lazım olanda.
- DTO, Value Object, cütlər, üçlüklər, rəng, nöqtə, interval, kolleksiya üçün açar və s. hazırlayanda.
8. Record-sinifləri ilə işləyərkən tipik səhvlər
Səhv №1: yaradıldıqdan sonra setter əlavə etmək və ya sahəni dəyişmək cəhdi.
Record-sinfi öz sahələrini dəyişməyə imkan vermir. setX(int x) kimi bir metod əlavə etməyə çalışsanız, kompilyator dərhal “olmaz” deyəcək. Eyni şey — sahəni birbaşa dəyişməyə çalışsanız da.
Səhv №2: statik olmayan sahə əlavə etmək cəhdi.
Record-sinfində yalnız komponentlər (record adından sonra mötərizədə göstərilən sahələr) və statik sahələr elan edilə bilər. Adi, statik olmayan sahələr əlavə etmək olmaz — kompilyator buna icazə verməyəcək.
Səhv №3: dəyişən (mutable) məntiq üçün record-dan istifadə.
Record-sinifləri dəyişən vəziyyətli obyektlər üçün nəzərdə tutulmayıb. Yaradıldıqdan sonra nələrisə dəyişmək lazımdırsa — adi sinifdən istifadə edin.
Səhv №4: record-un həmişə final olduğunu unutmaq.
Record-sinfi nə miras alına bilər, nə də onu super sinif etmək olar. Bu məhdudiyyəti pozmağa cəhd kompilyasiya xətası ilə nəticələnəcək. Əsas qayda — record-u «genişləndirməyə» çalışmırıq: o, tamamlanmış dəyişməz tip kimi nəzərdə tutulub.
Səhv №5: avtomatik generasiya olunan metodları görməməzlikdən gəlmək.
Əgər equals, hashCode və ya toString metodlarını override edirsinizsə, ehtiyatlı olun — onların kontraktını pozmayın, yoxsa kolleksiyalar və müqayisələr düzgün işləməyə bilər.
GO TO FULL VERSION