[Java][Effective Java] item 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

2022. 9. 29. 20:40JAVA/Effective Java

1. 적절하지 않는 public 클래스

class Point{
    public double x;
    public double y;
}

위와 같은 Point 클래스의 x,y 데이터 필드는 접근이 가능하기 때문에 다음과 같은 문제점이 있습니다.

  • 데이터 필드에 직접 접근이 가능하기 때문에 캡슐화의 이점을 갖지 못합니다
    • 외부에 클래스의 멤버를 노출시키지 않을 수 있습니다
  • API를 수정하지 않고는 내부 표현을 바꿀 수 없습니다
    • 예를 들어 Point 인스턴스의 필드 x를 직접 접근하는 클라이언트가 있다면 Point 클래스의 필드 x를 함부로 바꾸기가 어렵습니다. 필드 x를 변경한다면 그것을 참조하는 클라이언트도 변경되어야 합니다.
  • 불변식을 보장할 수 없으며, 외부에서 필드에 접근할 때 부수 작업을 수행할 수 없습니다
    • 불변식을 보장할 수 없기 때문에 클라이언트가 직접 데이터 필드의 값을 변경할 수 있습니다.
    • 부수작업을 수행할 수 없다는 의미는 필드를 사용하는 순간에 어떤 동작이 실행되도록 만들 수도 없다는 의미입니다. 예를 들어 외부에서 필드에 접근할 때 부수적인 작업(복사본 던지기, 파라미터 검증 등)을 할 수도 없습니다.

예를 들어 Point 클래스의 필드멤버를 private로 설정하고 getter/setter 메서드를 정의한다고 가정합니다. 이때 Point 클래스의 필드멤버 y는 1~5사이의 값으로 설정되야 한다고 가정합니다. 코드로 표현하면 다음과 같습니다.

class Point {
    private double x;
    private double y;
    
    ...

    public void setY(double y) {
        if(y < 1 || y > 5){
            throw new IllegalStateException("y는 1부터 5사이의 수를 갖습니다.");
        }
        this.y = y;
    }
}

만약 필드멤버를 public으로 설정하여 y의 값을 저장한다면 외부에서 필드를 접근할때 파라미터 검증같은 부수적인 작업을 수행할 수 없습니다.

 

2. 캡슐화를 적용한 클래스

기존 public 멤버 필드들의 접근자를 private로 변경하고 접근자 메서드(getter)를 적용하여 데이터를 캡슐화합니다.

class Point {
    private double x;
    private double y;

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

    public double getX() {
        return x;
    }

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

    public double getY() {
        return y;
    }

    public void setY(double y) {
        this.y = y;
    }
}
  • 패키지 바깥에서 접근할 수 있는 클래스라면 접근자를 제공함으로써 클래스 내부 표현 방식을 언제든 바꿀수 있는 유용성을 얻음
  • 만약 설정자 메서드(setter)를 정의하지 않는다면 불변식을 보장할 수 있습니다.
  • getter / setter 메서드에 부수 작업(파라미터 검증, 형식화)을 수행할 수 있습니다.

 

3. package-private 클래스 또는 private 중첩 클래스

다음은 private 중첩 클래스를 표현한 코드입니다.

class Outer {
    private static class Point{
        public double x;
        public double y;
    }

    public String name;

    public Point getPoint(){
        Point point = new Point();
        point.x = 5;
        point.y = 3;
        return point;
    }
}
  • Outer 클래스는 package-private 클래스로써 같은 패키지 내에서 접근이 가능합니다.
    • 클라이언트 코드가 클래스 내부 표현에 묶이기는 하나, 클라이언트도 어차피 이 클래스를 포함하는 패키지 안에서만 동작하는 코드일 뿐입니다.
    • 패키지 바깥 코드는 전혀 손대지 않고도 데이터 표현 방식을 변경할 수 있습니다.
  • Point 클래스는 private 중첩 클래스인데 수정 범위가 더 좁아져서 이 클래스를 포함하는 외부 클래스(Outer)까지로 제한됩니다.

 

4. 클래스의 public 필드가 불변인 경우

다음은 public 필드이지만 불변인 클래스 정의입니다.

public class Time {
    private static final int HOURS_PER_DAY    = 24;
    private static final int MINUTES_PER_HOUR = 60;

    public final int hour;
    public final int minute;

    public Time(int hour, int minute) {
        if(hour < 0 || hour >= HOURS_PER_DAY){
            throw new IllegalStateException("시간 : " + hour);
        }
        if(minute < 0 || minute >= MINUTES_PER_HOUR){
            throw new IllegalStateException("분 : " + minute);
        }
        this.hour = hour;
        this.minute = minute;
    }
}
  • public 클래스의 필드가 불변이라면 직접 노출 시 단점이 조금 줄어들지만 좋은 생각은 아닙니다.
    • API를 변경하지 않고는 내부 표현 방식을 변경할 수 없음
    • 필드를 읽을 때 부수 작업을 수행할 수 없음
    • 단, 값이 변하지 않게 불변식은 보장함

 

핵심 정리

public 클래스는 절대 가변 필드를 직접 노출해서는 안됩니다. 불변 필드라면 노출해도 덜 위험하지만 내부 표현을 쉽게 변경할 수 없습니다. 하지만 package-private 클래스나 private 중첩 클래스에서는 종종 (불변이든 가변이든) 필드를 노출하는 편이 나을 때도 있습니다.

 

References

source code : https://github.com/yonghwankim-dev/effective_java/tree/master/src/role16
effective java 3/E
[이펙티브 자바3판] 4장 클래스와 인터페이스