자바에서 클래스와 객체를 사용하다 보면 public, private 같은 키워드를 자주 만나게 됩니다. 처음에는 그냥 외워야 하는 문법처럼 보이지만, 핵심은 단순합니다.

접근 제어자는 어디까지 보여주고, 어디부터 숨길지 정하는 문법입니다.

객체 안의 모든 값을 아무 곳에서나 바꿀 수 있다면 편해 보일 수 있습니다. 하지만 실제로는 객체가 지켜야 할 규칙이 쉽게 깨질 수 있습니다.

앞에서 클래스와 객체가 아직 헷갈린다면 자바 클래스와 데이터 정리 - 객체로 묶어 생각하기를 먼저 보면 더 이해하기 쉽습니다.


접근 제어자가 필요한 이유

사육장 온도 조절기를 코드로 만든다고 생각해보겠습니다.

온도 조절기는 현재 온도를 가지고 있고, 온도를 올리거나 내리는 기능을 제공합니다. 그런데 외부에서 현재 온도를 마음대로 바꿀 수 있다면 문제가 생길 수 있습니다.

public class Thermostat {
    int temperature;
}

이 상태에서는 다른 코드에서 아래처럼 직접 값을 바꿀 수 있습니다.

Thermostat thermostat = new Thermostat();
thermostat.temperature = 80;

온도 조절기 입장에서는 80도 같은 값이 들어오면 안 됩니다. 하지만 필드가 외부에 열려 있으면 객체가 스스로 값을 지킬 수 없습니다.

이럴 때 접근 제어자를 사용합니다.


private으로 필드 숨기기

가장 먼저 많이 사용하는 접근 제어자는 private입니다.

private은 같은 클래스 안에서만 접근할 수 있게 만듭니다. 외부에서는 직접 읽거나 바꿀 수 없습니다.

public class Thermostat {
    private int temperature;

    public Thermostat(int temperature) {
        this.temperature = temperature;
    }
}

이제 외부에서 아래 코드는 사용할 수 없습니다.

Thermostat thermostat = new Thermostat(28);

// thermostat.temperature = 80; // 컴파일 오류

temperatureThermostat 내부에서만 직접 다룰 수 있습니다. 외부에서 온도를 바꾸려면 클래스가 제공하는 메서드를 사용해야 합니다.


public 메서드로 필요한 기능만 열기

필드를 숨겼다면, 외부에서 사용할 기능은 메서드로 열어줍니다.

public class Thermostat {
    private int temperature;

    public Thermostat(int temperature) {
        this.temperature = temperature;
    }

    public void increase() {
        if (temperature >= 35) {
            return;
        }

        temperature++;
    }

    public void decrease() {
        if (temperature <= 20) {
            return;
        }

        temperature--;
    }

    public int getTemperature() {
        return temperature;
    }
}

외부 코드는 이제 필드를 직접 바꾸지 않습니다. 대신 increase(), decrease(), getTemperature()를 사용합니다.

public class ThermostatMain {
    public static void main(String[] args) {
        Thermostat thermostat = new Thermostat(28);

        thermostat.increase();
        thermostat.increase();

        System.out.println(thermostat.getTemperature());
    }
}

실행 결과입니다.

30

이 방식의 장점은 객체가 자기 규칙을 지킬 수 있다는 점입니다. 온도는 20도보다 낮아지지 않고, 35도보다 높아지지 않습니다.


접근 제어자 4가지

자바에는 대표적으로 4가지 접근 범위가 있습니다.

접근 제어자접근 가능 범위특징
private같은 클래스 안가장 좁은 범위
default같은 패키지 안접근 제어자를 쓰지 않은 상태
protected같은 패키지 또는 상속 관계상속을 배울 때 더 중요해짐
public어디서든 가능가장 넓은 범위

좁은 범위부터 넓은 범위로 보면 다음 순서입니다.

private -> default -> protected -> public

처음에는 privatepublic부터 확실히 구분하면 충분합니다.

  • 외부에서 직접 건드리면 안 되는 값은 private
  • 외부에서 사용해야 하는 기능은 public

이 기준만 잡아도 객체를 훨씬 안전하게 만들 수 있습니다.


default는 같은 패키지에서만 열립니다

접근 제어자를 아무것도 쓰지 않으면 default 접근 범위가 됩니다. 다른 말로는 package-private이라고도 부릅니다.

class CareNote {
    String memo;
}

위 코드에서 CareNote 클래스와 memo 필드는 public도 아니고 private도 아닙니다. 이 경우 같은 패키지 안에서는 접근할 수 있지만, 다른 패키지에서는 접근할 수 없습니다.

같은 패키지 -> 접근 가능
다른 패키지 -> 접근 불가

패키지를 기준으로 내부에서만 사용할 도구를 만들 때 사용할 수 있습니다. 하지만 입문 단계에서는 의도를 분명히 하기 위해 publicprivate을 먼저 익히는 편이 좋습니다.


protected는 상속과 함께 이해하면 좋습니다

protected는 같은 패키지에서 접근할 수 있고, 다른 패키지라도 상속 관계라면 접근할 수 있습니다.

처음 보면 default보다 조금 넓고 public보다 좁은 범위라고 생각하면 됩니다.

protected
- 같은 패키지에서는 접근 가능
- 다른 패키지에서는 상속 관계일 때 접근 가능

다만 상속을 배우기 전에는 protected를 완전히 이해하기 어렵습니다. 그래서 지금은 “상속과 관련된 접근 제어자” 정도로 기억해도 괜찮습니다.


