[Java] 개념노트 26 접근 제어자 이해하기

[Java 개념노트 26] 접근 제어자 이해하기

안녕하세요. Java 개념노트 시리즈 스물여섯 번째 글입니다.

지난 글에서는 값을 변경하지 못하게 막는 final 키워드에 대해 정리했습니다. 이번 글에서는 클래스, 필드, 메서드, 생성자에 접근할 수 있는 범위를 정하는 접근 제어자에 대해 알아보겠습니다.

자바 객체지향을 공부하다 보면 public, private, protected 같은 키워드를 자주 만나게 됩니다. 이 키워드들은 단순히 붙이는 장식이 아니라, 코드의 안정성과 유지보수성을 높이는 중요한 문법입니다.


1. 접근 제어자란?

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

어떤 코드는 외부에서 자유롭게 사용할 수 있어야 하고, 어떤 코드는 클래스 내부에서만 사용되어야 합니다. 접근 제어자는 이런 접근 범위를 조절하는 역할을 합니다.

쉽게 말하면
접근 제어자는 코드에 문을 달아서 “누가 여기까지 들어올 수 있는지” 정하는 문법입니다.

2. 접근 제어자의 종류

자바의 대표적인 접근 제어자는 다음 네 가지입니다.

접근 제어자 접근 범위 설명
public 모든 곳 어디서든 접근할 수 있습니다.
protected 같은 패키지 + 자식 클래스 상속 관계에서 자주 사용됩니다.
default 같은 패키지 접근 제어자를 작성하지 않은 상태입니다.
private 같은 클래스 내부 클래스 내부에서만 접근할 수 있습니다.

접근 범위를 넓은 순서로 정리하면 다음과 같습니다.

public > protected > default > private

3. public 접근 제어자

public은 가장 넓은 접근 범위를 가집니다. 어디서든 접근할 수 있습니다.

public class Member {
    public String name;

    public void printName() {
        System.out.println(name);
    }
}

위 코드에서 Member 클래스, name 필드, printName() 메서드는 모두 public입니다. 따라서 다른 클래스에서도 접근할 수 있습니다.

Member member = new Member();

member.name = "건";
member.printName();

핵심 포인트
public은 외부에서 자유롭게 사용해도 되는 클래스나 메서드에 붙입니다.

4. private 접근 제어자

private은 가장 좁은 접근 범위를 가집니다. 같은 클래스 안에서만 접근할 수 있습니다.

class Member {
    private String name;

    private void printSecret() {
        System.out.println("private 메서드입니다.");
    }
}

name 필드와 printSecret() 메서드는 Member 클래스 내부에서만 사용할 수 있습니다. 외부 클래스에서는 직접 접근할 수 없습니다.

Member member = new Member();

member.name = "건"; // 오류 발생

nameprivate 필드이기 때문에 클래스 밖에서 직접 접근할 수 없습니다.

중요
객체의 중요한 데이터는 보통 private으로 숨기고, 필요한 경우 메서드를 통해 접근하게 만듭니다.

5. default 접근 제어자

접근 제어자를 아무것도 작성하지 않으면 default 접근 범위가 됩니다. default는 같은 패키지 안에서만 접근할 수 있습니다.

class Member {
    String name;

    void printName() {
        System.out.println(name);
    }
}

위 코드에서 Member 클래스, name 필드, printName() 메서드는 접근 제어자가 없습니다. 이 상태를 default 접근이라고 합니다.

같은 패키지 안에서는 접근할 수 있지만, 다른 패키지에서는 접근할 수 없습니다.

6. protected 접근 제어자

protected는 같은 패키지 안에서 접근할 수 있고, 다른 패키지라도 상속받은 자식 클래스라면 접근할 수 있습니다.

class Parent {
    protected String message = "protected 필드입니다.";

    protected void printMessage() {
        System.out.println(message);
    }
}

protected는 상속과 관련이 깊은 접근 제어자입니다. 아직 상속을 본격적으로 배우기 전이라면, 지금은 같은 패키지와 자식 클래스에서 접근 가능하다 정도로 이해하면 됩니다.

7. 접근 범위 한눈에 보기

접근 제어자의 범위를 표로 정리하면 다음과 같습니다.

접근 제어자 같은 클래스 같은 패키지 자식 클래스 전체 접근
public 가능 가능 가능 가능
protected 가능 가능 가능 제한적
default 가능 가능 같은 패키지면 가능 불가능
private 가능 불가능 불가능 불가능

8. 클래스에 붙는 접근 제어자

일반 클래스에는 보통 public 또는 default 접근만 사용할 수 있습니다.

public class Member {
}

class Product {
}

public class Member는 다른 패키지에서도 접근할 수 있습니다. 반면 class Product는 접근 제어자가 없으므로 같은 패키지 안에서만 접근할 수 있습니다.

주의
일반 외부 클래스에는 private이나 protected를 직접 붙일 수 없습니다. 클래스 접근 제어는 주로 public 또는 default로 생각하면 됩니다.

9. public class와 파일 이름

자바에서는 public class 이름과 파일 이름이 같아야 합니다.

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello Java");
    }
}

