∮explotación≒ 개발

java Record

TipoAzul 2023. 2. 25.
반응형
Using Record to Model Immutable Data


 

레코드를 사용하여 불변 데이터 모델링

Java 언어는 불변 클래스를 만드는 여러 가지 방법을 제공합니다. 아마도 가장 간단한 방법은 최종 필드와 이러한 필드를 초기화 할 생성자를 가진 최종 클래스를 만드는 것입니다. 다음은 그러한 클래스의 예입니다.

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

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
 

이러한 요소를 작성 했으므로 필드에 대한 액세스자를 추가해야합니다. 당신은 또한 추가 할 것입니다 toString() 방법과 아마 equals() 함께 hashCode() 방법. 이 모든 것을 손으로 쓰는 것은 매우 지루하고 오류가 발생하기 쉽습니다. 다행히도 IDE가 이러한 방법을 생성 할 수 있습니다.

네트워크를 통해 또는 파일 시스템을 통해이 클래스의 인스턴스를 한 응용 프로그램에서 다른 응용 프로그램으로 전송해야하는 경우, 이 클래스를 직렬화 할 수도 있습니다. 그렇게하면이 클래스의 인스턴스가 직렬화되는 방법에 대한 정보를 추가해야 할 수도 있습니다. JDK는 직렬화를 제어하는 여러 가지 방법을 제공합니다.

결국, 당신의 Point 클래스는 파일에 써야하는 두 개의 정수로 불변 집계를 모델링하기 위해 IDE에 의해 생성 된 코드로 채워진 백 줄 길이 일 수 있습니다.

이를 변경하기 위해 JDK에 레코드가 추가되었습니다. 레코드는이 모든 것을 한 줄의 코드로 제공합니다. 레코드의 상태를 선언하기 만하면됩니다. 나머지는 컴파일러에서 생성합니다.

 

구조에 기록 호출

이 코드를 훨씬 간단하게 만드는 데 도움이되는 기록이 있습니다. Java SE 14부터 다음 코드를 작성할 수 있습니다.

public record Point(int x, int y) {}
 

이 단일 코드 줄은 다음과 같은 요소를 만듭니다.

  1. 두 개의 필드가있는 불변의 클래스입니다: x  y, 유형의 int.
  2. 이 두 필드를 초기화하기위한 표준 생성자가 있습니다.
  3. 그만큼 toString(), equals()  hashCode() IDE가 생성 한 것에 해당하는 기본 동작을 가진 컴파일러가 메소드를 작성했습니다. 필요한 경우 이러한 메소드의 자체 구현을 추가하여이 동작을 수정할 수 있습니다.
  4. 구현할 수 있습니다 Serializable 인스턴스를 보낼 수 있도록 인터페이스 Point 네트워크 또는 파일 시스템을 통해 다른 응용 프로그램에. 레코드가 직렬화되고 직렬화 해제되는 방식은이 자습서 끝에서 다루는 몇 가지 특수 규칙을 따릅니다.

레코드는 IDE의 도움없이 불변의 데이터 집합을 훨씬 간단하게 만듭니다. 레코드의 구성 요소를 수정할 때마다 컴파일러가 자동으로 equals()  hashCode() 당신을위한 방법.

 

기록의 종류

레코드는 record 대신 키워드 class 키워드. 다음 기록을 선언하겠습니다.

public record Point(int x, int y) {}
 

레코드를 만들 때 컴파일러가 생성하는 클래스는 최종입니다.

이 클래스는 java.lang.Record 수업. 따라서 귀하의 기록은 어떤 수업도 연장 할 수 없습니다.

레코드는 여러 인터페이스를 구현할 수 있습니다.

 

레코드의 구성 요소 선언

레코드 이름 바로 뒤에 오는 블록은 (int x, int y). 그것은 선언 구성 요소 명명 된 기록 Point. 레코드의 각 구성 요소에 대해 컴파일러는이 구성 요소와 동일한 이름을 가진 개인 최종 필드를 만듭니다. 레코드에 여러 구성 요소를 선언 할 수 있습니다.

