우리가 클래스, 변수, 메서드 등을 선언할 때 항상 public, protected, private를 사용했다.
가끔 아무 생각도 없이 public, private를 남발하여, 코딩한 적이 많을 것이다.
그래서 각 접근 제어자가 무엇을 의미하는지, 개발에서 어떤 역할을 하는지 등 간단한 예시로 설명했다.
접근 제어자
자바 접근제어자는 클래스, 메서드, 변수에 대한 접근 권한을 제어하여 불필요한 노출을 막고 데이터의 무결성을 지키는 핵심 기능이다.
이것은 단순히 볼 수 있다/없다의 개념을 넘어, 객체지향의 핵심인 캡슐화를 완성하는 도구이다.
| 접근 제어자 | 같은 클래스 | 같은 패키지 | 자식 클래스(타 패키지) | 전체(외부) | 비고 |
| private | O | X | X | X | 가장 엄격한 접근 제어자로 나만 사용 가능하다. |
| default | O | O | X | X | 키워드 생략 시 적용된다. |
| protected | O | O | O | X | 상속 관계 허용된다. |
| public | O | O | O | O | 어디서든 접근 가능하다. |
private
선언된 해당 클래스 내부에서만 접근 가능하다.
멤버 변수(필드 변수)는 private로 선언한다. 외부에서 값을 함부로 수정하지 못하고 Getter/Setter 메서드를 통해서만 접근하게 하여 데이터를 보호한다. 외부에서 알 필요 없는 복잡한 내부 계산 로직을 숨길 때 사용한다.
public class BankAccount {
// 멤버 변수는 무조건 private으로 숨김
private double balance;
// 생성자를 통해 초기화
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
// 외부에서는 이 메서드를 통해서만 잔액 변경 가능
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount; // 내부에서는 private 변수 접근 가능
}
}
}
default
접근 제어자를 아무것도 적지 않았을 때 적용되며, 같은 패키지 내의 클래스들끼리지만 접근이 가능하다.
특정 패키지 안에서만 협력이 가능하고, 외부 패키지에는 노출하고 싶지 않은 도우미(Helper) 클래스나 내부 설정 클래스에 사용된다. 하지만, 명시적인 private나 public을 선호하기 때문에 default는 의도적으로 사용하기엔 드물다.
package com.mycompany.order;
// OrderService에서만 쓰이는 검증 클래스 (외부 패키지에선 몰라도 됨)
class OrderValidator {
void validate() { ... }
}
protected
같은 패키지 + 다른 패키지의 자식 클래스(상속)에서 접근 가능하다.
라이브러리나 프레임워크를 만들 때, 외부에서 직접 호출하지는 말고, 상속받아서 기능을 확장(Override)할 때만 사용하라는 의도이다. 예를 들어, AbstractMap 추상 클래스 내부의 핵심 메서드들이 protected로 되어 있어, 이를 상속받는 HashMap 등은 해당 기능에 접근할 수 있다.
package com.core;
public class GraphicCard {
// 자식 클래스(구체적인 그래픽카드 모델)에서만 접근하여 수정 가능
protected String chipset;
protected void boot() {
System.out.println("그래픽 카드 부팅");
}
}
같은 그래픽카드 모델 예를 들면, 부모 클래스를 엔비디아의 제품으로 지정할 경우, 자식들은 엔비디아만 상속받고, AMD의 라데온은 받을 수 없다.
public
제한 없이 어디서나 접근 가능하다.
API 엔드 포인터인 컨트롤러 메서드처럼 외부 요청을 받아야 할 경우, DTO(Data Transfer Object)에서 사용하는 Getter/Setter, 인터페이스의 메서드 경우(Java 9 private 등도 가능하지만, 표준은 public) 모두, public을 사용한다.
개발 권장 사항
개발에서는 접근 권한은 최소화한다.(Principle of Least Privilege)는 원칙을 따른다.
일단 private로 시작하라
변수나 메서드를 만들 때 일단 private로 시작한다.
만약 외부에서 접근이 반드시 필요할 경우, protected -> public 순으로 범위를 넓히는 것이 안전하다.
- 만약 모든 메서드를 public으로 열어두면, 다른 개발자(혹은 미래의 나)가 내부용으로 만든 메서드까지 가져다 쓰게 된다.
- 나중에 내부 로직을 바꾸거나 메서드 이름을 바꾸려 할 때, 이미 그 메서드를 쓰고 있는 다른 수십 개의 클래스들까지 전부 에러가 발생한다. (결합도가 높아짐)
- private으로 막아두면, "이건 내 내부 사정이니 언제든 맘대로 바꿔도 됨"이라는 보장을 받게 되어 유지보수가 훨씬 쉬워진다.
변수는 절대 public으로 사용하지 말자
public String password;
위와 같은 코드는 최악이 될 것이다.
누구나 비밀번호를 변경할 수 있기 때문에, 변수는 private, 접근은 public 메서드(Getter/Setter)를 권장한다.
- public int age; 라고 선언하면, 누군가 실수로 user.age = -500; 이라고 코드를 짜도 막을 방법이 없습니다.
나이는 음수가 될 수 없다. - 그렇기에 private int age; 로 막고 setAge(int age) 메서드를 만들면, 메서드 안에서 if (age < 0) 와 같은 검증 로직(Validation)을 넣을 수 있다.
- 즉, 객체가 망가지는 것을 방지하는 최후의 보루 역할이다.
유틸리티 클래스의 생성자는 private으로 막아라
모든 메서드가 static인 클래스(StringUtils)는 객체를 생성할 필요가 없다.
생성자를 private로 선언하여 new StringUtils()를 방지해야 한다.
public class StringUtils {
// 인스턴스화 방지
private StringUtils() {}
public static boolean isEmpty(String str) { ... }
}
- MathUtils 같은 클래스는 기능(함수)만 모아둔 것이라 객체를 생성해서(new MathUtils()) 들고 있을 필요가 없다.
- 생성자를 안 막으면 신입 개발자가 MathUtils m = new MathUtils(); 처럼 의미 없는 객체를 힙 메모리에 계속 만들 수 있다.
- 생성자를 private으로 막는 것은 "이 클래스는 new 하지 말고 static 메서드만 갖다 쓰세요"라고 코드 자체로 대화(문서화)하는 것이다.
불변 객체( Immutable Object ) 생성
생성자에 값을 넣은 후 바뀌지 않아야 하는 객체는 필드를 private final로 선언하고 Setter를 만들지 말아야 한다.(VO)
- 웹 개발(Spring 등)은 수많은 사용자가 동시에 요청을 보낸다.(멀티 스레드).
- 만약 A 사용자의 요청을 처리하는 중에 B 사용자의 요청이 같은 객체의 값을 set으로 바꿔버리면, A 사용자는 엉뚱한 결과를 받게 된다. (Side Effect / 부작용)
- 애초에 수정할 수 없도록(final, Setter 없음) 만들면, 누가 언제 접근해도 항상 같은 값을 보장하므로 시스템이 훨씬 안정적이다.
면접 답변식 요약
접근 제어자는 객체 지향의 핵심인 '캡슐화'를 구현하여 데이터를 보호하고 유지보수성을 높이는 역할을 합니다.
종류는 private, default, protected, public 네 가지가 있으며, 저는 코드를 작성할 때 '최소 권한의 원칙'을 따릅니다.
기본적으로 모든 멤버 변수는 private으로 선언하여 외부의 직접적인 조작을 막아 데이터 무결성을 지킵니다. 외부 접근이 꼭 필요한 경우에만 메서드를 통해 제한적으로 엽니다.
이렇게 접근 범위를 최소화하면, 향후 내부 로직을 변경하더라도 외부에 미치는 영향(Side Effect)을 최소화할 수 있어 결합도를 낮추고 유지보수를 쉽게 할 수 있기 때문입니다.
'Daily Dev Q&A 정리 템플릿' 카테고리의 다른 글
| 25.12.11 자바의 빌더 패턴(Builder Pattern) (0) | 2025.12.11 |
|---|---|
| 25.12.10 자바에 제네릭을 왜 사용할까? (1) | 2025.12.10 |
| 25.12.08 자바의 Collections Framework에 대해 설명해보기 (0) | 2025.12.08 |
| 25.12.07 SOLID원칙에 대해 (1) | 2025.12.07 |
| 25.12.04 오버로딩과 오버라이딩에 대해서 구체적으로 설명하라는 질문에 대한 대답은? (0) | 2025.12.05 |