JavaEasy#55
Java의 접근 제어자(Access Modifier) 4가지를 설명해주세요.
#Java#접근제어자#캡슐화#OOP
힌트
public, protected, default, private의 접근 범위 차이를 생각해보세요.
정답 및 해설
Java의 접근 제어자(Access Modifier) 4가지를 설명해주세요.
접근 제어자(Access Modifier)는 클래스, 메서드, 변수 등의 멤버에 대한 접근 범위를 제한하는 키워드입니다. 캡슐화(Encapsulation)의 핵심 도구로, 외부에 노출할 것과 숨길 것을 구분합니다.
4가지 접근 제어자
1. private
같은 클래스 내에서만 접근 가능합니다. 캡슐화의 기본 원칙을 구현하는 가장 강한 접근 제한입니다.
public class BankAccount {
private double balance; // 외부에서 직접 접근 불가
public void deposit(double amount) {
if (amount > 0) {
balance += amount; // 같은 클래스 내부에서는 접근 가능
}
}
public double getBalance() {
return balance; // public getter로만 노출
}
}
BankAccount account = new BankAccount();
// account.balance = 1000; // 컴파일 오류!
account.deposit(1000); // OK
2. default (package-private)
같은 패키지 내에서만 접근 가능합니다. 키워드 없이 선언하며, 명시적인 접근 제어자를 붙이지 않으면 자동으로 적용됩니다.
// com.example.service 패키지
class UserValidator { // default 접근 제어자
boolean isValid(String email) { // default 메서드
return email.contains("@");
}
}
// 같은 패키지 내에서 접근 가능
class UserService {
private UserValidator validator = new UserValidator(); // OK
public void register(String email) {
if (validator.isValid(email)) { // OK — 같은 패키지
System.out.println("등록 성공");
}
}
}
// com.example.controller 패키지 (다른 패키지)
class UserController {
// UserValidator validator = new UserValidator(); // 컴파일 오류!
}
3. protected
같은 패키지 + 다른 패키지의 자식 클래스에서 접근 가능합니다. 상속 관계에서 부모 클래스의 구현을 자식 클래스에 선택적으로 노출할 때 사용합니다.
// com.example.base 패키지
public class Shape {
protected String color; // 자식 클래스에서 접근 가능
protected double area;
protected void draw() { // 자식 클래스에서 오버라이드 가능
System.out.println(color + " 도형을 그립니다.");
}
}
// com.example.shapes 패키지 (다른 패키지, Shape 상속)
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
this.color = "빨간색"; // protected 필드 접근 가능 (상속 관계)
}
@Override
protected void draw() {
System.out.println(color + " 원을 그립니다."); // OK
}
}
// com.example.other 패키지 (다른 패키지, 상속 관계 아님)
public class OtherClass {
public void test() {
Shape shape = new Shape();
// shape.color = "파란색"; // 컴파일 오류! (상속 관계가 아닌 직접 접근)
}
}
4. public
어디서든 접근 가능합니다. 패키지나 상속 여부에 관계없이 모든 클래스에서 접근할 수 있습니다.
public class Calculator {
public int add(int a, int b) { // 어디서든 접근 가능
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
}
// 어떤 패키지, 어떤 클래스에서든 사용 가능
Calculator calc = new Calculator();
calc.add(10, 5); // OK
calc.subtract(10, 5); // OK
접근 범위 비교
| 접근 제어자 | 같은 클래스 | 같은 패키지 | 자식 클래스 | 외부 |
|---|---|---|---|---|
private | O | X | X | X |
default | O | O | X | X |
protected | O | O | O | X |
public | O | O | O | O |
접근 범위: private < default < protected < public
클래스에 적용되는 접근 제어자
최상위(top-level) 클래스에는 public과 default만 사용할 수 있습니다. private이나 protected는 중첩 클래스(inner class)에만 사용 가능합니다.
public class OuterClass { // public 또는 default만 가능
private class PrivateInner {} // 중첩 클래스는 private 가능
protected class ProtectedInner {} // 중첩 클래스는 protected 가능
public class PublicInner {}
class DefaultInner {}
}
최소 권한 원칙
실무에서는 가능한 한 좁은 접근 제어자를 사용하는 것을 권장합니다. 이 원칙은 캡슐화를 강화하고 코드의 변경 범위를 제한합니다.
public class UserService {
// 외부에 공개할 필요 없는 것은 private
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
// 같은 패키지에서만 사용하는 유틸성 메서드는 default
boolean isEmailFormat(String email) {
return email.matches("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$");
}
// 상속 계층에서만 공유할 메서드는 protected
protected User createUser(String email, String password) {
String encoded = passwordEncoder.encode(password);
return new User(email, encoded);
}
// 외부에 실제로 공개할 API만 public
public void register(String email, String password) {
if (!isEmailFormat(email)) {
throw new IllegalArgumentException("유효하지 않은 이메일 형식입니다.");
}
userRepository.save(createUser(email, password));
}
}
실무 권장 패턴
public class Product {
// 1. 필드는 항상 private
private Long id;
private String name;
private BigDecimal price;
// 2. 외부에 공개할 접근자는 public
public Long getId() { return id; }
public String getName() { return name; }
// 3. 내부 검증 로직은 private
private boolean isValidPrice(BigDecimal price) {
return price != null && price.compareTo(BigDecimal.ZERO) > 0;
}
// 4. 상태 변경은 의미 있는 public 메서드로만 허용
public void updatePrice(BigDecimal newPrice) {
if (!isValidPrice(newPrice)) {
throw new IllegalArgumentException("가격은 0보다 커야 합니다.");
}
this.price = newPrice;
}
}