9osari.log
← 목록으로

[JAVA] 접근제어자

2025년 4월 11일 · #java#김영한-java-기본

접근 제어자

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

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

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

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

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

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 + "원");
  }
}
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; 때문

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

현재 잔액: -500000원

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

public class BankAccount {
    private int balance; //public -> private

    // 이하 동일
}

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

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

접근 제어자 종류

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

package-private

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

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

접근 제어자의 사용 위치

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

public class BankAccount { // 클래스 레벨
    private int balance; // 필드

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

    public void deposit(int amount) {} // 메서드
    public void withdraw(int amount) {}
    public void showBalance() {}
}

접근 제어자의 핵심은 속성과 기능을 외부로부터 숨기는 것

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

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();
  }
}

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

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();      // 내부 메서드에선 모두 접근 가능
  }
}

다른 패키지에서 접근

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 이므로 호출 가능
  }
}

innerAccessTest() 메서드는 외부에서 호출되었지만, Car 클래스 내부 메서드이므로 내부의 private 필드와 메서드에 자유롭게 접근할 수 있다.

클래스 레벨 접근 제어자

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

규칙 정리

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

같은 패키지에서 접근

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
  }
}

다른 패키지에서 접근

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
  }
}

캡슐화(Encapsulation)

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

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

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

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

캡슐화의 목적

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

캡슐화의 핵심 원칙

  1. 데이터는 숨겨라

2. 기능도 숨겨라

캡슐화 예제

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");
  }
}
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 (숨겨야 하는 내부 요소)

public (외부에 공개된 기능)

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

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

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


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

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


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

···
← PREV [JAVA] 생성자 NEXT → [JAVA] 메모리 구조와 static