Java - Record

החל מ JAVA 16 ומעלה, האופציה של שימוש ב record פתוחה לנו כמפתחים.

למה אנחנו צריכים Record?

אם היינו רוצים מחלקה שתחזיק לנו נתונים "יבשים" כמו גיל, שם, טלפון וכו' היינו צריכים לכתוב מחלקה עם מלא קוד: קונסטרקטור, Getter ים, ()equals(), hashCode, ו() toString.

Record בא לייתר לנו את כתיבת הקוד המלא, הוא מחלקה מיוחדת שמוגדרת כנתונים טהורים (Data Carrier). ברגע שנגדיר Record, הקומפיילר של Java מייצר עבורנו אוטומטית את כל הקונסטרקטורים, המתודות וההשוואות מאחורי הקלעים.

איך מייצרים record?

שורה אחת ויש לנו record!

public record User(String name, int age, String phoneNumber) { }

איך משתמשים ב record?

ניצור רשומה עם כל השדות שהגדרנו, ואז פשוט אפשר לקרוא לכל פיסת מידע שהוספנו בקוד.

⚠️ שימו לב!

כפי שניתן לראות בקוד ה toString כבר נוצר לנו אוטומטית, ללא כל מעורבות שלנו.

User user = new User("Alice", 30, "050 12345678");
System.out.println(user.name()); // "Alice"
System.out.println(user);        // User[name=Alice, age=30, phoneNumber=050 12345678]

מה השימושים העיקריים ב record?

ירושה של Record

⚠️ שימו לב!

Record הם אימוטביליים (Immutable) לחלוטין: כל השדות ב Record מוגדרים אוטומטית כ private final. אי אפשר לשנות את הערך של שדה אחרי שה Record נוצר אין Setter. אם צריך ערך שונה, יוצרים אובייקט חדש. רוצים להרחיב על נושא ה Immutable? כאן תמצאו עוד מידע עליו.

יתכנו בעיות עם פריימוורקים ישנים (כמו JPA/Hibernate): פריימוורקים מסוימים לניהול בסיסי נתונים דורשים קונסטרקטור ריק (No args constructor) ואפשרות לשנות שדות (Setters). מכיוון של Records אין את אלה, עבודה איתם בתוך ישויות DB דורשת לפעמים התאמות או ספריות צד שלישי כמו Jackson ב JSON שדווקא תומך בהם מעולה.

אי אפשר להוסיף שדות מנוהלים נוספים: כל השדות חייבים להיות מוגדרים ישירות בכותרת של ה Record, השדות שהוגדרו במלואם וללא חסרים.

שדות ומתודות סטטיים

למרות שאסור להוסיף שדות רגילים, כלומר שדות שניתנים לשינוי עם set, מותר לגמרי להוסיף שדות סטטיים: כמו קבועים (public static final String DEFAULT_NAME = "Guest") ומתודות סטטיות.

זה נפוץ מאוד למשל כשנרצה ליצור מתודת Factory (כמו of(...)) שמייצרת את ה Record בצורה חכמה. במקום שקוד חיצוני יצטרך לקרוא ישירות ל new User(...) ולהסתבך עם העברת פרמטרים או ברירת מחדל, אנחנו קוראים למתודה בעלת משמעות (כמו User.of או User.createGuest)) שתעשה את העבודה מאחורי הקלעים: היא יכולה להוסיף ערכי ברירת מחדל, לנקות נתונים, או אפילו להחליט איזו גרסה של אובייקט ליצור בהתאם למה שהועבר.

public record User(String name, int age, String phoneNumber) {

    // 1. Static constant fields shared across all record instances
    public static final int DEFAULT_AGE = 18;
    public static final String DEFAULT_PHONE = "050 00000000";

    // Compact Constructor for standard validation
    public User {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be null or blank!");
        }
    }

    // 2. Static Factory Method: Creates a User with a default phone number
    public static User of(String name, int age) {
        // Calls the standard constructor with a fallback phone number
        return new User(name, age, DEFAULT_PHONE);
    }

    // 3. Static Factory Method: Creates an adult user with a default age
    public static User createAdult(String name, String phoneNumber) {
        // Uses the static constant DEFAULT_AGE
        return new User(name, DEFAULT_AGE, phoneNumber);
    }
}

איך נשתמש במתודות שיצרנו? כמו כל מתודה אחרת בקוד (דוגמת רצה ב MAIN):

public class Main {
    public static void main(String[] args) {

        // Using static factory method 'of' (provides default phone automatically)
        User user1 = User.of("Alice", 25);
        System.out.println(user1);
        // Output: User[name=Alice, age=25, phoneNumber=050 00000000]

        // Using static factory method 'createAdult' (provides default age automatically)
        User user2 = User.createAdult("Bob", "054 12345678");
        System.out.println(user2);
        // Output: User[name=Bob, age=18, phoneNumber=054 12345678]

        // Accessing the static constant fields directly from the Record name
        System.out.println("Default user age is: " + User.DEFAULT_AGE);
    }
}

Compact Constructor

למרות שלא נדרש Constructor ב record, ניתן להוסיף Constructor על מנת ליצור ולדיציה לנתונים שלנו.

איך זה עובד?

ב Compact Constructor, אנחנו לא כותבים את הפרמטרים בסוגריים ולא מעבירים מחדש את השדות ל this. Java כבר יודעת לבד מה הפרמטרים כי הגדרנו אותם בשורת ה Record הראשית. אנחנו פשוט ניגשים לשדות ישירות ובודקים אותם לפני שהם נשמרים.

דוגמת קוד:

נניח שאנחנו רוצים לוודא שגיל של משתמש לא יכול להיות שלילי, וששם המשתמש לעולם אינו ריק או null:

public record User(String name, int age, String phoneNumber) {

    // Compact Constructor - no parameter parentheses required!
    public User {
        // 1. Check if the name is null or blank
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Name cannot be null or blank!");
        }

        // 2. Check if the age is valid
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative: " + age);
        }

        // 3. Validate the phone number:
        //   Must not be null
        //   Must be strictly greater than 12 characters long
        //   Must have a hyphen (' ') exactly after the first 3 characters (index 3)
        //   All other characters (before and after the hyphen) must be digits only
        if (phoneNumber == null || phoneNumber.length() != 12) {
            throw new IllegalArgumentException("Phone number must be exactly 12 characters long!");
        }

        if (phoneNumber.charAt(3) != ' ') {
            throw new IllegalArgumentException("Phone number must contain a hyphen (' ') after the first 3 characters!");
        }

        // Validate that everything else consists strictly of digits
        for (int i = 0; i < phoneNumber.length(); i++) {
            if (i == 3) continue; // Skip the hyphen at index 3

            if (!Character.isDigit(phoneNumber.charAt(i))) {
                throw new IllegalArgumentException("Phone number can only contain digits (except for the hyphen at index 3)!");
            }
        }

        // No need to write this.name = name; > Java assigns the fields automatically at the end of the block!
    }
}

מה מיוחד בזה?

זה הופך את ה Records לא רק למחלקות נתונים נוחות, אלא גם ליותר בטוחות באמצעות ולידציות.

🏠 Back to Orly's Code Corner