위 코드가 들어 있는 파일 이름은 반드시 Main.java여야 합니다.

한 파일 안에 public class는 하나만 작성할 수 있습니다. 연습할 때는 아래처럼 public class Main 하나를 두고, 다른 클래스는 public 없이 작성하는 경우가 많습니다.

public class Main {
    public static void main(String[] args) {
        Member member = new Member();
    }
}

class Member {
}

10. 필드에 접근 제어자 사용하기

필드는 객체의 데이터를 저장하는 부분입니다. 중요한 데이터는 외부에서 직접 변경하지 못하도록 private으로 만드는 경우가 많습니다.

class Member {
    private String id;
    private String password;
    private int age;
}

아이디, 비밀번호, 나이 같은 값은 외부에서 아무렇게나 바꾸게 두면 위험할 수 있습니다. 그래서 필드는 보통 private으로 숨기고, 필요한 메서드를 통해 접근하게 만듭니다.

11. private 필드에 접근하는 방법

private 필드는 클래스 밖에서 직접 접근할 수 없습니다. 대신 public 메서드를 통해 값을 저장하거나 꺼낼 수 있습니다.

class Member {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

사용 예시는 다음과 같습니다.

Member member = new Member();

member.setName("건");

System.out.println(member.getName());

name 필드는 private이지만, setName()getName()은 public이므로 외부에서 사용할 수 있습니다.

이런 방식은 다음 글에서 다룰 캡슐화와 깊게 연결됩니다.

12. 메서드에 접근 제어자 사용하기

메서드도 외부에서 사용할 메서드와 내부에서만 사용할 메서드를 구분할 수 있습니다.

class OrderService {
    public void order() {
        validateOrder();
        saveOrder();
        System.out.println("주문 완료");
    }

    private void validateOrder() {
        System.out.println("주문 검증");
    }

    private void saveOrder() {
        System.out.println("주문 저장");
    }
}

order()는 외부에서 사용할 수 있는 public 메서드입니다. 반면 validateOrder()saveOrder()는 주문 처리 과정 내부에서만 사용하는 private 메서드입니다.

핵심 포인트
외부에 공개할 기능은 public, 내부에서만 사용할 세부 기능은 private으로 나누면 코드 구조가 깔끔해집니다.

13. 생성자에 접근 제어자 사용하기

생성자에도 접근 제어자를 붙일 수 있습니다.

class Member {
    private String name;

    public Member(String name) {
        this.name = name;
    }
}

위 생성자는 public이므로 외부에서 new Member("건")처럼 객체를 생성할 수 있습니다.

반대로 생성자를 private으로 만들면 클래스 밖에서 객체를 직접 생성할 수 없습니다.

class AppConfig {
    private AppConfig() {
    }
}

이런 방식은 나중에 유틸 클래스나 싱글톤 패턴을 배울 때 다시 등장합니다. 처음에는 생성자도 접근 범위를 제한할 수 있다 정도로 이해하면 됩니다.

14. 접근 제어자를 사용하는 이유

접근 제어자를 사용하는 가장 큰 이유는 코드를 안전하게 보호하고, 사용 범위를 명확하게 만들기 위해서입니다.

  • 외부에서 직접 건드리면 안 되는 데이터를 보호할 수 있습니다.
  • 클래스 내부 구현을 숨길 수 있습니다.
  • 사용해야 하는 기능과 내부용 기능을 구분할 수 있습니다.
  • 잘못된 사용을 줄일 수 있습니다.
  • 유지보수하기 쉬운 구조를 만들 수 있습니다.

모든 필드와 메서드를 public으로 열어두면 편해 보일 수 있지만, 프로그램이 커질수록 위험해집니다. 외부에서 어디서든 값을 바꿀 수 있으면 오류 원인을 찾기도 어려워집니다.

15. 모든 것을 public으로 만들면 안 되는 이유

다음 코드를 보겠습니다.

class Member {
    public String name;
    public int age;
}

이렇게 작성하면 외부에서 값을 마음대로 바꿀 수 있습니다.

Member member = new Member();

member.name = "건";
member.age = -100;

나이에 -100 같은 잘못된 값이 들어가도 막을 방법이 없습니다. 이런 문제를 줄이기 위해 필드를 private으로 숨기고, 메서드에서 검증 후 값을 저장하는 방식이 필요합니다.

16. private 필드와 검증 메서드

private 필드에 값을 저장할 때 public 메서드에서 검증을 넣을 수 있습니다.

class Member {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        if (age < 0) {
            System.out.println("나이는 0 이상이어야 합니다.");
            return;
        }

        this.age = age;
    }

    public void printInfo() {
        System.out.println("이름: " + name + ", 나이: " + age);
    }
}

사용 예시는 다음과 같습니다.

Member member = new Member();

member.setName("건");
member.setAge(-100);
member.setAge(20);

member.printInfo();

실행 결과는 다음과 같습니다.

나이는 0 이상이어야 합니다.
이름: 건, 나이: 20

외부에서 필드를 직접 바꾸지 못하게 하고, 메서드를 통해 검증 후 값을 저장하도록 만들었습니다.

