JavaRush /Java Blog /Random-KO /커피 브레이크 #128. Java 레코드 가이드

커피 브레이크 #128. Java 레코드 가이드

Random-KO 그룹에 게시되었습니다
출처: abhinavpandey.dev 이 튜토리얼에서는 Java에서 레코드를 사용하는 기본 사항을 다룹니다. 불변 객체를 활용하면서 Value 객체 생성과 관련된 상용구 코드를 제거하는 방법으로 레코드가 Java 14에 도입되었습니다. 커피 브레이크 #128.  Java 기록 가이드 - 1

1. 기본 개념

항목 자체에 들어가기 전에 항목이 해결하는 문제를 살펴보겠습니다. 이를 위해서는 Java 14 이전에 값 개체가 어떻게 생성되었는지 기억해야 합니다.

1.1. 값 객체

값 객체는 Java 애플리케이션의 필수적인 부분입니다. 애플리케이션 계층 간에 전송해야 하는 데이터를 저장합니다. 값 개체에는 해당 필드에 액세스하기 위한 필드, 생성자 및 메서드가 포함되어 있습니다. 다음은 값 개체의 예입니다.
public class Contact {
    private final String name;
    private final String email;

    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

1.2. 값 개체 간의 평등

값 개체는 값 개체가 동일한지 비교하는 방법을 제공할 수도 있습니다. 기본적으로 Java는 메모리 주소를 비교하여 객체의 동등성을 비교합니다. 그러나 어떤 경우에는 동일한 데이터를 포함하는 객체가 동일한 것으로 간주될 수 있습니다. 이를 구현하기 위해 equals.hashCode 메소드를 재정의할 수 있습니다 . Contact 클래스 에 대해 구현해 보겠습니다 .
public class Contact {

    // ...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Contact contact = (Contact) o;
        return Object.equals(email, contact.email) &&
                Objects.equals(name, contact.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, email);
    }
}

1.3. 값 객체의 불변성

값 객체는 변경할 수 없어야 합니다. 이는 객체의 필드를 변경할 수 있는 방법을 제한해야 함을 의미합니다. 이는 다음과 같은 이유로 권장됩니다.
  • 실수로 필드 값을 변경하는 위험을 방지합니다.
  • 동일한 대상이 평생 동안 동일하게 유지되도록 보장합니다.
Contact 클래스는 이미 변경 불가능 하므로 이제 다음을 수행합니다.
  1. 필드를 비공개최종으로 만들었습니다 .
  2. 각 필드에 getter 만 제공했습니다 ( setter 없음 ).

1.4. 값 개체 등록

객체에 포함된 값을 등록해야 하는 경우가 많습니다. 이는 toString 메소드를 제공하여 수행됩니다 . 객체가 등록되거나 인쇄될 때마다 toString 메소드가 호출됩니다 . 여기서 가장 쉬운 방법은 각 필드의 값을 인쇄하는 것입니다. 예는 다음과 같습니다.
public class Contact {
    // ...
    @Override
    public String toString() {
        return "Contact[" +
                "name='" + name + '\'' +
                ", email=" + email +
                ']';
    }
}

2. 기록으로 템플릿 줄이기

대부분의 값 개체에는 동일한 요구 사항과 기능이 있으므로 해당 개체를 만드는 과정을 단순화하는 것이 좋습니다. 녹음이 이를 달성하는 데 어떻게 도움이 되는지 살펴보겠습니다.

2.1. Person 클래스를 Record로 변환

위에 정의된 Contact 클래스 와 동일한 기능을 가진 Contact 클래스 항목을 만들어 보겠습니다 .
public record Contact(String name, String email) {}
Record 키워드는 Record 클래스를 생성하는 데 사용됩니다 . 레코드는 클래스와 동일한 방식으로 호출자가 처리할 수 있습니다. 예를 들어, 새 항목 인스턴스를 생성하려면 new 키워드를 사용할 수 있습니다 .
Contact contact = new Contact("John Doe", "johnrocks@gmail.com");

2.2. 기본 동작

코드를 한 줄로 줄였습니다. 포함된 내용을 나열해 보겠습니다.
  1. 이름이메일 필드는 기본적으로 비공개이며 최종입니다.

  2. 코드는 필드를 매개변수로 사용하는 "정규 생성자"를 정의합니다.

  3. 필드는 getter와 유사한 메소드( name()email() ) 를 통해 액세스할 수 있습니다 . 필드에 대한 설정자가 없으므로 객체의 데이터는 변경할 수 없습니다.

  4. Contact 클래스 에서 했던 것처럼 필드를 인쇄하기 위해 toString 메소드를 구현했습니다 .

  5. equals.hashCode 메소드를 구현했습니다 . 여기에는 Contact 클래스 와 마찬가지로 모든 필드가 포함됩니다 .

2.3 정식 생성자

기본 생성자는 모든 필드를 입력 매개변수로 사용하여 필드로 설정합니다. 예를 들어 기본 정식 생성자는 다음과 같습니다.
public Contact(String name, String email) {
    this.name = name;
    this.email = email;
}
녹음 클래스에 동일한 시그니처를 가진 생성자를 정의하면 표준 생성자 대신 사용됩니다.

