본문 바로가기
우아한테크코스 4기/레벨1

[Java] public final vs private final

by 나는후니 2022. 3. 2.

저는 불변하는 Value Object를 만들 때 field를 private final로 선언합니다.

불변 객체이기 때문에 필드를 변경할 수 없게 만들 뿐 아니라 외부에서의 직접 접근을 막기 때문에 안정성이 높다고 생각하기 때문입니다.

 

이때 자바를 공부하는 또 다른 사람이 만약 아래와 같은 이야기를 한다면 어떻게 답변을 할 수 있을까요?

불변임을 보장하기 위해서는 public final을 사용해도 충분하지 않나요?
오히려 getter를 만들지 말라는 중론에 더 부합하지 않을까요? 게다가 외부에서 변경하려 해도 컴파일 에러가 발생해서 불변도 보장되는데요?

무지성 토론의 시작

아래 코드를 보면 정말 private final보다 명료한 것 같기도 하면서 불변성도 잘 보장해주고 있습니다.

public class Member {
    public final String name;
    public final int age;

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

    public static void main(String[] args) {
        Member huni = new Member("huni", 26);
        huni.age += 1; // 컴파일 에러
        int age = huni.age;

    }
}

실제로 유명 개발 관련 사이트에서도 Using public final rather than private getters 라는 제목으로 토론이 오가기도 했습니다.

하지만 안통하죠.

우테코에서 자바 교육을 받는다면 이 사람들의 의견을 깨부수고 private final을 사용해야하는 이유를 설득시킬 수 있어야 합니다.

물론 평소 public final을 사용한다면 그 반대로 설득할 수 있어야 겠죠.


하지만 저는 private final - use getter 의 입장에서 저의 의견을 적어보려 합니다.

1. public final이 더 깔끔하다?

맞습니다. public final이 더 깔끔하죠. 한 세 줄 정도.
아래 코드를 보면 getter가 추가되어도 크게 코드가 더럽다는 생각은 들지 않습니다.

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

오히려 field를 가져온다라는 의미가 더더욱 명확해져 코드를 사용하는 시점에는 가독성이 더 높을 것 같습니다.

// 둘 중 뭐가 더 보기 좋나요? ㅋㅎ
a.stream.map(user -> user.name).collect(toList());
a.stream.map(User::getName).collect(toList());

2. getter는 sideEffect가 존재한다?

getter가 목적을 드러내지 않는다는 점에서 sideEffect가 존재할 수는 있습니다.
하지만 이는 목적에 맞게 코드를 작성하지 않았을 때 발생하는 issue라고 생각합니다.

 

더욱이 public final처럼 객체에 직접 접근하여 값을 가져오는 행위는 되려 캡슐화의 장점을 누리지 못합니다.
만약 이름을 가져올 때, 최씨 성을 가진 경우만 가져온다 라는 요구사항이 api에 숨어있다고 가정해봅시다.

 

private final 이면 먼저 값을 가져오는 메서드를 해당 객체의 역할로 두고 이를 캡슐화하여 사용할 것입니다.

public String getNameOnlyChoi() {
    if(!name.startsWith("Choi")) {
        return "No Name";   
    }
    return name;
}

그런데 public으로 열려있다면? 대충 실행되는 시점에 user.name 하면서 가져오겠죠..

3. field로 배열을 사용할 경우?

그렇다면 field로 배열을 사용할 경우 public final을 통해 직접 값을 가져온다고 가정해봅시다.

public class PublicArray {
    public final String[] names;

    public PublicArray(String[] names) {
        this.names = names;
    }

    public static void main(String[] args) {
        PublicArray publicArray = new PublicArray(new String[]{"a", "n", "c"});
        publicArray.names[0] = "q"; // 이건 우짤코?
    }
}

위 코드처럼 final로 선언했지만 그 내부 요소에 대한 불변성을 유지 못한다면 우짤코?

public class PublicArray {
    public final String[] names;
    private final int[] ages;

    public PublicArray(String[] names, int[] ages) {
        this.names = names;
        this.ages = ages;
    }

    // private 으로 닫고 getter를 사용하면 이렇게 복사본을 만들어 반환해줄 수 있음
    public int[] getAges() {
        return Arrays.copyOf(ages, ages.length);
    }
}

하지만 getter를 만들 경우 내부 필드에는 손상받을 걱정 없이 배열 field를 리턴할 수 있습니다!

4. 그러면 무조건 private final을 쓸까?

그렇다고해서 무조건 private final을 사용하는 것이 맞는가? 에 대해 묻는다면 아니라고 답할 수도 있습니다.
package-private 클래스나 중첩 클래스인 경우에는 데이터 필드를 노출하는게 큰 문제가 되지 않습니다.

public class Member {
    public final String name;
    public final int age;
    private final Address address;

    public Member(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public void printMemberAddress() {
        System.out.println(address.country + " " + address.city);
    }

    static class Address {
        public final String country;
        public final String city;

        public Address(String country, String city) {
            this.country = country;
            this.city = city;
        }
    }
}

위 코드처럼 패키지 내부에서만 사용할 수 있도록 세팅된 경우 거의 문제가 되지 않습니다. (물론 그래도 저는 private을 사용할 것입니다.)

정리

Effective Java 아이템 16에서도 변경될 때마다 API가 수정되어야 한다는 점, 필드를 읽을 때 부수작업이 불가하다는 점 을 이유로 public final의 사용을 권장하지 않고 있습니다.

여러가지 글을 읽고 공부해보고 자신만의 소신을 가지어 코드에 힘을 실는다면, 저는 그것이 정답이라고 생각합니다.

 

무작정 "이게 좋다더라"라는 생각보다는 "왜 이게 좋을까?"라는 생각을 할 수 있는 개발자가 되는 것이 중요한 것 같습니다.

 

ps. 자바는 자바처럼 씁시다. 반박시 파이썬 쓰세여 ㅋㅎㅋ