클래스에 붙는 접근 제어자

접근 제어자는 필드와 메서드에만 붙는 것이 아닙니다. 클래스에도 붙일 수 있습니다.

다만 일반적인 바깥 클래스에는 publicdefault만 사용할 수 있습니다.

public class PublicTool {
}

class PackageTool {
}

PublicTool은 다른 패키지에서도 사용할 수 있습니다. 반면 PackageTool은 같은 패키지 안에서만 사용할 수 있습니다.

또 하나 중요한 규칙이 있습니다. public 클래스의 이름은 파일 이름과 같아야 합니다.

PublicTool.java 파일 -> public class PublicTool

이 규칙이 맞지 않으면 컴파일 오류가 발생합니다.


캡슐화란 무엇일까?

접근 제어자를 배우면 자연스럽게 캡슐화라는 말을 만나게 됩니다.

캡슐화는 객체 내부의 데이터와 구현 방법을 숨기고, 외부에는 필요한 기능만 보여주는 방식입니다.

쉽게 말하면 이런 느낌입니다.

숨길 것
- 객체 내부 데이터
- 내부 계산 과정
- 외부에서 몰라도 되는 보조 기능

보여줄 것
- 객체를 사용하는 데 필요한 기능
- 안전하게 호출할 수 있는 메서드

온도 조절기를 사용하는 사람은 내부에서 온도 제한을 어떻게 검사하는지 몰라도 됩니다. 그냥 increase()decrease()를 호출하면 됩니다.

객체 내부 규칙은 객체가 책임지고 지키는 것이 좋습니다.


예시: 사료 급여 기록 클래스

사료 급여 기록을 관리하는 클래스를 만들어보겠습니다.

하루 급여량은 0보다 작을 수 없습니다. 또 너무 큰 값도 그대로 들어가면 안 됩니다.

public class FeedingRecord {
    private int gram;

    public FeedingRecord(int gram) {
        setGram(gram);
    }

    public void setGram(int gram) {
        if (gram < 0) {
            this.gram = 0;
            return;
        }

        if (gram > 300) {
            this.gram = 300;
            return;
        }

        this.gram = gram;
    }

    public int getGram() {
        return gram;
    }
}

외부에서는 gram 필드에 직접 접근할 수 없습니다. 반드시 setGram()을 거쳐야 합니다.

public class FeedingRecordMain {
    public static void main(String[] args) {
        FeedingRecord record = new FeedingRecord(120);

        record.setGram(500);

        System.out.println(record.getGram());
    }
}

실행 결과입니다.

300

500을 넣으려고 했지만, 클래스 내부 규칙에 따라 300까지만 저장됩니다. 이처럼 접근 제어자는 객체의 값을 안전하게 지키는 데 사용됩니다.


private 메서드는 언제 사용할까?

필드뿐 아니라 메서드도 private으로 만들 수 있습니다.

외부에서 직접 호출할 필요는 없지만, 클래스 내부에서 반복해서 사용하는 기능이라면 private 메서드로 분리할 수 있습니다.

public class FeedingSchedule {
    private int morningGram;
    private int eveningGram;

    public void setMorningGram(int gram) {
        morningGram = normalize(gram);
    }

    public void setEveningGram(int gram) {
        eveningGram = normalize(gram);
    }

    public int getDailyTotal() {
        return morningGram + eveningGram;
    }

    private int normalize(int gram) {
        if (gram < 0) {
            return 0;
        }

        if (gram > 200) {
            return 200;
        }

        return gram;
    }
}

normalize()는 급여량을 안전한 범위로 바꾸는 내부용 메서드입니다. 외부에서는 호출할 필요가 없으므로 private으로 숨깁니다.

이렇게 하면 외부에 보여지는 기능은 단순해지고, 내부 구현은 클래스 안에 숨길 수 있습니다.


실수하기 쉬운 부분

첫 번째는 필드를 무조건 public으로 만드는 것입니다.

public class UserPoint {
    public int point;
}

이렇게 하면 외부에서 점수를 마음대로 바꿀 수 있습니다.

UserPoint point = new UserPoint();
point.point = -1000;

점수가 음수가 되면 안 되는 규칙이 있다면, 필드는 private으로 숨기고 메서드로 조절하는 편이 안전합니다.

두 번째는 모든 메서드를 무조건 public으로 만드는 것입니다. 외부에서 사용할 필요가 없는 보조 메서드는 private으로 숨기는 편이 좋습니다.

외부에 열어둔 메서드는 다른 코드가 사용할 수 있는 약속이 됩니다. 그래서 꼭 필요한 기능만 열어두는 습관이 중요합니다.


정리

접근 제어자는 클래스, 필드, 메서드에 접근할 수 있는 범위를 정하는 문법입니다.

핵심은 단순합니다.

  • private은 같은 클래스 안에서만 접근할 수 있습니다.
  • default는 같은 패키지 안에서 접근할 수 있습니다.
  • protected는 같은 패키지와 상속 관계에서 접근할 수 있습니다.
  • public은 어디서든 접근할 수 있습니다.

처음에는 모든 것을 열어두기보다, 먼저 숨기고 필요한 기능만 열어둔다고 생각하면 좋습니다.

필드는 private
필요한 기능은 public 메서드
내부 보조 기능은 private 메서드

이 기준을 잡으면 객체가 자기 데이터를 스스로 지키는 구조를 만들 수 있습니다. 이것이 접근 제어자와 캡슐화를 함께 배우는 이유입니다.