이 예에서 컴파일러는 두 가지 개인 최종 필드를 만듭니다 int: x  y, 선언 한 두 구성 요소에 해당합니다.

이 필드와 함께 컴파일러는 하나를 생성합니다 접근 자 각 구성 요소에 대해. 이 액세스자는 구성 요소의 이름이 같은 메소드이며 값을 리턴합니다. 이것의 경우 Point 기록적으로, 생성 된 두 가지 방법은 다음과 같습니다.

public int x() {
    return this.x;
}

public int y() {
    return this.y;
}
 

이 구현이 응용 프로그램에 효과적이라면 아무것도 추가 할 필요가 없습니다. 그래도 자신의 액세스 방법을 정의 할 수 있습니다. 특정 필드의 방어 사본을 반환해야하는 경우에 유용 할 수 있습니다.

컴파일러가 생성 한 마지막 요소는 toString(), equals()  hashCode() 의 방법 Object 수업. 필요한 경우 이러한 메소드의 고유 한 대체를 정의 할 수 있습니다.

 

레코드에 추가 할 수없는 것

레코드에 추가 할 수없는 세 가지가 있습니다:

  1. 레코드에서 인스턴스 필드를 선언 할 수 없습니다. 구성 요소에 해당하지 않는 인스턴스 필드를 추가 할 수 없습니다.
  2. 필드 이니셜 라이저를 정의 할 수 없습니다.
  3. 인스턴스 이니셜 라이저를 추가 할 수 없습니다.

초기화 기와 정적 초기화 기로 정적 필드를 만들 수 있습니다.

 

정식 생성자로 레코드 구성

컴파일러는 또한 표준 생성자. 이 생성자는 레코드의 구성 요소를 인수로 가져 와서 해당 값을 레코드 클래스의 필드에 복사합니다.

이 기본 동작을 재정의해야하는 상황이 있습니다. 두 가지 사용 사례를 살펴 보겠습니다:

  1. 기록 상태를 확인해야합니다
  2. 가변 구성 요소의 방어 사본을 만들어야합니다.

 

소형 컨스트럭터 사용

두 개의 다른 구문을 사용하여 레코드의 표준 생성자를 재정의 할 수 있습니다. 컴팩트 한 생성자 또는 표준 생성자 자체를 사용할 수 있습니다.

다음 기록이 있다고 가정하십시오.

public record Range(int start, int end) {}
 

그 이름의 기록을 위해 end 더 큰 start. 컴팩트 생성자를 레코드에 작성하여 유효성 검사 규칙을 추가 할 수 있습니다.

public record Range(int start, int end) {

    public Range {
        if (end <= start) {
            throw new IllegalArgumentException("End cannot be lesser than start");
        }
    }
}
 

컴팩트 한 표준 생성자는 매개 변수 블록을 선언 할 필요가 없습니다.

이 구문을 선택하면 다음과 같이 레코드 필드를 직접 할당 할 수 없습니다 this.start = start -컴파일러가 추가 한 코드로 수행됩니다. 그러나 컴파일러 생성 코드가 이러한 새로운 값을 필드에 할당하기 때문에 매개 변수에 새 값을 할당 할 수 있습니다.

public Range {
    // set negative start and end to 0
    // by reassigning the compact constructor's
    // implicit parameters
    if (start < 0)
        start = 0;
    if (end < 0)
        end = 0;
}
 

 

정식 생성자 사용

예를 들어 매개 변수를 재 할당하지 않기 때문에 비 소형 양식을 선호하는 경우 다음 예와 같이 표준 생성자를 직접 정의 할 수 있습니다.

public record Range(int start, int end) {

    public Range(int start, int end) {
        if (end <= start) {
            throw new IllegalArgumentException("End cannot be lesser than start");
        }
        if (start < 0) {
            this.start = 0;
        } else {
            this.start = start;
        }
        if (end > 100) {
            this.end = 10;
        } else {
            this.end = end;
        }
    }
}
 

이 경우 작성하는 생성자는 레코드의 필드에 값을 할당해야합니다.