17. 접근 제어자 선택 기준

처음에는 어떤 접근 제어자를 써야 할지 헷갈릴 수 있습니다. 아래 기준으로 생각하면 좋습니다.

상황 추천 접근 제어자 이유
외부에서 반드시 사용해야 하는 기능 public 다른 클래스에서 호출해야 하기 때문입니다.
객체의 내부 데이터 private 외부에서 직접 변경하지 못하게 보호합니다.
클래스 내부에서만 쓰는 보조 메서드 private 외부에 공개할 필요가 없습니다.
같은 패키지 안에서만 사용할 클래스 default 패키지 내부 전용으로 제한할 수 있습니다.
상속받은 클래스에서 사용해야 하는 멤버 protected 자식 클래스에서 접근할 수 있게 합니다.

18. 접근 제어자 전체 예제

이번에는 접근 제어자를 함께 사용하는 전체 예제를 보겠습니다.

public class Main {
    public static void main(String[] args) {
        Account account = new Account("건");

        account.deposit(10000);
        account.withdraw(3000);
        account.withdraw(10000);

        account.printBalance();
    }
}

class Account {
    private String owner;
    private int balance;

    public Account(String owner) {
        this.owner = owner;
        this.balance = 0;
    }

    public void deposit(int amount) {
        if (amount <= 0) {
            System.out.println("입금액은 0보다 커야 합니다.");
            return;
        }

        balance += amount;
    }

    public void withdraw(int amount) {
        if (amount <= 0) {
            System.out.println("출금액은 0보다 커야 합니다.");
            return;
        }

        if (amount > balance) {
            System.out.println("잔액이 부족합니다.");
            return;
        }

        balance -= amount;
    }

    public void printBalance() {
        System.out.println(owner + "님의 잔액: " + balance);
    }
}

실행 결과는 다음과 같습니다.

잔액이 부족합니다.
건님의 잔액: 7000

ownerbalance는 private 필드로 보호했습니다. 외부에서는 deposit(), withdraw(), printBalance() 같은 public 메서드를 통해서만 계좌 기능을 사용할 수 있습니다.

19. 접근 제어자 사용 시 자주 하는 실수

접근 제어자를 처음 배울 때는 아래와 같은 실수를 자주 합니다.

실수 문제점 해결 방법
모든 필드를 public으로 작성 외부에서 값이 마음대로 변경됨 필드는 기본적으로 private 고려
private 필드에 직접 접근 컴파일 오류 발생 public 메서드를 통해 접근
접근 제어자를 안 쓰면 public이라고 착각 default 접근 범위가 됨 작성하지 않으면 같은 패키지 접근으로 이해
protected를 public처럼 생각 접근 범위를 오해할 수 있음 같은 패키지와 자식 클래스 중심으로 이해
public class 파일 이름 불일치 컴파일 오류 발생 public class 이름과 파일 이름 맞추기

20. 직접 연습해보기

아래 코드를 직접 작성하고 실행해보세요.

public class Main {
    public static void main(String[] args) {
        Product product = new Product("키보드", 50000);

        product.printInfo();

        product.setPrice(-1000);
        product.setPrice(45000);

        product.printInfo();
    }
}

class Product {
    private String name;
    private int price;

    public Product(String name, int price) {
        this.name = name;
        setPrice(price);
    }

    public void setPrice(int price) {
        if (price < 0) {
            System.out.println("가격은 0 이상이어야 합니다.");
            return;
        }

        this.price = price;
    }

    public int getPrice() {
        return price;
    }

    public void printInfo() {
        System.out.println("상품명: " + name + ", 가격: " + price);
    }
}

실행 결과는 다음과 같습니다.

상품명: 키보드, 가격: 50000
가격은 0 이상이어야 합니다.
상품명: 키보드, 가격: 45000

21. 이번 글 정리

이번 글에서는 자바의 접근 제어자에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.

  • 접근 제어자는 클래스, 필드, 메서드, 생성자의 접근 범위를 정한다.
  • public은 어디서든 접근할 수 있다.
  • private은 같은 클래스 내부에서만 접근할 수 있다.
  • 접근 제어자를 쓰지 않으면 default 접근 범위가 된다.
  • default는 같은 패키지 안에서만 접근할 수 있다.
  • protected는 같은 패키지와 자식 클래스에서 접근할 수 있다.
  • 일반 외부 클래스에는 주로 public 또는 default 접근만 사용할 수 있다.
  • 필드는 보통 private으로 숨기고 메서드를 통해 접근하게 만든다.
  • 외부에 공개할 기능은 public, 내부 보조 기능은 private으로 나눌 수 있다.
  • 접근 제어자는 객체지향의 캡슐화와 깊게 연결된다.

한 줄 요약
접근 제어자는 클래스와 멤버를 어디까지 공개할지 정해서 데이터를 보호하고 코드 구조를 안정적으로 만드는 문법입니다.

다음 글 예고
다음 글에서는 [Java 개념노트 27] 캡슐화와 getter/setter 이해하기라는 주제로 private 필드를 안전하게 다루는 방법을 정리해보겠습니다.

GWDEVELBlog Java 개념노트 시리즈