개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › Java › 클래스와 객체

접근 제어자와 캡슐화

private, protected, public은 어디까지 열어 주나

필드나 메서드 앞의 접근 제어자는 그 멤버를 어느 코드가 쓸 수 있는지를 정합니다. 컴파일러는 접근하는 코드가 어느 클래스, 어느 패키지에 있는지를 보고 허용할지 판단하며, 허용되지 않으면 컴파일 오류입니다. protected는 자식 클래스에게 열려 있지만, 다른 패키지의 자식에서는 조건이 하나 더 붙습니다.

실행으로 대조 · JDK 21에서 패키지를 나눈 소스로 스물네 가지 조합을 컴파일해 성공 여부와 오류 메시지를 대조

이 실습에 나오는 개념

접근 제어자
클래스, 필드, 메서드에 접근할 수 있는 범위를 정하는 키워드다. public은 어디서든(모듈을 쓰는 경우 다른 모듈에서는 그 패키지가 export되어 있어야 한다), protected는 같은 패키지와 하위 클래스에서, 아무것도 쓰지 않으면(package-private) 같은 패키지에서만, private은 선언된 최상위 클래스 안에서만(중첩 클래스 포함) 접근할 수 있다. 공개 범위를 최소로 잡는 것이 캡슐화의 기본이다.
캡슐화
객체의 상태(필드)와 그 상태를 다루는 메서드를 하나로 묶고, 내부 구현을 외부에서 직접 접근하지 못하게 숨기는 것이다. 필드를 private으로 두고 공개 메서드로만 값을 바꾸게 하면, 잘못된 값이 들어오는 것을 메서드에서 막을 수 있고 내부 구조를 바꿔도 외부 코드에 영향이 없다.
패키지
관련된 클래스들을 묶어 두는 이름 공간이다. 소스 파일 맨 위의 package bank; 한 줄이 그 파일의 클래스가 어느 패키지에 속하는지를 정하고, 보통 폴더 구조와 맞춘다. 다른 패키지의 클래스를 쓰려면 import를 하거나 bank.Account처럼 패키지 이름까지 적는다. 접근 제어자를 쓰지 않은 멤버는 같은 패키지 안에서만 쓸 수 있으므로, 패키지는 이름을 나누는 단위이면서 접근을 나누는 단위이기도 하다.
protected
멤버를 같은 패키지의 클래스와, 다른 패키지에 있더라도 자식 클래스에게 열어 주는 접근 제어자다. 접근 제어자를 쓰지 않은 것(같은 패키지까지만)보다 넓고 public보다 좁다. 다른 패키지의 자식 클래스는 물려받은 멤버를 자기 타입을 통해서만 쓸 수 있다. 곧 this나 자기 클래스 타입의 변수로는 되지만, 부모 타입 변수로 접근하면 컴파일 오류가 난다. 자식이 아닌 같은 패키지의 클래스도 쓸 수 있다는 점을 놓치기 쉽다.
상속
기존 클래스(상위 클래스)의 필드와 메서드를 물려받아 새 클래스(하위 클래스)를 정의하는 것이다. 공통 코드를 재사용하고 하위 클래스에서 필요한 부분만 추가하거나 재정의한다. 자바의 클래스는 단일 상속만 가능하며, 상위 클래스 변경이 하위 클래스 전체에 영향을 주므로 "is-a" 관계가 분명할 때만 쓰는 것이 좋다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. bank/Account.java의 선언: balance는 protected로 선언되어 있습니다. protected는 "같은 패키지 안에서, 그리고 다른 패키지에 있더라도 자식 클래스 안에서 쓸 수 있다"는 뜻입니다.
  2. app/SavingAccount.java의 read(Account other): SavingAccount 안의 코드가 other.balance를 읽으려 합니다. other의 타입은 부모인 Account입니다. SavingAccount는 Account의 자식이지만 다른 패키지인 app에 있습니다.
  3. 확인 1: 접근하는 코드가 Account 클래스 안에 있는가. 아닙니다. SavingAccount 클래스 안에 있습니다. private이었다면 여기서 끝이지만, 이 멤버는 private이 아니므로 다음을 확인합니다.
  4. 확인 2: 접근하는 코드가 Account와 같은 패키지에 있는가. 아닙니다. SavingAccount 클래스는 app 패키지에 있습니다. protected는 다른 패키지라도 자식 클래스에게는 열려 있으므로 다음을 확인합니다.
  5. 확인 3: 접근하는 코드가 Account의 자식 클래스 안에 있는가. 그렇습니다. SavingAccount는 Account를 상속했습니다. 다만 다른 패키지의 자식에게는 조건이 하나 더 있습니다.
  6. 확인 4: balance 앞에 적은 참조의 타입이 자기 클래스인 SavingAccount인가. 아닙니다. other는 부모 타입인 Account로 선언되어 있습니다. protected가 다른 패키지의 자식에게 열어 주는 것은 "자기 타입을 통한 접근"뿐이어서, 자식 클래스 안의 코드인데도 쓸 수 없습니다. 컴파일 오류입니다.

실행 결과

코드

// 파일: bank/Account.java
package bank;

public class Account {
    protected int balance = 1000;
}

// 파일: app/SavingAccount.java  (다른 패키지, Account의 자식)
package app;

import bank.Account;

public class SavingAccount extends Account {
    int read(Account other) {
        return other.balance;       // 컴파일 오류
    }
}

이 모형의 전제

스물네 가지 조합을 이 PC의 JDK 21에서 패키지를 bank와 app으로 나눈 소스 파일로 실제로 컴파일해, 성공 여부와 오류 메시지를 대조했습니다(실행 결과가 아니라 컴파일 결과를 보는 실습입니다). "판단한 이유" 칸과 확인 순서는 자바 언어 명세의 접근 규칙을 풀어 쓴 설명이며, 컴파일러가 실제로 이 순서로 검사한다는 뜻은 아닙니다. 필드 대신 메서드에 같은 접근 제어자를 붙여 컴파일했을 때도 스물네 조합의 성공과 오류가 똑같았습니다(오류 메시지의 글귀는 일부 다릅니다). 다루지 않은 것: 클래스 자체의 접근 제어자, 생성자, static 멤버, 중첩 클래스, 모듈(module-info). protected의 "자기 타입을 통해서만" 조건은 static이 아닌 멤버에 대한 것입니다. 오류 메시지는 javac의 영어 메시지 첫 줄입니다.

앞 실습: 인터페이스와 추상 클래스 · 다음 실습: 내부 클래스와 익명 클래스

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요