학습 자료

캡슐화 — private 을 왜 붙이나


Article

11강 마지막에 남긴 구멍이다.

Book b = new Book("자바", 20000);
b.price = -50000;

b.print();
콘솔
  • 자바 / -50000

가격이 마이너스인 책이 생겼다. 아무도 막지 않았다.

생성자에서 아무리 값을 잘 검사해봐야 소용없다. 만든 뒤에 밖에서 마음대로 바꿀 수 있다면 그 검사는 한순간만 유효하다.

문을 잠근다

필드 앞에 private 을 붙인다.

class Book {
    private String title;
    private int price;
}
b.price = -50000;
터미널
  • error: price has private access in Book
  • b.price = -50000;
  • ^

컴파일러가 막았다. private"이 클래스 안에서만 쓸 수 있다" 는 뜻이다.

그런데 이제 밖에서 가격을 읽지도 못한다. 잠그기만 하면 쓸모가 없다. 문을 만들어줘야 한다.

getter 와 setter

값을 꺼내주는 메서드를 getter, 넣어주는 메서드를 setter 라고 부른다. 그냥 관례적인 이름이지 자바 문법은 아니다.

class Book {
    private String title;
    private int price;

    public Book(String title, int price) {
        this.title = title;
        setPrice(price);
    }

    public String getTitle() {
        return title;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        if (price < 0) {
            throw new IllegalArgumentException("가격은 음수일 수 없다: " + price);
        }
        this.price = price;
    }
}

이제 가격은 오직 setPrice 를 통해서만 바뀐다. 문이 하나뿐이니 그 문에 검사대를 놓으면 된다.

Book b = new Book("자바", -50000);
터미널
  • Exception in thread "main" java.lang.IllegalArgumentException:
  • 가격은 음수일 수 없다: -50000

생성자가 setPrice 를 부르고 있어서, 만들 때도 검사를 통과해야 한다.

throw 는 "문제가 생겼다"고 알리며 프로그램을 중단시키는 명령이다. 18강에서 제대로 다룬다. 지금은 잘못된 값이 조용히 들어가는 것보다 시끄럽게 죽는 게 낫다는 것만 챙겨두자.

필드를 열어둘 때 vs 잠글 때

public 필드

b.price = -50000

  • 누구나 언제든 바꾼다
  • 검사할 자리가 없다
  • 잘못된 값이 조용히 들어간다
  • 나중에 필드 이름을 못 바꾼다

private 필드 + 메서드

b.setPrice(-50000)

  • 들어오는 문이 하나뿐이다
  • 그 문에 검사대를 놓는다
  • 잘못된 값은 그 자리에서 막힌다
  • 안쪽은 마음대로 바꿔도 밖이 안 깨진다

2강의 public

2강에서 public static void main 의 다섯 단어 중 첫 번째를 미뤄뒀다. 이제 답할 수 있다.

publicprivate 의 반대다. 누구나 부를 수 있다는 뜻이다.

main 은 우리 코드 밖에 있는 JVM 이 부른다. private 이면 JVM 이 부를 수 없다. 그래서 public 이어야 한다.

자바에는 이런 표시가 네 개 있다. 이걸 접근 제어자라고 부른다.

접근 제어자 — 넓은 순서대로
표시누가 쓸 수 있나주로 쓰는 곳
public누구나메서드, 클래스
protected같은 묶음 + 자식 클래스14강에서 다시 본다
(안 적음)같은 묶음 안에서만묶음은 21강에서
private이 클래스 안에서만필드

아무것도 안 적은 상태도 하나의 선택지다. 이걸 default 접근이라고 부르는데, 7강의 switch 에 나온 default 키워드와는 아무 관계가 없다.

외울 것은 두 줄이다. 필드는 private, 메서드는 public. 나머지는 필요해질 때 배운다.

감추면 나중에 바꿀 수 있다

