Java 14 레코드 클래스

Java 14가 도입한 Records라는 새로운 클래스 생성 방법이 있습니다. 이 튜토리얼에서는 다음을 학습하게 됩니다:

  • Java Records가 필요한 이유
  • Records 생성 및 사용 방법
  • Records 클래스 오버라이딩 및 확장

추천 독서: Java 14 기능

Java Records가 필요한 이유?

Java의 일반적인 불만 중 하나는 그 번잡함입니다. 간단한 POJO 클래스를 생성하려면 다음과 같은 보일러플레이트 코드가 필요합니다.

  • Private 필드
  • Getter 및 Setter 메서드
  • 생성자
  • hashCode(), equals(), 그리고 toString() 메서드

이러한 번잡함은 KotlinProject Lombok에 대한 높은 관심의 이유 중 하나입니다.

사실, 이러한 일반적인 메서드를 매번 작성하는 불만으로 인해 Eclipse 및 IntelliJ IDEA와 같은 Java IDE에서 이를 생성하기 위한 단축키가 나왔습니다.

다음은 Eclipse IDE에서 클래스의 의식적인 메서드를 생성하는 옵션을 보여주는 스크린샷입니다.

Eclipse Shortcuts to Generate Ceremonial Methods

Java Records는 POJO 클래스를 만드는 간결한 구조를 제공하여 이러한 번잡함을 제거하기 위해 만들어졌습니다.

Java 레코드 만들기 방법

Java 레코드는 JEP 359에서 개발된 미리보기 기능입니다. 그래서 Java 프로젝트에서 Records를 생성하려면 두 가지가 필요합니다.

  1. JDK 14가 설치되어 있어야 합니다. IDE를 사용하는 경우 Java 14를 지원해야 합니다. Eclipse와 IntelliJ는 이미 Java 14를 지원하고 있으므로 우리는 이쪽에 좋습니다.
  2. 미리보기 기능 활성화: 기본적으로 미리보기 기능은 비활성화되어 있습니다. Eclipse에서 프로젝트 Java 컴파일러 설정에서 활성화할 수 있습니다.
Java 14 Enable Preview Feature In Eclipse

명령줄에서 --enable-preview -source 14 옵션을 사용하여 Java 14 미리보기 기능을 활성화할 수 있습니다.

직원 모델 클래스를 만들고 싶다고 가정해 보겠습니다. 다음 코드처럼 보일 것입니다.

package com.journaldev.java14;

import java.util.Map;

public class Employee {

	private int id;
	private String name;
	private long salary;
	private Map<String, String> addresses;

	public Employee(int id, String name, long salary, Map<String, String> addresses) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
		this.addresses = addresses;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public long getSalary() {
		return salary;
	}

	public Map<String, String> getAddresses() {
		return addresses;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((addresses == null) ? 0 : addresses.hashCode());
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + (int) (salary ^ (salary >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Employee other = (Employee) obj;
		if (addresses == null) {
			if (other.addresses != null)
				return false;
		} else if (!addresses.equals(other.addresses))
			return false;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (salary != other.salary)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + ", addresses=" + addresses + "]";
	}

}

휴, 이것은 자동 생성된 코드로 70+줄입니다. 이제 Employee Record 클래스를 만드는 방법을 살펴보겠습니다. 이 클래스는 기본적으로 동일한 기능을 제공합니다.

package com.journaldev.java14;

import java.util.Map;

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
}

와우, 이것보다 더 짧을 수는 없겠군요. 나는 이미 Record 클래스를 사랑합니다.

이제 javap 명령을 사용하여 Record가 컴파일될 때 무엇이 일어나는지 알아보겠습니다.

# javac --enable-preview -source 14 EmpRecord.java
Note: EmpRecord.java uses preview language features.
Note: Recompile with -Xlint:preview for details.

# javap EmpRecord      
Compiled from "EmpRecord.java"
public final class EmpRecord extends java.lang.Record {
  public EmpRecord(int, java.lang.String, long, java.util.Map<java.lang.String, java.lang.String>);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int id();
  public java.lang.String name();
  public long salary();
  public java.util.Map<java.lang.String, java.lang.String> addresses();
}
# 
Java Record Class Details

# javac enablepreview source 14 EmpRecord.java

# javap EmpRecord

If you want more internal details, run the javap command with -v option.

