Post

[JAVA] 접근제어자

[JAVA] 접근제어자

접근 제어자

자바는 public, private 같은 접근 제어자(access modifier)를 제공한다.

접근 제어자를 사용하면 해당 클래스 외부에서 특정 필드나 메서드에 접근하는 것을 허용하거나 제한할 수 있다.

접근 제어자를 왜 쓰는지 알아보자.

내가 은행 계좌 시스템을 만들었다.

입금, 출금, 현재 잔액 표시 기능이 있는 단순한 프로그램이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class BankAccount {
    public int balance;

    public BankAccount(int initialBalance) {
        if (initialBalance < 0) {
            System.out.println("초기 잔액은 0 이상이어야 합니다.");
            balance = 0;
        } else {
            balance = initialBalance;
        }
    }

    public void deposit(int amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println(amount + "원이 입금되었습니다.");
        }
    }

    public void withdraw(int amount) {
        if (amount > balance) {
            System.out.println("잔액 부족으로 출금할 수 없습니다.");
        } else {
            balance -= amount;
            System.out.println(amount + "원이 출금되었습니다.");
        }
    }

    public void showBalance() {
        System.out.println("현재 잔액: " + balance + "");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BankMain {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(10000);
        account.showBalance();
        account.deposit(5000);
        account.showBalance();
        account.withdraw(12000); // 초과 출금
        account.showBalance();
    }
}

현재 잔액: 10000
5000원이 입금되었습니다.
현재 잔액: 15000
잔액 부족으로 출금할  없습니다.
현재 잔액: 15000

기능은 잘 동작하지만 나중에 다른 개발자가 BankAccount 클래스를 사용할 때, 실수로 다음과 같이 balance 값을 마음대로 바꿀 수도 있다.

왜? public int balance; 때문

1
2
3
4
5
6
7
8
9
public class BankMain {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(10000);
        account.balance = -500000; // 잘못된 직접 접근
        account.showBalance();     // 시스템 이상 발생 가능
    }
}

현재 잔액: -500000

이런 대참사를 막으려면, balance 필드를 외부에서 직접 접근하지 못하게 막아야 한다.

1
2
3
4
5
public class BankAccount {
    private int balance; //public -> private

    // 이하 동일
}

private 접근 제어자는 모든 외부 호출을 막는다. 따라서 private이 붙은 경우 해당 클래스 내부에서만 호출할 수 있다.

이렇게 하면 클래스 외부에서 account.balance = -500000; 같은 코드는 더 이상 사용할 수 없다.

접근 제어자 종류

  • private

    → 모든 외부 호출을 막는다. 해당 클래스 내부에서만 접근 가능하다.

    정보 은닉(Encapsulation) 을 위해 자주 사용된다.

  • default (아무 접근 제어자도 붙이지 않았을 때)

    같은 패키지 내에서만 접근 가능하다. 다른 패키지에서는 접근할 수 없다.

  • protected

    같은 패키지 또는 상속받은 자식 클래스에서만 접근 가능하다.

    → 주로 상속 관계에서 부모 클래스의 일부 기능을 제한적으로 열어줄 때 사용한다.

  • public

    모든 클래스에서 접근 가능하다. 어디서든 자유롭게 사용할 수 있다.

✅ private 이 가장 많이 차단하고, public 이 가장 많이 허용한다. private -> default -> protected -> public

package-private

package-private은 접근 제어자를 생략을때 기본으로 적용되는 접근 수준

즉, 아무 접근 제어자도 쓰지 않으면 default 접근 수준이 되고, 이를 package-private 라고 부른다.

접근 제어자의 사용 위치

접근 제어자는 필드와 메서드, 생성자에 사용된다.

1
2
3
4
5
6
7
8
9
public class BankAccount { // 클래스 레벨
    private int balance; // 필드

    public BankAccount(int initialBalance) {} // 생성자

    public void deposit(int amount) {} // 메서드
    public void withdraw(int amount) {}
    public void showBalance() {}
}
✅ 접근 제어자의 핵심은 속성과 기능을 외부로부터 숨기는 것

필드, 메서드 레벨의 접근 제어자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package access.car;

public class Car {
    public String modelName;     // 누구나 접근 가능
    int speed;                   // 같은 패키지 내에서만 접근 가능
    private boolean engineOn;    // 외부에서 직접 접근 불가

    public void startEngine() {
        engineOn = true;
        System.out.println("엔진이 켜졌습니다.");
    }

    void accelerate() {
        if (engineOn) {
            speed += 10;
            System.out.println("속도 증가: " + speed + "km/h");
        } else {
            System.out.println("엔진이 꺼져 있어 가속할 수 없습니다.");
        }
    }

    private void stopCompletely() {
        speed = 0;
        System.out.println("차량이 정지했습니다.");
    }

    public void shutdown() {
        stopCompletely();
        engineOn = false;
        System.out.println("엔진이 꺼졌습니다.");
    }

    public void innerAccessTest() {
        // 클래스 내부에서는 모두 접근 가능
        modelName = "Avante";
        speed = 60;
        engineOn = true;

        startEngine();
        accelerate();
        stopCompletely();
    }
}

외부에서 이 클래스에 접근해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package access.car;

public class CarMain {
    public static void main(String[] args) {
        Car car = new Car();

        car.modelName = "Sonata";   // O
        car.startEngine();          // O

        car.speed = 30;             // O (같은 패키지니까)
        car.accelerate();           // O

        // car.engineOn = true;     // X (private)
        // car.stopCompletely();    // X (private)

        car.shutdown();             // O
        car.innerAccessTest();      // 내부 메서드에선 모두 접근 가능
    }
}

  • public → 외부에서 사용 가능한 필드/기능 (modelName, startEngine)
  • default → 같은 패키지에서만 접근 가능한 필드/기능 (speed, accelerate)
  • private → 클래스 내부에서만 접근 가능 (engineOn, stopCompletely)
  • innerAccessTest() 메서드 내부에선 모두 접근 가능 (자기 클래스니까!)

다른 패키지에서 접근

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package access.b;

import access.a.Car;

public class CarOuterMain {
    public static void main(String[] args) {
        Car car = new Car();

        // public 접근 가능
        car.modelName = "Sonata";
        car.startEngine();

        // default 접근 불가 (다른 패키지)
        // car.speed = 50;
        // car.accelerate();

        // private 접근 불가
        // car.engineOn = true;
        // car.stopCompletely();

        car.innerAccessTest(); // public 이므로 호출 가능
    }
}
  • public 은 모든 접근을 허용하기 때문에 modelName, startEngine()은 접근 가능하다.
  • default(접근 제어자 생략)는 같은 패키지에서만 접근 가능하다.

    access.b.CarOuterMainaccess.a.Car다른 패키지에 있으므로 speed, accelerate()는 접근 불가.

  • private 은 클래스 내부에서만 접근 가능하다. 외부에서는 engineOn, stopCompletely()에 접근할 수 없다.
  • innerAccessTest()public 이기 때문에 외부에서 호출 가능하다.
innerAccessTest() 메서드는 외부에서 호출되었지만, Car 클래스 내부 메서드이므로 내부의 private 필드와 메서드에 자유롭게 접근할 수 있다.

클래스 레벨 접근 제어자

클래스 레벨의 접근 제어자 규칙

  • public
  • default (접근 제어자 생략) ✅
  • private, protected ❌ → 클래스 레벨에서는 사용할 수 없음

규칙 정리

  • public 클래스는 반드시 파일명과 클래스명이 같아야 한다.
  • 하나의 .java 파일에 public 클래스는 오직 하나만 선언 가능
  • default 클래스는 같은 파일에 여러 개 선언 가능
  • public 클래스는 모든 패키지에서 접근 가능
  • default 클래스는 같은 패키지 내에서만 접근 가능
1
2
3
4
5
6
7
8
9
10
11
12
package access.a;

public class PublicClass {
    public static void main(String[] args) {
        PublicClass pc = new PublicClass();
        DefaultClass1 d1 = new DefaultClass1();
        DefaultClass2 d2 = new DefaultClass2();
    }
}

class DefaultClass1 {}   // default
class DefaultClass2 {}   // default

같은 패키지에서 접근

1
2
3
4
5
6
7
8
9
package access.a;

public class PublicClassInnerMain {
    public static void main(String[] args) {
        PublicClass pc = new PublicClass();      // O
        DefaultClass1 d1 = new DefaultClass1();  // O
        DefaultClass2 d2 = new DefaultClass2();  // O
    }
}
  • 같은 패키지 → default 클래스도 접근 가능

다른 패키지에서 접근

1
2
3
4
5
6
7
8
9
10
11
12
package access.b;

import access.a.PublicClass;

public class PublicClassOuterMain {
    public static void main(String[] args) {
        PublicClass pc = new PublicClass();      // O

        // DefaultClass1 d1 = new DefaultClass1();  // X
        // DefaultClass2 d2 = new DefaultClass2();  // X
    }
}
  • PublicClasspublic 이므로 어디서든 접근 가능
  • DefaultClass1, DefaultClass2default 이므로 다른 패키지에서 접근 불가

캡슐화(Encapsulation)

캡슐화(Encapsulation)란 객체의 속성과 기능을 외부로부터 숨기고, 제한된 방법으로만 접근하도록 만드는 객체지향의 핵심 원칙이다.