레코드의 구성 요소를 변경할 수없는 경우 표준 생성자와 액세스 자 모두에서 방어 사본을 만드는 것이 좋습니다.

 

생성자 정의

이 생성자가 레코드의 표준 생성자를 호출하는 한 생성자를 레코드에 추가 할 수도 있습니다. 구문은 다른 생성자와 생성자를 호출하는 클래식 구문과 동일합니다. 모든 수업에 관해서는 this() 생성자의 첫 번째 진술이어야합니다.

다음을 살펴 보자 State 기록. 세 가지 구성 요소로 정의됩니다:

  1. 이 상태의 이름
  2. 이주의 수도의 이름
  3. 비어있을 수있는 도시 이름 목록.

이 기록의 외부에서 수정되지 않도록 도시 목록의 방어 사본을 저장해야합니다. 이는 매개 변수를 방어 사본에 재 할당하는 컴팩트 한 형식으로 표준 생성자를 재정의하여 수행 할 수 있습니다.

도시를 취하지 않는 생성자를 갖는 것이 응용 프로그램에 유용합니다. 이것은 주 이름과 수도 이름 만 사용하는 다른 생성자 일 수 있습니다. 이 두 번째 생성자는 표준 생성자를 호출해야합니다.

그런 다음 도시 목록을 전달하는 대신 도시를 vararg로 전달할 수 있습니다. 그렇게하려면 올바른 목록을 가진 표준 생성자를 호출해야하는 세 번째 생성자를 만들 수 있습니다.

public record State(String name, String capitalCity, List<String> cities) {

    public State {
        // List.copyOf returns an unmodifiable copy,
        // so the list assigned to `cities` can't change anymore
        cities = List.copyOf(cities);
    }

    public State(String name, String capitalCity) {
        this(name, capitalCity, List.of());
    }

    public State(String name, String capitalCity, String... cities) {
        this(name, capitalCity, List.of(cities));
    }

}
 

그 점에 유의하십시오 List.copyOf() 메소드는 인수로 얻는 콜렉션에서 널값을 허용하지 않습니다.

 

기록 상태 얻기

컴파일러가이를 수행하므로 레코드에 액세스자를 추가 할 필요가 없습니다. 레코드에는 구성 요소 당 하나의 액세스 방법이 있으며이 구성 요소의 이름이 있습니다.

그만큼 Point 이 자습서의 첫 번째 섹션에서 기록에는 두 가지 액세스 방법이 있습니다: x()  y() 해당 구성 요소의 값을 반환합니다.

그러나 자신의 액세스자를 정의해야하는 경우가 있습니다. 예를 들어 State 이전 섹션의 레코드는 수정 불가능한 방어 사본을 만들지 않았습니다 cities 건설 중 목록-발신자가 내부 상태를 변경할 수 없도록 액세스기에서이를 수행해야합니다. 다음 코드를 추가 할 수 있습니다 State 이 방어 사본을 반환하는 기록.

public List<String> cities() {
    return List.copyOf(cities);
}
 

 

직렬화 기록

레코드 클래스가 구현 된 경우 레코드를 직렬화하고 직렬화 해제 할 수 있습니다 Serializable. 그러나 제한이 있습니다.

  1. 기본 직렬화 프로세스를 대체하는 데 사용할 수있는 시스템은 레코드에 사용할 수 없습니다. 만들기 writeObject()  readObject() 방법은 효과가 없으며 구현 Externalizable.
  2. 레코드는 프록시 객체로 사용하여 다른 객체를 직렬화 할 수 있습니다. ᅡ readResolve() 메소드는 레코드를 반환 할 수 있습니다. 추가 writeReplace() 기록에서도 가능합니다.
  3. 기록 탈수 항상 정식 생성자에게 전화하십시오. 따라서이 생성자에 추가 할 수있는 모든 유효성 검사 규칙은 레코드를 역 직렬화 할 때 적용됩니다.

따라서 응용 프로그램에서 데이터 전송 객체를 작성하기 위해 레코드를 매우 잘 선택합니다.

 

