Language/Java

Java - 참조(reference) 객체가 아닌 값(value) 객체 사용하는 방법

JaeHoney 2022. 3. 5. 00:26

Reference Object vs Value Object

기본적으로 객체는 참조 객체(Reference Object)를 많이 사용합니다.

 

하지만 값 객체(Value Object)를 사용하면 해결할 수 있는 문제도 많고, 적합한 경우도 아주 많습니다.

 

그래서, 참조 객체와 값 객체를 사용하는 기준과 사용하는 방법에 대해 다루겠습니다.

 

선택 기준

가장 중요한 기준mutable(가변) vs immutable(불변) 입니다. 내부에 있는 값을 바꾸면서 사용해야 하고, 변경된 값을 여러 곳에서 사용해야 한다면 Reference Object를 사용하고 그렇지 않은 경우 Value Object입니다.

 

두 번째 기준객체의 고유성을 어떻게 판단할 지 입니다. 이해가 어려우실 것 같아 코드로 가져왔습니다.

class Point {
    int x;
    int y;

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

Point 클래스로 만든 객체 2개가 있습니다. 수학적으로 같은 점이지만, 비교하는 테스트 코드를 돌리면 테스트가 깨집니다. point는 값 객체가 아닌 참조 객체이고, 고유성이 참조(주소)에 있어, 값이 아닌 주소를 비교하기 때문입니다.

Point point1 = new Point(1, 2);
Point point2 = new Point(1, 2);
assertEquals(point1, point2); // Tests failed

같은 이유로 HashSet에서도 다른 객체로 인식하고, add 처리됩니다.

Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
set.add(new Point(1, 2));
assertEquals(set.size(), 1); // Test failed

 

만약 이런 결과를 원하지 않고, 참조(주소)가 아닌 값을 객체의 고유성을 판단하는 요소로 사용하고 싶다면, 값 객체(Value Object)를 사용해야 합니다.

 

코드 작성

참조 객체(Reference Object)의 경우 필드와 생성자 Getter, Setter로 구성됩니다. 자주 보는 Basic한 코드인 것 같습니다.

public class Point {

    private int x;
    private int y;

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

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }
    
}

 

반면, 값 객체(Value Object)는 우선 필드값이 변경되선 안되기 때문에 private final fields을 사용합니다. 그리고 getter를 사용해서 값에 접근하고, setter는 사용하지 않습니다.

 

equals와 hashCode 메서드도 반드시 작성해야 합니다. equals의 경우 작성하지 않으면 java core 라이브러리에 있는 equals를 사용하는데, 이는 참조(주소)를 기준으로 비교하기 때문입니다. 마찬가지로 HashSet 등에 삽입할 때 사용할 Hash의 기준이 참조가 아니라 값이 되어야 하기때문에 hashCode도 정의해야 합니다.

public class Point {

    private final int x;
    private final int y;

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

    public int x() {
        return x;
    }
    
    public int y() {
        return y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

 

Java 14버전 이상부터 사용할 수 있는 레코드는 equals, hashCode 등 값 객체(value object)에 필요한 메서드가 포함되어 있습니다.

 

레코드를 사용하면 훨씬 간단하게 값 객체를 정의할 수 있습니다.

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

 

 

감사합니다.

 


 

참고

마틴 파울러 - ValueObject: https://martinfowler.com/bliki/ValueObject.html