접근 제어자를 사용하면 필드와 메서드의 접근 범위를 조절하여, 잘못된 접근을 방지하고, 객체 내부 상태를 안전하게 보호할 수 있다.

예를 들어 private 으로 필드를 감추고,

public 메서드를 통해서만 값을 설정하거나 읽도록 제한하는 것이 대표적인 캡슐화 구현 방식이다.

캡슐화의 목적

  • 객체 내부 데이터를 보호하고 잘못된 접근을 막기 위함
  • 안정성과 일관성을 유지하기 위함

접근 제어자는 캡슐화를 완성하는 도구다. 어떤 데이터/기능을 숨기고, 어떤 것만 외부에 공개할지를 정할 수 있게 해준다.

캡슐화의 핵심 원칙

  1. 데이터는 숨겨라
  • 필드(속성)는 대부분 private 으로 숨긴다
  • 외부에서는 public 메서드를 통해서만 간접적으로 접근하게 한다
  • 예: 자동차의 속도나 음악 플레이어의 볼륨처럼, 내부 데이터를 직접 조작하지 않고 기능만 사용

2. 기능도 숨겨라

  • 외부에서 사용할 필요 없는 내부 전용 메서드는 private 으로 감춘다
  • 사용자에게 필요한 기능만 최소한으로 노출하자
  • 예: 자동차 내부의 엔진 작동 방식은 감추고, 운전자는 엑셀/브레이크만 알면 된다

캡슐화 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package access.car;

public class Car {
    // 속성은 모두 private → 외부에서 직접 접근 불가
    private int speed;
    private boolean engineOn;

    // 엔진 시동 켜기
    public void startEngine() {
        engineOn = true;
        System.out.println("엔진이 켜졌습니다.");
    }

    // 엔진 시동 끄기
    public void stopEngine() {
        engineOn = false;
        speed = 0;
        System.out.println("엔진이 꺼졌습니다. 속도: 0km/h");
    }

    // 가속
    public void accelerate() {
        if (!engineOn) {
            System.out.println("엔진이 꺼져 있어 가속할 수 없습니다.");
            return;
        }
        speed += 10;
        System.out.println("속도 증가: " + speed + "km/h");
    }

    // 현재 상태 확인 (getter 역할)
    public void showStatus() {
        System.out.println("현재 상태 - 엔진: " + (engineOn ? "ON" : "OFF") + ", 속도: " + speed + "km/h");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package access.car;

public class CarMain {
    public static void main(String[] args) {
        Car car = new Car();

        // 내부 상태는 접근 불가
        // car.speed = 100;        // private 접근 불가
        // car.engineOn = true;    // private 접근 불가

        car.startEngine();         // ✅
        car.accelerate();          // ✅
        car.accelerate();
        car.showStatus();          // ✅

        car.stopEngine();          // ✅
        car.accelerate();          // 시동 꺼졌을 때 가속 불가
    }
}

자동차(Car) 객체는 다음과 같은 기능을 가지고 있다:

private (숨겨야 하는 내부 요소)

  • engineOn: 엔진 상태는 외부에서 직접 조작하지 않는다. 자동차 내부 로직이 제어한다.
  • speed: 속도 역시 외부에서 직접 변경할 수 없고, 자동차의 기능(가속 등)을 통해서만 바뀐다.
  • stopCompletely(): 정지 처리 메서드는 내부에서만 호출되며, 외부에서는 사용할 필요가 없다.

public (외부에 공개된 기능)

  • startEngine() : 시동을 켠다.
  • accelerate() : 가속한다.
  • stopEngine() : 시동을 끈다.
  • showStatus() : 현재 상태(엔진, 속도)를 보여준다.

자동차를 사용하는 입장에서는 단 4개의 public 메서드만 알면 된다:

  • startEngine()
  • accelerate()
  • stopEngine()
  • showStatus()

자동차 내부의 속도(speed)나 엔진 상태(engineOn) 같은 구현 세부사항은 몰라도 된다.

그저 “엑셀 밟으면 앞으로 나간다”, “시동을 걸면 움직일 준비가 된다”만 알면 충분하다.


접근 제어자와 캡슐화를 통해:

  • 데이터는 숨기고
  • 기능은 꼭 필요한 것만 공개함으로써
  • 객체의 안정성과 사용자의 개발 편의성을 높일 수 있다.

자동차처럼 복잡한 시스템도, 필요한 기능만 단순하게 노출하면 사용자는 쉽게 다룰 수 있다.


출처: 김영한의 실전 자바 - 기본편

This post is licensed under CC BY 4.0 by the author.

Trending Tags