실제 사용 사례에서 레코드 사용

레코드는 여러 상황에서 사용할 수있는 다목적 개념입니다.

첫 번째는 응용 프로그램의 객체 모델에 데이터를 전달하는 것입니다. 불변의 데이터 반송파 역할을하는 레코드를 사용할 수 있습니다.

로컬 레코드를 선언 할 수 있으므로 해당 레코드를 사용하여 코드의 가독성을 향상시킬 수도 있습니다.

다음과 같은 사용 사례를 고려해 봅시다. 레코드로 모델링 된 두 개의 엔티티가 있습니다: City  State.

public record City(String name, State state) {}
 
public record State(String name) {}
 

도시 목록이 있고 도시 수가 가장 많은 주를 계산해야한다고 가정하십시오. Stream API를 사용하여 먼저 각 도시 수를 가진 상태의 히스토그램을 작성할 수 있습니다. 이 히스토그램은 Map.

List<City> cities = List.of();

Map<State, Long> numberOfCitiesPerState =
    cities.stream()
          .collect(Collectors.groupingBy(
                   City::state, Collectors.counting()
          ));
 

이 히스토그램의 최대 값을 얻는 것은 다음과 같은 일반 코드입니다.

Map.Entry<State, Long> stateWithTheMostCities =
    numberOfCitiesPerState.entrySet().stream()
                          .max(Map.Entry.comparingByValue())
                          .orElseThrow();
 

이 마지막 코드는 기술적입니다. 비즈니스 의미를 갖지 않습니다. 사용하기 때문에 Map.Entry 히스토그램의 모든 요소를 모델링하는 인스턴스.

로컬 레코드를 사용하면이 상황이 크게 개선 될 수 있습니다. 다음 코드는 주와 주 수를 집계하는 새로운 레코드 클래스를 작성합니다. 인스턴스를 취하는 생성자가 있습니다 Map.Entry 키-값 쌍의 스트림을 레코드 스트림에 맵핑하는 매개 변수로서.

도시 수만큼 이러한 집계를 비교해야하므로 공장 방법을 추가하여이 비교기를 제공 할 수 있습니다. 코드는 다음과 같습니다.

record NumberOfCitiesPerState(State state, long numberOfCities) {

    public NumberOfCitiesPerState(Map.Entry<State, Long> entry) {
        this(entry.getKey(), entry.getValue());
    }

    public static Comparator<NumberOfCitiesPerState> comparingByNumberOfCities() {
        return Comparator.comparing(NumberOfCitiesPerState::numberOfCities);
    }
}

NumberOfCitiesPerState stateWithTheMostCities =
    numberOfCitiesPerState.entrySet().stream()
                          .map(NumberOfCitiesPerState::new)
                          .max(NumberOfCitiesPerState.comparingByNumberOfCities())
                          .orElseThrow();
 

코드는 이제 의미있는 방식으로 최대 값을 추출합니다. 코드를보다 읽기 쉽고 이해하기 쉽고 오류가 적고 장기적으로 유지 관리하기가 더 쉽습니다.

 

 

 

 

 

 

 

 

Expressions, Statements and Blocks

Expressions, Statements and Blocks 언어의 구문에 따라 구성되는 변수, 연산자 및 메소드 호출로 구성된 구성으로 단일 값으로 평가됩니다. int cadence = 0; anArray[0] = 100; System.out.println("Element 1 at index 0: " + a

tipoazul.tistory.com

 

 

 


 

 

※ 쿠팡 파트너스 활동을 통해 일정액의 수수료를 제공 받을 수 있습니다 

반응형

'∮explotación≒ 개발' 카테고리의 다른 글

java Interfaces  (0) 2023.03.04
Oracle 오라클 조인 [ ANSI JOIN, Oracle Join ]  (0) 2023.02.28
java Switch 에 대한 설명  (0) 2023.02.22
java if 함수란 무엇인가 알아보자  (0) 2023.02.21
java Expressions  (0) 2023.02.19

댓글