-
Java Records공부/자바 2023. 7. 31. 00:04728x90레코드는 변경 불가능한 데이터를 투명하게 보유하는 클래스로 Java 언어를 강화한다. 레코드는 명목적인 튜플로 생각할 수 있다.
목표
간단한 값들의 집합을 표현하는 객체 지향적인 구조를 설계한다.
개발자가 변경 불가능한 데이터 모델링에 집중할 수 있도록 도와준다. 확장가능한 동작보다는 데이터 중심의 메서드를 자동으로 구현한다.
equal와 접근자 같은 데이터 기반 메서드를 자동으로 구현한다.
명목적 유형과 이전의 Java 원칙을 보존한다.동기
데이터 운반자 클래스를 올바르게 작성하려면 생성자, 접근자, equals, hashCod, toString 등 많은 저가가치, 반복적이고 오류가 발생하기 쉬운 코드로 작성하게 된다.
class Point { private final int x; private final int y; Point(int x, int y) { this.x = x; this.y = y; } int x() { return x; } int y() { return y; } public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point other = (Point) o; return other.x == x && other.y == y; } public int hashCode() { return Objects.hash(x, y); } public String toString() { return String.format("Point[x=%d, y=%d]", x, y); } }
때로는 equals와 같은 메서드를 생략하여 예상치 못한 동작이나 디버깅이 어려운 상황을 초래한다. 코드의 가독성과 유지보수성이 저하될 수 있다. IDE들은 대부분의 코드 작성을 도와 주지만 Java 코드를 작성하는 것은 더 쉽고 읽기 쉽고 올바르게 확인 가능해야 한다. 이를 위해 데이터가 기본적으로 불변이고 데이터를 생성하고 소비하는 메소드의 관용적인 구현을 제공하는 데이터 캐리어 클래스를 쉽고 간결하게 선언해야 한다.
설명
레코드 클래스는 Java 언어에서 새로운 종류의 클래스다. 일반적인 클래스보다 적은 절차로 단순한 데이터 집합을 모델링 하는데 도움이 된다.
레코드 클래스의 선언은 주로 상태 선언으로 이루어지며, 레코드 클래스는 해당 상태와 일치하는 API를 사용한다. 이는 레코드 클래스가 일반적으로 클래스가 즐기는 자유인 클래스의 API를 내부 표현과 분리할 수 있는 능력을 포기한다는 것이다. 그러나 그 대신 레코드 클래스 선언이 상당히 간결해진다.
보다 정확하게 말하면, 레코드 클래스 선언은 이름, 선택적인 매개변수 타입, 헤더, 그리고 바디로 구성된다. 헤더는 클래스의 구성요소를 나열하는데, 이 구성 요소들은 레코드 클래스의 상태를 이루는 변수들을 의미한다.record Point(int x, int y) { }
레코드 클래스는 자신의 데이터를 투명하게 전달하는 목적을 가지므로, 자동으로 많은 표준 멤버들을 획득한다.
- 헤더에 나열된 각 구성요소마다 다음과 같은 두 가지 멤버가 있다: 해당 구성 요소와 동일한 이름과 반환 유형을 가진 public 접근자 메서드, 그리고 private final 필드이다.
- 헤더와 동일한 시그니처를 가진 규범적인 생성자도 있으며, 이 생성자는 각 비공개 필드를 new 표현식으로부터 해당 인수에 할당한다.
- equal 및 hashCode 메서드를 포함하여 두 레코드 값이 동일한 유형이며, 동일한 구성 요소 값을 포함하는 경우 동등하다고 보장한다.
- 또한 toString 메서드가 있으며, 이 메서드는 모든 레코드 구성요소와 그 이름을 포함하는 문자열 표현을 반환한다.
다시 말해, 레코드 클래스의 헤더는 해당 상태를 설명하는데 유형과 이름을 말한다. 그리고 이 API는 해당 상태 설명에서 기계적이고 완전하게 유도된다.
API에는 생성, 멤버 접근, 동등성 및 표시를 위한 프로토콜이 포함된다.레코드 클래스의 생성자
레코드 클래스의 생산자 규칙은 일반 클래스와 다르다. 생성자 선언이 없는 레코드 클래스는 자동으로 생성자를 재공받는다. 이 생성자는 레코드를 인스턴스화 한 new 표현식의 인수를 모든 private 필드에 할당한다.
record Point(int x, int y) { }
record Point(int x, int y) { // Implicitly declared fields private final int x; private final int y; // Other implicit declarations elided ... // Implicitly declared canonical constructor Point(int x, int y) { this.x = x; this.y = y; } }
위와 같이 컴파일 된다.
규범적인 생성자는 레코드 헤더와 일치하는 매개변수 목록과 함께 명시적으로 선언될 수 있다. 또한 더 간결하게 선언될 수 있다. 이 경우, 매개변수는 암시적으로 선언되고, 레코드 구성요소에 해당하는 private 필드를 body에서 할당할 수 없지만, 생성자 끝에서 자동으로 해당 형식 매개변수(this.x=x;)에 할당된다.
암시적 형식 매개변수를 검증하는 간결한 규범적 생성자의 예시이다.record Range(int lo, int hi) { Range { if (lo > hi) // referring here to the implicit constructor parameters throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); } }
형식 매개변수를 정규화하는 간결한 규범적 생성자의 예시이다.
record Rational(int num, int denom) { Rational { int gcd = gcd(num, denom); num /= gcd; denom /= gcd; } }
전통적인 생성자 형태와 동일하다.
record Rational(int num, int denom) { Rational(int num, int demon) { // Normalization int gcd = gcd(num, denom); num /= gcd; denom /= gcd; // Initialization this.num = num; this.denom = denom; } }
묵시적으로 선언된 생성자와 메서드가 있는 레코드 클래스는 중요하고 직관적인 의미적 특성을 만족시킨다.
모든 레코드 클래스에 대해 암묵적으로 선언된 equals 메서드는 반사성을 가지며, 부동 소수점 구성 요소를 가진 레코드 클래스에 대해 hashCode와 일관성을 유지하도록 구현된다.레코드 클래스 규칙
- 레코드 클래스 선언에는 extends 절이 없다. 일반 클래스는 Objectf를 확장할 수 있지만, 레코드는 어떤 클래스도 명시적으로 확장할 수 없다.
- 레코드 클래스는 암묵적으로 final이며, 추상 클래스가 될 수 없다. 다른 클래스에서 확장할 수 없음을 강조한다.
- 레코드 구성요소에서 파생된 필드들은 모두 final이다. 기본적인 불편성 정책을 나타낸다.
- 레코드 클래스는 인스턴스 필드를 명시적으로 선언할 수 없고, 인스턴스 초기화를 포함할 수 없다. 레코드 헤더만으로 레코드 값의 상태를 정의하도록 보장해준다.
- 자동으로 유도되는 멤버의 명시적 선언은 해당 명시적 선언에 있는 어노테이션을 무시하고, 자동으로 유도된 멤버의 타입과 정확히 일치해야 한다.
- 레코드 클래스는 네이티브 메서드를 선언할 수 없다. 선언할 경우 레코드 클래스의 동작은 외부 상태에 따라 달라져 마이그레이션에 적합한 후보가 아니다.
위에서 언급한 제약 사항 이외에는 레코드 클래스는 일반적인 클래스와 동일하게 작동한다. - 레코드 클래스의 인스턴스는 new 표현식을 사용하여 생성된다.
- 최상위 또는 중첩으로 선언할 수 있으며, 제네릭하게 선언할 수 있다.
- 정적 메서드, 필드, 초기화 블록을 선언할 수 있다.
- 인스턴스 메서드를 선언할 수 있다.
- 인터페이스를 구현할 수 있다. 부모 클래스로는 지성할 수 없다.
- 중첩된 타입, 중첩된 레코드 클래스를 포함하여 선언할 수 있다.
- 클래스와 헤더의 구성 요소는 어노테이션을 달 수 있다.
- 직렬화와 역직렬화가 가능하다.
로컬 레코드 클래스
레코드 클래스를 생성하고 사용하는 프로그램은 많은 중간값을 다루고 있다. 이러한 중간 값들은 자체적으로 간단한 변수들의 그룹일 수 있다.
레코드 클래스를 사용하면 스트림 연산의 가독성이 향상된다.List<Merchant> findTopMerchants(List<Merchant> merchants, int month) { // Local record record MerchantSales(Merchant merchant, double sales) {} return merchants.stream() .map(merchant -> new MerchantSales(merchant, computeSales(merchant, month))) .sorted((m1, m2) -> Double.compare(m2.sales(), m1.sales())) .map(MerchantSales::merchant) .collect(toList()); }
로컬 레코드 클래스는 중첩된 레코드 클래스의 특수한 경우이다. 로컬 레코드 클래스가 암묵적으로 정적인 것은 로컬 클래스와 대조적이고, 로컬 클래스는 정적으로 선언되지 않고, 항상 주변 메서드의 변수에 접근할 수 있다.
로컬 열거형 클래스와 로컬 인터페이스
로컬 열거형 클래스와 로컬 인터페이스의 추가는 암시적으로 정적인 다른 종류의 로컬 선언을 추가할 수 있다. 중첩된 열거형 클래스와 중첩된 인터페이스는 이미 암시적으로 정적이므로, 일관성을 유지하기 위해 로컬 열거형 클래스와 로컬 인터페이스를 정의한다.
내부 클래스의 정적 멤버들
내부 클래스는 내부 레코드 클래스가 암묵적으로 정적이기 때문에 레코드 클래스 멤버를 선언할 수 없다.
이러한 제한을 완화하여 내부 클래스가 명시적으로나 암묵적으로 정적인 멤버를 선언할 수 있도록 허용한다.예제
public class FruitsRecord { public static void main(String[] args) { Fruits peach = new Fruits("복숭아",2000); //객체 정의 System.out.println("이름: "+peach.name()+"\n가격: "+peach.price()); //get System.out.println(peach.toString()); //객체 정보 } } record Fruits(String name, int price){ Fruits{ if(price<=0){ //레코드 제한 throw new IllegalArgumentException("Price is not!!"); } } }
public class FruitsShop { public static void main(String[] args) { List<Fruits> fruits = new ArrayList<>(); fruits.add(new Fruits("복숭아",2000)); fruits.add(new Fruits("사과",1500)); fruits.add(new Fruits("수박",10000)); fruits.add(new Fruits("포도",3000)); System.out.println(sortFruits(fruits)); } static List<Fruits> sortFruits(List<Fruits> fruits){ return fruits.stream() .sorted((m1,m2)->Integer.compare(m1.price(),m2.price())) .collect(Collectors.toList()); } }
728x90반응형'공부 > 자바' 카테고리의 다른 글
flyway (0) 2023.08.27 자바 instanceof (0) 2023.07.29 외부함수와 메모리 API (0) 2023.07.29 switch를 위한 패턴 매칭 (0) 2023.07.29 17 preview version 안되는 오류 (0) 2023.07.29