JAVA

getter 언제, 어떻게 사용해야 하는가!!!

bang-dev 2025. 11. 6. 02:16

Getter를 지양하라는 말을 많이 한다.

사용을 지양해야 하는건 맞지만 왜 지양해야 되는지?? 생각은 깊게 해보지 않은 것 같아서 깊게 생각할 겸 정리할 겸 글을 작성해 보려고 한다.


많은 글에서 “getter를 쓰지 말라”는 조언이 나온다.
하지만 대부분 이유는 설명하지 않고, 금지 규칙만 남긴다.
정작 중요한 건 왜 그게 문제인지, 그리고 대신 어떻게 해야 하는지다.

 

1. getter를 지양하라는 진짜 이유

객체 지향 설계의 핵심 원칙 중 하나는 "캡슐화"이다.

객체 내부의 상태를 숨기고, 외부에서 직접 조작하거나 판단하지 못하도록 보호하는 개념이다.

 

public class Student {
    private final String name;
    private final int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

 

이 코드는 외부에서 get을 한다고 해서 값이 변경되지는 않는다.

getter을 사용해도 내부의 값을 변동시키지 못한다는 말이다.

 

if (student.getAge() >= 20) {
    lineupService.apply(student);
}

하지만 외부에 이런 코드가 있다면? 이 코드는 캡슐화를 깨뜨린다. 줄을 어떻게 세울지를 외부가 판단하고 있다.

 

학교를 예를 들면 선생님이 20살 이상인 사람들을 미리 모아 직접 줄을 세운다고 할 수 있다. 이런 판단을 학생 자신이 본인의 나이를 알고 있으니 본인이 판단하면 어떨까??

public boolean isAdult() {
    return age >= 20;
}

외부 호출
if (student.isAdult()) {
    lineupService.apply(student);
}

이렇게 되면 "저는 20살이니 여기에 줄슬게요" 이런 방식이 되는 것이고, 호출 하는 곳은 나이를 몰라도 알아서 줄이세워지게 된다.

 

“객체의 내부 상태를 꺼내서 외부에서 판단하지 말고, 객체에게 메시지를 보내라.”

라는 것을 명심하자.

 

2. getter가  항상 나쁜건 아니다

getter을 무조건 금지하자!!!! 가 절대 아니다.

어디에 쓰이냐가 제일 중요하다.

 

용도 설명 사용여부
비즈니스 로직 내부 상태 꺼내서 외부 판단 지양
데이터 전달(DTO, View 변환 등) 단순 조회, 출력용 허용

 

즉 객체의 책임을 외부로 넘기는건 허용하지 않고, 데이터 전달을 위한 getter는 허용을 한다는 것이다.

 

하지만 리스트를 `return lottos;` 와 같이 바로 넘겨준다면??

극단적으로 말하면 멤버면수도 그냥 public으로 필드를 열어두지 왜 private로 막냐 이런 말까지 할 수 있을 것 같다.

그래서 우리는 반환할 때 불변으로 넘겨주어야 한다.

 

3. List.copyOf()는 불변일까?

나도 그랬고, 이게 헷갈려서 이번에 정리를 완벽하게 하고 싶었다.

많은 개발자들이 List.copyOf()를 완전한 불변 복사로 오해한다. 하지만 실제로는 얕은 복사(shallow copy)다.

List<Student> list = new ArrayList<>();
list.add(new Student("홍길동"));

List<Student> copy = List.copyOf(list);

list.get(0).setName("이순신"); 
System.out.println(copy.get(0).getName());

이러면 겉으로는 수정이 안되는 것 처럼 보이지만 내부는 같은 참조를 지니고 있기에 원본이 변경되면 복사본도 변경된다는 치명적인 단점이 있다.

public List<Name> getStudentNames() {
    return Collections.unmodifiableList(studentNames);
}

public List<Name> getStudentNames() {
    return List.copyOf(studentNames);
}

이것도 Unmodifiable이 있으니 불변이겠지?? 복사 했으니까 참조는 다르겠지??하고 그냥 사용하면 안된다. 

물론 불변 리스트는 맞으나, 리스트 안의 객체 참조는 그대로 유지되기 때문에 진짜 불변을 원한다면 깊은 복사를 사용해야 한다.

public List<Name> getStudentNames() {
       return studentNames.stream()
               .map(name -> new Name(name.getName()))
               .toList();
}

로 리스트 안의 객체도 새로 생성을 한다면 참조가 다르기 때문에 완전히 다른 리스트를 생성했다고 볼 수 있다.

즉, 출력 결과는 같으나 내부 객체의 참조는 다르다는 말이다.

 

 

 

4. Map.copyOf()도 동일하게 얕은 복사이다.

저번에 코드를 짰을때 위와 당연하게 copyOf()를 사용했더니 완전한 불변이 아니라는 피드백도 받았다.

Map<String, Student> deepCopy = map.entrySet().stream()
    .collect(Collectors.toUnmodifiableMap(
        Map.Entry::getKey,
        e -> new Student(e.getValue().getName())
    ));

 

이런 식으로 위와 같이 새롭게 생성을 한다면 완전히 다른 Map을 생성했으므로 참조가 다르게 된다!


정리해 보면..

 

getter를 없애는 게 목적이 아니다. 객체의 책임을 외부로 빼내지 않는 것이 진짜 목적이다.

 

즉, 외부가 객체의 행동 책임을 빼앗는 순간, 그건 나쁜 설계다.

 

그리고 List.copyOf()나 Map.copyOf() 같은 불변 컬렉션도 얕은 복사라는 사실을 잊지 말자.
내부 요소가 지금은 불변이더라도, 미래를 생각한다면 깊은 복사로 방어적 캡슐화를 해두는 게 진짜 안전한 선택이다!!!!!

 


정리하면서 그냥 생각 없이 불변해야지~ 하면서 사용했던 게 머리에 확실히 정리가 된 것 같다.

 

외부로 값이 나간다면 깊은 복사로 전달해주자!!!!!!!!!!

 

 

참고.

https://velog.io/@backfox/getter-쓰지-말라고만-하고-가버리면-어떡해요

https://zzang9ha.tistory.com/372

 

getter 쓰지 말라고만 하고 가버리면 어떡해요

설명 좀 해주고 가요

velog.io