  1. A Record class is final, so we can’t extend it.
  2. # javap -v EmpRecord
  3. Record 클래스에 대한 중요한 포인트
  4. Record 클래스는 명시적으로 java.lang.Record 클래스를 확장합니다.
  5. A single constructor is created with all the fields specified in the record definition.
  6. 레코드 선언에서 지정한 모든 필드는 final입니다.
  7. 레코드 필드는 “얕은” 불변성을 가지며 타입에 따라 달라집니다. 예를 들어 주소 필드를 액세스하고 업데이트를 수행하여 해당 필드를 변경할 수 있습니다.

Record 클래스는 필드에 대한 accessor 메서드를 자동으로 제공합니다. 메서드 이름은 일반적인 게터 메서드와 달리 필드 이름과 동일합니다.

Record 클래스는 hashCode(), equals(), 및 toString() 구현도 제공합니다.

package com.journaldev.java14;

public class RecordTest {

	public static void main(String[] args) {
		
		EmpRecord empRecord1 = new EmpRecord(10, "Pankaj", 10000, null);
		EmpRecord empRecord2 = new EmpRecord(10, "Pankaj", 10000, null);

		Java 프로그램에서 레코드 사용하기
		System.out.println(empRecord1);
		
		EmpRecord 클래스를 사용하는 간단한 예제를 살펴보겠습니다.
		System.out.println("Name: "+empRecord1.name()); 
		System.out.println("ID: "+empRecord1.id());
		
		// toString()
		System.out.println(empRecord1.equals(empRecord2));
		
		// 필드 접근
		System.out.println(empRecord1 == empRecord2);		
	}
}

// equals()

EmpRecord[id=10, name=Pankaj, salary=10000, addresses=null]
Name: Pankaj
ID: 10
true
false

// hashCode()

출력:

Record 객체는 모델 클래스, 데이터 객체 등과 마찬가지로 작동합니다.

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
	
	public EmpRecord {
		if (id < 0)
			throw new IllegalArgumentException("employee id can't be negative");

		if (salary < 0)
			throw new IllegalArgumentException("employee salary can't be negative");
	}

}

레코드 생성자 확장하기

EmpRecord empRecord1 = new EmpRecord(-10, "Pankaj", 10000, null);

가끔은 생성자에서 일부 유효성 검사 또는 로깅을 수행하고 싶을 때도 있습니다. 예를 들어, 직원 ID와 급여는 음수일 수 없습니다. 기본 생성자는 이러한 유효성 검사를 수행하지 않습니다. 레코드 클래스에 간소화된 생성자를 생성할 수 있습니다. 이 생성자의 코드는 자동 생성된 생성자의 시작 부분에 배치됩니다.

Exception in thread "main" java.lang.IllegalArgumentException: employee id can't be negative
	at com.journaldev.java14.EmpRecord.<init>(EmpRecord.java:9)

다음과 같은 코드로 EmpRecord를 생성하면:

런타임 예외가 발생합니다:

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {

	public int getAddressCount() {
		if (this.addresses != null)
			return this.addresses().size();
		else
			return 0;
	}
}

레코드 클래스에 메서드를 가질 수 있습니까?

네, 레코드에 메서드를 생성할 수 있습니다.

하지만, 레코드는 데이터 운반자로 고안되었습니다. 레코드 클래스에 유틸리티 메서드를 가지는 것을 피해야 합니다. 예를 들어, 위의 메서드는 유틸리티 클래스에 생성할 수 있습니다.

만약 레코드 클래스에 메서드가 필요하다고 생각한다면, 정말 레코드 클래스가 필요한지 신중히 생각해보세요?

Source:
https://www.digitalocean.com/community/tutorials/java-records-class