캡슐화의 진짜 이득은 검사대가 아니다. 안쪽을 마음대로 뜯어고칠 수 있다는 것이다.

가격을 원 단위 int 로 저장하다가, 나중에 세금 계산 때문에 다른 방식으로 바꿔야 한다고 하자.

필드가 public 이었다면 b.price 를 쓰는 모든 코드가 깨진다. 그 코드가 몇 군데인지 알 수도 없다.

private 이었다면 getPrice() 안쪽만 고치면 된다. 밖에서는 getPrice() 를 부른다는 사실만 알 뿐, 안이 어떻게 생겼는지 모른다.

public int getPrice() {
    return basePrice + tax;    // 안쪽이 바뀌어도
}                              // 밖은 그대로 getPrice() 를 부른다

밖에 드러낸 것과 안에 감춘 것을 나누는 일. 이게 캡슐화다.

이 생각은 15강 인터페이스에서 한 번 더 크게 확장된다. 거기서는 클래스 통째로 감춘다.

record — getter 를 손으로 안 쓴다

값을 담아 나르기만 하는 클래스는 코드가 뻔하다. 필드, 생성자, getter, 그리고 나중에 배울 equals 까지.

자바 16 부터는 한 줄로 된다.

record Point(int x, int y) { }
Point p = new Point(1, 2);

System.out.println(p);
System.out.println(p.x());
System.out.println(p.equals(new Point(1, 2)));
콘솔
  • Point[x=1, y=2]
  • 1
  • true

세 가지가 눈에 띈다.

첫째, 출력이 Point@681a9515 가 아니라 내용이다. 둘째, 값을 꺼내는 메서드가 getX() 가 아니라 x() 다. 셋째, equalstrue 다.

11강에서 Book 두 권을 비교했을 때는 false 였다. record 는 다르다. 컴파일러가 equals 를 대신 만들어주는데, 내용을 비교하도록 만들어준다.

record 의 필드는 전부 private 이고 한 번 정해지면 바뀌지 않는다. 6강의 String 처럼 불변이다. 그래서 setter 가 없다.

검사대가 필요하면 이렇게 붙인다.

record Book(String title, int price) {
    Book {
        if (price < 0) throw new IllegalArgumentException("음수 불가");
    }
}

괄호도 매개변수도 없는 이상한 생성자다. 값을 필드에 넣기 직전에 끼어드는 자리라 이렇게 생겼다.

정리하면

필드는 잠그고, 필요한 문만 낸다. 그래야 안쪽을 바꿀 자유가 생긴다.

다시 짚어보기
  1. 01

    필드는 private

    밖에서 직접 못 바꾼다. 컴파일 에러로 막힌다.

  2. 02

    문은 메서드로 낸다

    들어오는 문이 하나면 그 문에 검사대를 놓을 수 있다.

    public void setPrice(int price) {
        if (price < 0) throw new IllegalArgumentException("...");
        this.price = price;
    }
  3. 03

    필요한 문만 낸다

    getter/setter 를 전부 만들면 필드를 public 으로 여는 것과 다르지 않다.

  4. 04

    public 은 누구나 부를 수 있다는 뜻

    main 이 public 인 이유. 우리 코드 밖의 JVM 이 불러야 하니까.

  5. 05

    값만 나르는 불변 클래스는 record

    생성자·접근자·equals·toString 을 컴파일러가 대신 써준다.

    record Point(int x, int y) { }

main 의 다섯 단어 중 이제 static 만 남았다. void 는 10강에서, String[] args 는 9강에서, public 은 방금 답했다.

다음 강의가 static 이다. 그리고 이 단어를 이해하면 두 가지가 한꺼번에 풀린다. System.outnew 없이 쓸 수 있었는지, 그리고 10강에서 만난 non-static method cannot be referenced from a static context 가 무슨 소리였는지.

캡슐화 — private 을 왜 붙이나 — 디코드랩(DCODELAB)