레코드 클래스란 일반 클래스와 다르게 간단하게 데이터를 저장하고 사용할 수 있으며
헤더에 내용을 선언한다. 기본적으로 equals, hashCode, toString 메서드가 자동으로 생성되어 제공된다.
예를들어 이렇게 선언할 수 있다.
record Rectangle(double length, double width) { }```
간단한 이 문장은 다음 코드와 동일하다.
public final class Rectangle {
private final double length;
private final double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double length() { return this.length; }
double width() { return this.width; }
// Implementation of equals() and hashCode(), which specify
// that two record objects are equal if they
// are of the same type and contain equal field values.
public boolean equals...
public int hashCode...
// An implementation of toString() that returns a string
// representation of all the record class's fields,
// including their names.
public String toString() {...}
}
다음 예제에서는 레코드 클래스의 정식 생성자를 명시적으로 선언한다.
record Rectangle(double length, double width) {
public Rectangle(double length, double width) {
if (length <= 0 || width <= 0) {
throw new java.lang.IllegalArgumentException(
String.format("Invalid dimensions: %f, %f", length, width));
}
this.length = length;
this.width = width;
}
}
예를들어 다음 예제의 기본 생성자 선언은 이전 예제와 동일한 방식으로 길이와 넓이의 유효성을 검사한다.
record생성자가 private필드를 초기화 하는것보다 더 많은 행동을 하기 원할때는 생성자를 커스텀 할 수 있다. 이때 Class Constructor보다 더 간단하게 적을 수 있다.
public RecordCarsDto { // 매개변수를 받는 부분이 생략됨
if (Objects.isNull(values)) {
values = new ArrayList<>();
}
if (Objects.isNull(speed)) {
speed = 10;
}
// this.values = values; this.speed = speed; 와 같은 초기화 로직은 마지막에 자동으로 호출해줌.
}
출저 : https://velog.io/@leverest96/Compact-Constructor
record Rectangle(double length, double width) {
public Rectangle {
if (length <= 0 || width <= 0) {
throw new java.lang.IllegalArgumentException(
String.format("Invalid dimensions: %f, %f", length, width));
}
}
}
record Rectangle(double length, double width) {
// Public accessor method
public double length() {
System.out.println("Length is " + length);
return length;
}
}
레코드 클래스에서도 동일하게 static필드 static초기화 static메서드를 구현할 수 있다.
이들은 일반 클래스에서와 같이 동일하게 동작한다.
record Rectangle(double length, double width) {
// Static field
static double goldenRatio;
// Static initializer
static {
goldenRatio = (1 + Math.sqrt(5)) / 2;
}
// Static method
public static Rectangle createGoldenRectangle(double width) {
return new Rectangle(width, width * goldenRatio);
}
}
record Rectangle(double length, double width) {
// Field declarations must be static:
BiFunction<Double, Double, Double> diagonal;
// Instance initializers are not allowed in records:
{
diagonal = (x, y) -> Math.sqrt(x*x + y*y);
}
}
record Rectangle(double length, double width) {
// Nested record class
record RotationAngle(double angle) {
public RotationAngle {
angle = Math.toRadians(angle);
}
}
// Public instance method
public Rectangle getRotatedRectangleBoundingBox(double angle) {
RotationAngle ra = new RotationAngle(angle);
double x = Math.abs(length * Math.cos(ra.angle())) +
Math.abs(width * Math.sin(ra.angle()));
double y = Math.abs(length * Math.sin(ra.angle())) +
Math.abs(width * Math.cos(ra.angle()));
return new Rectangle(x, y);
}
}
레코드 클래스에서는 네이티브 메서드를 선언할 수 없다.
record Triangle<C extends Coordinate> (C top, C left, C right) { }
record Customer(...) implements Billable { }
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface GreaterThanZero { }
record Rectangle(
@GreaterThanZero double length,
@GreaterThanZero double width) { }
public final class Rectangle {
private final @GreaterThanZero double length;
private final @GreaterThanZero double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double length() { return this.length; }
double width() { return this.width; }
}
sealed classes는 간단하게 extends, impliments할 클래스를 지정하고 상속 혹은 구현을 허용하는 키워드 이다.
public sealed interface CarBrand permits Hyundai, Kia{}
public final class Hyundai implements CarBrand {}
public non-sealed class Kia implements CarBrand {}
레코드 클래스는 sealed classes와 잘 동작한다.
import java.time.*;
import java.util.*;
import java.util.stream.*;
record Merchant(String name) { }
record Sale(Merchant merchant, LocalDate date, double value) { }
public class MerchantExample {
List<Merchant> findTopMerchants(
List<Sale> sales, List<Merchant> merchants, int year, Month month) {
// Local record class
record MerchantSales(Merchant merchant, double sales) {}
return merchants.stream()
.map(merchant -> new MerchantSales(
merchant, this.computeSales(sales, merchant, year, month)))
.sorted((m1, m2) -> Double.compare(m2.sales(), m1.sales()))
.map(MerchantSales::merchant)
.collect(Collectors.toList());
}
double computeSales(List<Sale> sales, Merchant mt, int yr, Month mo) {
return sales.stream()
.filter(s -> s.merchant().name().equals(mt.name()) &&
s.date().getYear() == yr &&
s.date().getMonth() == mo)
.mapToDouble(s -> s.value())
.sum();
}
public static void main(String[] args) {
Merchant sneha = new Merchant("Sneha");
Merchant raj = new Merchant("Raj");
Merchant florence = new Merchant("Florence");
Merchant leo = new Merchant("Leo");
List<Merchant> merchantList = List.of(sneha, raj, florence, leo);
List<Sale> salesList = List.of(
new Sale(sneha, LocalDate.of(2020, Month.NOVEMBER, 13), 11034.20),
new Sale(raj, LocalDate.of(2020, Month.NOVEMBER, 20), 8234.23),
new Sale(florence, LocalDate.of(2020, Month.NOVEMBER, 19), 10003.67),
// ...
new Sale(leo, LocalDate.of(2020, Month.NOVEMBER, 4), 9645.34));
MerchantExample app = new MerchantExample();
List<Merchant> topMerchants =
app.findTopMerchants(salesList, merchantList, 2020, Month.NOVEMBER);
System.out.println("Top merchants: ");
topMerchants.stream().forEach(m -> System.out.println(m.name()));
}
}
Java SE 16 이전에는 내부 클래스에서 맴버가 상수인 경우에만 명시적 혹은 묵시적으로 static 멤버를 선언할 수 있었다.
public class ContactList {
record Contact(String name, String number) { }
public static void main(String[] args) {
class Task implements Runnable {
// Record class member, implicitly static,
// declared in an inner class
Contact c;
public Task(Contact contact) {
c = contact;
}
public void run() {
System.out.println(c.name + ", " + c.number);
}
}
List<Contact> contacts = List.of(
new Contact("Sneha", "555-1234"),
new Contact("Raj", "555-2345"));
contacts.stream()
.forEach(cont -> new Thread(new Task(cont)).start());
}
}
package com.myapp;
public class Record {
public String greeting;
public Record(String greeting) {
this.greeting = greeting;
}
}
다음 예제인 org.example.MyappPackageExample은 와일드카드를 사용하여 com.myapp.Record를 가져오지만 컴파일되지 않습니다.
package org.example;
import com.myapp.*;
public class MyappPackageExample {
public static void main(String[] args) {
Record r = new Record("Hello world!");
}
}
컴파일러는 다음과 유사한 오류 메시지를 생성합니다:
./org/example/MyappPackageExample.java:6: error: reference to Record is ambiguous
Record r = new Record("Hello world!");
^
both class com.myapp.Record in com.myapp and class java.lang.Record in java.lang match
./org/example/MyappPackageExample.java:6: error: reference to Record is ambiguous
Record r = new Record("Hello world!");
^
both class com.myapp.Record in com.myapp and class java.lang.Record in java.lang match
com.myapp 패키지의 Record와 java.lang 패키지의 Record가 모두 와일드카드로 가져 왔습니다. 결과적으로 두 클래스 모두 우선되지 않으므로 컴파일러는 간단한 이름 Record 사용 시 오류를 생성합니다.
이 예제를 컴파일할 수 있도록 하려면 import 문을 변경하여 Record의 완전한 패키지 경로를 가져오도록 해야합니다.
import com.myapp.Record;
참고: java.lang 패키지에 클래스를 도입하는 것은 드물지만 Enum(Java SE 5), Module(Java SE 9) 및 Record(Java SE 14)와 같이 때때로 필요합니다.