3. 기록 작업

여러 가지 방법으로 항목의 동작을 변경할 수 있습니다. 몇 가지 사용 사례와 이를 달성하는 방법을 살펴보겠습니다.

3.1. 기본 구현 재정의

모든 기본 구현은 이를 재정의하여 변경할 수 있습니다. 예를 들어, toString 메소드 의 동작을 변경하려면 중괄호 {} 사이에 이를 재정의할 수 있습니다 .
public record Contact(String name, String email) {
    @Override
    public String toString() {
        return "Contact[" +
                "name is '" + name + '\'' +
                ", email is" + email +
                ']';
    }
}
마찬가지로, equalshashCode 메소드를 재정의할 수 있습니다 .

3.2. 컴팩트한 구성 키트

때때로 우리는 생성자가 필드를 초기화하는 것 이상의 작업을 수행하기를 원합니다. 이를 위해 Compact Constructor의 항목에 필요한 작업을 추가할 수 있습니다. 필드 초기화나 매개변수 목록을 정의할 필요가 없기 때문에 컴팩트하다고 합니다.
public record Contact(String name, String email) {
    public Contact {
        if(!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
}
매개변수 목록이 없으며 확인이 수행되기 전에 백그라운드에서 이름이메일 초기화가 수행됩니다.

3.3. 생성자 추가

레코드에 여러 생성자를 추가할 수 있습니다. 몇 가지 예와 제한 사항을 살펴보겠습니다. 먼저 유효한 새 생성자를 추가해 보겠습니다.
public record Contact(String name, String email) {
    public Contact(String email) {
        this("John Doe", email);
    }

    // replaces the default constructor
    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }
}
첫 번째 경우에는 this 키워드를 사용하여 기본 생성자에 액세스합니다 . 두 번째 생성자는 동일한 매개변수 목록을 갖고 있으므로 기본 생성자를 재정의합니다. 이 경우 항목 자체는 기본 생성자를 생성하지 않습니다. 생성자에는 몇 가지 제한 사항이 있습니다.

1. 기본 생성자는 항상 다른 생성자에서 호출되어야 합니다.

예를 들어 아래 코드는 컴파일되지 않습니다.
public record Contact(String name, String email) {
    public Contact(String name) {
        this.name = "John Doe";
        this.email = null;
    }
}
이 규칙은 필드가 항상 초기화되도록 보장합니다. 또한 압축 생성자에 정의된 작업이 항상 실행된다는 것도 보장됩니다.

2. 압축 생성자가 정의된 경우 기본 생성자를 재정의할 수 없습니다.

압축 생성자가 정의되면 초기화 및 압축 생성자 논리를 사용하여 기본 생성자가 자동으로 생성됩니다. 이 경우 컴파일러는 기본 생성자와 동일한 인수를 가진 생성자를 정의하는 것을 허용하지 않습니다. 예를 들어, 다음 코드에서는 컴파일이 발생하지 않습니다.
public record Contact(String name, String email) {
    public Contact {
        if(!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

3.4. 인터페이스 구현

다른 클래스와 마찬가지로 레코드에도 인터페이스를 구현할 수 있습니다.
public record Contact(String name, String email) implements Comparable<Contact> {
    @Override
    public int compareTo(Contact o) {
        return name.compareTo(o.name);
    }
}
중요 사항. 완전한 불변성을 보장하기 위해 레코드는 상속될 수 없습니다. 항목은 최종적이며 확장할 수 없습니다. 또한 다른 클래스를 확장할 수도 없습니다.

3.5. 메소드 추가

메서드와 인터페이스 구현을 재정의하는 생성자 외에도 원하는 메서드를 추가할 수도 있습니다. 예를 들어:
public record Contact(String name, String email) {
    String printName() {
        return "My name is:" + this.name;
    }
}
정적 메소드를 추가할 수도 있습니다. 예를 들어 이메일을 확인할 수 있는 정규식을 반환하는 정적 메서드를 갖고 싶다면 아래와 같이 정의할 수 있습니다.
public record Contact(String name, String email) {
    static Pattern emailRegex() {
        return Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
    }
}

3.6. 필드 추가

레코드에 인스턴스 필드를 추가할 수 없습니다. 그러나 정적 필드를 추가할 수 있습니다.
public record Contact(String name, String email) {
    private static final Pattern EMAIL_REGEX_PATTERN = Pattern
            .compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);

    static Pattern emailRegex() {
        return EMAIL_REGEX_PATTERN;
    }
}
정적 필드에는 암시적 제한이 없습니다. 필요한 경우 최종 공개되지 않고 공개적으로 제공될 수 있습니다.

결론

레코드는 데이터 클래스를 정의하는 좋은 방법입니다. 이는 JavaBeans/POJO 접근 방식보다 훨씬 편리하고 강력합니다. 구현하기 쉽기 때문에 값 개체를 생성하는 다른 방법보다 선호되어야 합니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION