Home > Backend Development > 📚[Backend Development] 🚀 private vs private final

📚[Backend Development] 🚀 private vs private final
Backend Development Server Java

🔒 private vs private final - 단 하나의 키워드가 만드는 차이

“final 하나 붙이면 뭐가 달라지나요?” 🤔
이 질문은 자바 객체 설계의 핵심입니다. 실무에서 정말 중요한 개념이니 확실히 잡고 가세요!


🎯 한눈에 비교하기

구분 private private final
💫 값 변경 ⭕ 가능 ❌ 불가능
⏰ 초기화 시점 언제든 가능 생성 시 단 1번
🔄 재할당 ⭕ 가능 ❌ 불가능
🛡️ 객체 불변성 낮음 높음
🎨 설계 의도 가변 객체 불변 객체

📝 private 필드 - “변할 수 있는 상태”

특징

private int level;  // 얼마든지 바꿀 수 있어요!
  • ✅ 클래스 내부에서 자유롭게 변경 가능
  • ✅ setter로 값 변경 가능
  • 상태가 계속 바뀌는 객체에 사용

실전 예시

public class Monster {
    private int level;      // 레벨은 계속 올라가죠
    private int hp;         // HP는 변동됩니다
    private int exp;        // 경험치도 계속 쌓입니다

    public void levelUp() {
        this.level++;       // ✅ 변경 가능!
        this.hp = level * 100;
    }

    public void takeDamage(int damage) {
        this.hp -= damage;  // ✅ 변경 가능!
    }
}

🎮 언제 사용?

  • 게임 캐릭터의 레벨, HP, 경험치
  • 주문의 상태 (대기 → 진행 → 완료)
  • 카운터, 점수, 진행률
  • “시간에 따라 변하는 것들”

🔐 private final 필드 - “절대 변하지 않는 값”

특징

private final String name;  // 한 번 정하면 끝!
  • 딱 한 번만 초기화
  • ✅ 이후 절대 변경 불가
  • ✅ 생성자에서 반드시 값 할당

실전 예시

public class Monster {
    private final String name;        // 이름은 바뀌면 안 됨
    private final String species;     // 종족도 바뀌면 안 됨
    private final LocalDate birthDate; // 생일도 바뀌면 안 됨

    public Monster(String name, String species) {
        this.name = name;           // ✅ 생성 시 한 번만!
        this.species = species;
        this.birthDate = LocalDate.now();
    }

    // ❌ 이런 메서드는 만들 수 없어요
    // public void changeName(String newName) {
    //     this.name = newName;  // 컴파일 에러!
    // }
}

⚠️ 컴파일 에러 발생 케이스

Monster pikachu = new Monster("피카츄", "전기");
pikachu.name = "라이츄";  // ❌ 컴파일 에러!

💎 왜 final이 이렇게 중요할까? (실무 관점)

1️⃣ 객체 무결성 보장 🛡️

public class Order {
    private final Long orderId;      // 주문 ID는 절대 바뀌면 안 됨
    private final Long memberId;     // 주문한 사람도 바뀌면 안 됨
    private final LocalDateTime orderDate;  // 주문 날짜도 바뀌면 안 됨

    private OrderStatus status;      // 상태는 변할 수 있음 (대기→완료)
}

🤔 만약 orderId가 중간에 바뀐다면?

  • 주문 추적 불가능
  • 결제 정보와 불일치
  • 데이터 정합성 깨짐
  • 치명적인 버그 발생! 💥

👉 final“이건 절대 변하면 안 돼!” 를 명시


2️⃣ 버그 예방 - 의도치 않은 변경 차단 🐛

public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;

    // 실수로도 이런 코드를 못 씀
    // this.userRepository = null;  ❌ 컴파일 에러!
}

실제 사례:

// final 없으면 이런 실수 가능
public void processOrder(Order order) {
    order = new Order();  // ⚠️ 파라미터를 실수로 재할당!
    // 원래 order는 사라짐...
}

// final 있으면 컴파일 에러
public void processOrder(final Order order) {
    order = new Order();  // ❌ 컴파일 에러! 미연에 방지
}

3️⃣ 스레드 안전성 향상 🔒

public class Configuration {
    private final String apiKey;       // ✅ 멀티스레드 안전
    private final int maxConnections;  // ✅ 동시 접근해도 안전

    // final 필드는 생성 이후 모든 스레드에서 안전하게 읽을 수 있음
}

왜 안전한가?

  • 생성 후 값이 절대 안 바뀜
  • 동기화 필요 없음
  • 멀티스레드 환경에서 안정적

4️⃣ 코드 가독성 - 설계 의도가 명확 📖

@Entity
public class Member {
    private final Long id;              // 🔑 정체성 (Identity)
    private final String email;         // 🔑 불변 식별자
    private final LocalDateTime joinDate;  // 🔑 변하지 않는 사실

    private String nickname;            // 📝 변경 가능한 정보
    private String profileImage;        // 📝 변경 가능한 정보
    private int point;                  // 📝 계속 변하는 상태
}

👀 한눈에 알 수 있음:

  • final = 핵심 불변 속성
  • private = 변경 가능한 상태

🗄️ JPA Entity에서의 현실

⚠️ JPA에서 final 사용 시 주의사항

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

    @Id
    @GeneratedValue
    private Long id;  // ❌ final 쓰지 않음!

    private String name;
    private String email;

    private final LocalDateTime createdAt = LocalDateTime.now(); // ⭕ 가능
}

🤔 왜 @Id는 final을 안 쓰나요?

1. JPA는 리플렉션으로 필드 값 설정
2. @GeneratedValue는 객체 생성 후 DB가 값 할당
3. final이면 생성 후 값 변경 불가
4. 충돌 발생! 💥

📌 실무 규칙:

케이스 final 사용
@Id @GeneratedValue ❌ 사용 불가
생성 시점 확정 값 ⭕ 사용 가능
비즈니스 키 (이메일 등) ⚠️ 신중하게
변경 가능한 필드 ❌ 사용 안 함

📦 DTO / VO에서는 적극 활용!

✨ 완전 불변 DTO

public class MemberResponseDto {
    private final Long id;
    private final String name;
    private final String email;
    private final LocalDateTime joinDate;

    @Builder
    public MemberResponseDto(Long id, String name, String email, LocalDateTime joinDate) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.joinDate = joinDate;
    }

    // Getter만 있고 Setter 없음!
}

✅ 장점:

  • 완전한 불변 객체
  • 멀티스레드 안전
  • 버그 위험 제로
  • 의도가 명확

💰 VO (Value Object) 예시

public class Money {
    private final BigDecimal amount;
    private final String currency;

    public Money(BigDecimal amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }

    // 값을 바꾸는게 아니라 새 객체를 반환
    public Money add(Money other) {
        return new Money(
            this.amount.add(other.amount),
            this.currency
        );
    }
}

🎯 언제 무엇을 쓸까? (판단 기준표)

private 사용해야 할 때

// 상태가 변하는 것들
private int hp;              // HP는 줄었다 늘었다
private OrderStatus status;  // 주문 상태 변경
private int quantity;        // 수량 변동
private boolean isActive;    // 활성화 토글

// JPA Entity의 대부분 필드
@Entity
public class Product {
    @Id @GeneratedValue
    private Long id;         // JPA가 세팅
    private String name;     // 상품명 변경 가능
    private int price;       // 가격 변동
}

private final 사용해야 할 때

// 객체의 정체성
private final Long id;           // 식별자
private final String code;       // 고유 코드

// 절대 변하면 안 되는 값
private final String email;      // 이메일 (로그인 ID)
private final LocalDate birthDate; // 생일

// 생성 시 확정되는 값
private final LocalDateTime createdAt;
private final String createdBy;

// DTO, VO의 모든 필드
public class UserDto {
    private final String name;
    private final int age;
}

// 의존성 주입받는 필드
public class MemberService {
    private final MemberRepository memberRepository;
    private final EmailService emailService;
}

🧠 실무 판단 공식

💡 이 질문에 답하세요

"이 값이 객체 생명주기 중간에 바뀌면 이상한가?"
답변 사용할 키워드
YES - 바뀌면 이상함 private final
NO - 바뀌어도 됨 private

📋 체크리스트

  • 이 값은 객체의 정체성인가? → final
  • 이 값은 생성 후 절대 안 바뀌나? → final
  • 이 값은 설정값인가? → final
  • JPA의 @GeneratedValue인가? → private
  • 상태 변화를 추적해야 하나? → private

🎨 실전 종합 예제

Case 1: 게임 몬스터 클래스

public class Monster {
    // 불변 속성 (정체성)
    private final String id;           // 고유 ID
    private final String name;         // 이름
    private final MonsterType type;    // 종족
    private final LocalDateTime createdAt;

    // 가변 속성 (상태)
    private int level;                 // 레벨 올라감
    private int hp;                    // HP 변동
    private int exp;                   // 경험치 증가
    private boolean isAlive;           // 생존 여부

    @Builder
    public Monster(String id, String name, MonsterType type) {
        this.id = id;
        this.name = name;
        this.type = type;
        this.createdAt = LocalDateTime.now();
        // 가변 필드는 기본값으로 초기화
        this.level = 1;
        this.hp = 100;
        this.exp = 0;
        this.isAlive = true;
    }
}

Case 2: 주문 도메인

public class Order {
    // 불변 속성
    private final Long orderId;
    private final Long memberId;
    private final LocalDateTime orderDate;
    private final String orderNumber;

    // 가변 속성
    private OrderStatus status;        // 대기 → 진행 → 완료
    private LocalDateTime completedAt; // 완료 시점
    private String cancelReason;       // 취소 사유

    public void complete() {
        this.status = OrderStatus.COMPLETED;
        this.completedAt = LocalDateTime.now();
    }

    public void cancel(String reason) {
        this.status = OrderStatus.CANCELED;
        this.cancelReason = reason;
    }
}

🔥 핵심 정리

💬 한 줄 요약

private final은 설계 선언이다.
“이 값은 절대 바뀌면 안 된다”는 개발자의 의지 표현! 💪


📌 기억해야 할 3가지

  1. final은 컴파일러의 도움을 받는 것 - 실수를 미연에 방지
  2. 불변성은 안정성 - 멀티스레드, 버그 예방, 코드 품질
  3. 설계 의도 명시 - 코드만 봐도 “이건 안 바뀌는구나” 알 수 있음

🎯 실무 팁

// ✅ 좋은 습관
private final MemberRepository memberRepository;  // 의존성은 final
private final String API_KEY = "...";            // 상수는 final
private final LocalDateTime createdAt;            // 생성 시각은 final

// ⚠️ 주의
@Id @GeneratedValue
private Long id;  // JPA는 final 안 됨

// ❌ 피해야 할 패턴
private String name;  // 변하면 안 되는데 final 안 붙임

이제 privateprivate final의 차이를 완벽하게 이해하셨나요? 🎉
“불변으로 만들 수 있다면 불변으로 만들어라” - 이것이 좋은 객체 설계의 시작입니다!