Java : 접근 제한자 - (private, default, protected, public)

최혜린·2025년 3월 13일

접근제한자란?

✔ 클래스, 변수, 메서드에 접근할 수 있는 범위를 지정하는 키워드.
✔ 허용되지 않은 외부 접근을 막아 데이터 보호 및 보안 강화 역할.

자바에서는 클래스, 변수, 메서드에 접근할 수 있는 범위를 접근제한자(Access Modifier)로 조절할 수 있다.

같은 클래스같은패키지자식클래스외부클래스
private✅ 가능❌ 불가능❌ 불가능❌ 불가능
default✅ 가능✅ 가능❌ 불가능❌ 불가능
protected✅ 가능✅ 가능✅ 가능❌ 불가능
public✅ 가능✅ 가능✅ 가능✅ 가능

1. private (완전 비공개)

  • 같은 클래스 내부에서만 접근 가능하다.
  • 외부 클래스에서 직접 접근이 불가능하며 getter/setter를 사용해야한다.
  • private는 보안이 필요한 데이터 (비밀번호, 계좌 잔액 등 ) 에 사용된다.
class Person {
    private String name = "홍길동";  // private 변수 (외부 접근 불가)

    public String getName() {  // public 메서드를 통해 접근 가능
        return name;
    }
}

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
        // System.out.println(p.name); // ❌ 오류 (private 변수 직접 접근 불가)
        System.out.println(p.getName()); // ✅ getter 사용해서 접근 (출력: 홍길동)
    }
}

2. dafault(패키지 내부 공개)

  • 접근 제한자를 아무것도 안쓰면 dafault가 적용된다.
  • 같은 패키지 내에서는 접근이 가능하다.
class DefaultExample {  // default 클래스 (같은 패키지 내에서만 접근 가능)
    int number = 10;   // default 변수
}

public class Main {
    public static void main(String[] args) {
        DefaultExample ex = new DefaultExample();
        System.out.println(ex.number); // ✅ 같은 패키지 내라서 접근 가능
    }
}

3. protected(상속 관계에서만 접근 가능)

  • 같은 패키지에서는 접근 가능
  • 다른 패키지라도 상속받은 자식 클래스라면 접근 가능하다.
class Parent {
    protected String message = "Hello from Parent";
}

class Child extends Parent {
    public void printMessage() {
        System.out.println(message); // ✅ 부모 클래스의 protected 변수 사용 가능
    }
}

public class Main {
    public static void main(String[] args) {
        Child c = new Child();
        c.printMessage(); // 출력: Hello from Parent
    }
}

4. public(완전공개)

  • 어디서든 접근가능
class PublicExample {
    public String message = "공개 데이터";
}

public class Main {
    public static void main(String[] args) {
        PublicExample ex = new PublicExample();
        System.out.println(ex.message); // ✅ 어디서든 접근 가능 (출력: 공개 데이터)
    }
}


왜 private + getter/setter를?

처음 공부할때.. 왜 굳이 private를 써서 getter/setter를 번거롭게 써야하지..? 라는 생각이 있었기에 나와 같은 코린이에게 도움이 되길 바라며 추가로 정리해보자면..

1️⃣ 데이터 보호 (정보 은닉)

  • public → 어디서든 필드 값을 변경할 수 있다. 예기치 못한 오류 발생 가능..!
  • private으로 숨기고, 필요한 기능만 getter/setter로 제공하면 안정적인 데이터 관리 가능.

🔴 public 필드를 그대로 사용하면?

class BankAccount {
    public int balance = 10000;  // public → 외부에서 직접 접근 가능!
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount();
        account.balance = -5000;  // ❌ 오류 발생! (마이너스 잔액이 가능해짐)
        System.out.println("잔액: " + account.balance);
    }
}

public 때문에 외부에서 잘못된 갑이 들어가도 막을 방법이 없음!


🟢 private을 사용하고, Getter/Setter를 통해 접근을 제어한다.

class BankAccount {
    private int balance = 10000;  // private → 직접 접근 불가

    public void setBalance(int amount) {
        if (amount >= 0) {
            this.balance = amount;
        } else {
            System.out.println("잘못된 금액입니다.");
        }
    }

    public int getBalance() {
        return balance;
    }
}

이제 setBalance(-5000) 같은 잘못된 입력을 방어할 수 있음!


2️⃣ 유지보수성과 확장성 향상

  • 필드가 public이면, 코드가 변경될 때 전체 프로그램을 수정해야 할 수도 있다.
  • 반면, getter/setter를 사용하면 내부 로직을 바꿔도 외부 코드는 수정할 필요가 없다.

🔴 예시: public 필드 사용 시 유지보수 문제

class User {
    public String username;  // public → 직접 접근 가능
}

public class Main {
    public static void main(String[] args) {
        User user = new User();
        user.username = "hyerin";  // 여기까지는 문제없음
    }
}

✅ 하지만 이후에 username을 대문자로 저장하고 싶다면?→ 모든 user.username을 수정해야 함

🟢 해결: private + setter 사용

class User {
    private String username;

    public void setUsername(String username) {
        this.username = username.toUpperCase();  // 자동으로 대문자로 변환
    }

    public String getUsername() {
        return username;
    }
}

✔ 이제 setUsername("heyrin")을 호출하면 자동으로 "heyrin" → "heyrin".toUpperCase()로 변환됨.
✔ 외부 코드는 수정할 필요 없이, 내부 로직만 변경하면 됨!


3️⃣ 코드의 안정성과 가독성 향상

  • public 필드를 남발하면 누가, 언제, 어디서 값을 바꾸는지 추적하기 어려움.
  • private과 메서드를 활용하면, 데이터 변경이 한정된 경로를 통해 이루어져 가독성이 좋아짐.

🔴 문제: 직접 필드 변경 시 추적 어려움

class Order {
    public int quantity;
}

public class Main {
    public static void main(String[] args) {
        Order order = new Order();
        order.quantity = 10;  // 여기서는 10
        order.quantity = 5;   // 어디서 바뀌었는지 추적 어려움
    }
}

→ order.quantity가 변경될 때, 어떤 조건으로 바뀌었는지 알 수 없음.

🟢 해결: private + setter 사용

class Order {
    private int quantity;

    public void setQuantity(int quantity) {
        System.out.println("수량 변경: " + this.quantity + " → " + quantity); 
        this.quantity = quantity;
    }
}

public class Main {
    public static void main(String[] args) {
        Order order = new Order();
        
        order.setQuantity(10);  // 값 변경 (초기값 0 → 10)
        order.setQuantity(5);   // 값 변경 (10 → 5)
    }
}

✔ 이제 값이 변경될 때 로그가 남아, 어디서 바뀌었는지 추적 가능!


초창기 자바 개발에서는 getter/setter 없이 public 필드를 직접 사용하는 경우가 많았지만 코드가 복잡해지고 유지보수성이 중요해지면서, 캡슐화의 필요성이 강조되었다.
현재는 데이터 보호와 유효성 검사를 위해 private 필드와 getter/setter를 사용하는 것이 일반적이다!

하지만, Getter/Setter를 사용하는 것에도 문제점이 있을 수 있다.
➡ 이 부분은 "Getter/Setter" 글에서 더 자세히 다룬다.

Getter/Setter

profile
산으로 가는 코딩.. 등산 중..🌄

0개의 댓글