문서 읽는 데 69분 · day15

Day 15 — Enum(열거형)과 어노테이션: 정해진 선택지를 코드로

전체 21강 중 15강 · 자바 기초
난이도 · 입문

안녕하세요! 여러분의 Java 가이드, 홍순구 튜터입니다.

Day 15에 오신 걸 환영해요! 지난 시간 마지막에 제가 두 가지 떡밥을 슬쩍 던져뒀던 거 기억하시나요?

하나는 "정해진 선택지" 이야기였어요. 게시물의 상태는 공개/비공개 둘 중 하나죠. 회원 등급은 일반/관리자/프리미엄 셋 중 하나고요. 이런 "정해진 몇 가지 중 하나" 를 그냥 문자열 "공개" 나 숫자 1 로 다루면 오타도 나고 실수도 생긴다고 했어요. 오늘 그걸 깔끔하고 안전하게 다루는 문법, Enum(열거형) 으로 풀어봅니다.

또 하나는 @ 기호였어요. Day 10부터 우리는 @Override 라는 걸 계속 붙여왔는데, 정작 "이 @ 가 정확히 뭔지" 는 깊게 안 파봤죠. 오늘 그 정체, 어노테이션(annotation) 도 함께 밝혀볼게요.

오늘 우리가 갈 길을 미리 그려볼게요.

  • 먼저 왜 Enum 이 필요한지 부터 짚어요. 문자열·숫자로 상태를 다룰 때 어떤 사고가 나는지 보고요.
  • 가장 단순한 Enum 선언과 기본 기능 을 익히고, == 비교와 switch 로 안전하게 다뤄봐요.
  • Enum 상수마다 데이터(필드) 를 달고, 아예 행동(메서드) 까지 넣어봐요.
  • 그 Enum 을 실제 도메인(Post) 에 끼우고, 상수마다 본문이 다른 전략 Enum 도 만들어봐요.
  • 그다음 어노테이션의 정체 를 밝히고, 자주 쓰는 기본 세 가지를 직접 붙여봐요.
  • 마지막으로 클래스 안의 클래스(정적 중첩 클래스) 까지 살펴보고 마무리해요.

오늘도 "왜 필요한가 → 어떻게 쓰는가 → 이름이 뭔가" 순서로 갈 거예요. 이름부터 외우는 게 아니라, 문제를 먼저 느끼고 나서 해결책에 이름을 붙이는 식이죠. 자, 그럼 첫 질문부터 풀어볼까요?

🎯 학습 목표

  • 문자열·숫자로 상태를 다룰 때의 위험을 이해하고, Enum(열거형) 으로 "정해진 값만 허용" 하게 만들 수 있다
  • Enum 의 기본 기능(values·name·ordinal·valueOf)을 쓰고, == 와 switch 로 안전하게 분기할 수 있다
  • Enum 상수마다 데이터(필드)와 행동(메서드)을 달아, 흩어진 규칙을 한 곳에 모을 수 있다
  • 상수별로 본문이 다른 Enum 으로 "전략 고르기" 를 구현할 수 있다
  • 어노테이션 이 코드에 붙이는 메타데이터(메모지)임을 이해하고, @Override·@Deprecated·@SuppressWarnings 를 구분할 수 있다
  • 클래스 안에 클래스를 두는 정적 중첩 클래스 의 의미와 사용법을 설명할 수 있다

Step 1. Enum은 뭐고, 왜 필요한가

지난 시간에 우리는 좋은 클래스를 짜는 안목을 키웠어요. 오늘은 다시 새 문법으로 돌아오는데, 시작은 아주 흔한 골칫거리 하나예요. 코드를 짜다 보면 자꾸 마주치는 게 있어요. "정해진 선택지" 죠.

게시물 상태를 문자열로 다루면?

인스타 게시물에는 상태가 있어요. 공개, 비공개, 보관됨. 딱 세 가지예요. 이걸 평범하게 문자열로 관리한다고 해봐요.

String status = "PUBLIC";   // 공개 상태

겉보기엔 멀쩡해 보이죠? 그런데 여기엔 함정이 숨어 있어요. 어느 날 동료가 코드를 짜다가 이렇게 적었다고 해봐요.

String status = "PUBIC";    // 앗, L 을 빼먹었어요 (오타)

자, 문제는 자바 컴파일러가 이걸 전혀 못 막는다는 거예요. "PUBIC" 도 그냥 멀쩡한 문자열이거든요. 컴파일은 통과하고, 프로그램도 잘 돌아가요. 그러다 한참 뒤에 "왜 이 게시물만 공개가 안 되지?" 하고 몇 시간을 헤매게 되는 거죠. 오타 하나 때문에요.

그럼 숫자로 다루면?

"문자열이 위험하니 숫자로 하자!" 하는 분도 있어요. 공개는 0, 비공개는 1, 보관됨은 2, 이런 식으로요.

int status = 0;   // 0 = 공개라고 정해두자

이건 더 위험해요. 첫째, 0 이 공개인지 비공개인지 코드만 봐서는 전혀 알 수 없어요. 매번 "0이 뭐였더라?" 하고 메모를 뒤져야 하죠. 둘째, 누가 실수로 이렇게 적어도 컴파일러는 통과시켜요.

int status = 5;   // 5번 상태? 그런 건 없는데...

5 라는 상태는 세상에 없는데도 컴파일러는 아무 말도 안 해요. int 는 그냥 정수니까요. 우리가 "0, 1, 2만 써야지" 라고 마음속으로 약속했을 뿐, 코드에는 그 약속이 적혀 있지 않거든요.

우리가 진짜 원하는 것

여기서 진짜 원하는 게 뭔지 정리해볼게요. "공개·비공개·보관됨 이 세 가지 값만 쓸 수 있게, 나머지는 아예 못 쓰게 막고 싶다." 오타 "PUBIC" 도 못 쓰고, 엉뚱한 숫자 5 도 못 쓰게요. 그림으로 그리면 이런 느낌이에요.

   [ 문자열·숫자로 다룰 때 ]              [ 정해진 값만 허용할 때 ]

   "PUBLIC"  "PRIVATE"  "PUBIC"⚠️         ┌─────────────────────┐
      0         1         5 ⚠️            │  PUBLIC             │
                                          │  PRIVATE     ← 이 셋만!
   아무 글자·숫자나 다 들어감              │  ARCHIVED           │
   컴파일러가 못 막음                      └─────────────────────┘
                                          이 밖의 값은 컴파일 단계에서 거부

오른쪽처럼 "허용된 값의 집합" 을 딱 정해두고, 그 밖의 값은 컴파일러가 거부해주면 얼마나 안전할까요? 오타를 적으면 빨간 줄이 쫙 그어지고, 잘못된 값은 아예 못 넣게요.

자바에는 이렇게 "정해진 상수들의 집합" 을 표현하는 전용 문법이 있어요. 바로 Enum(열거형) 이에요. "열거(enumerate)" 는 "하나하나 늘어놓는다" 는 뜻인데, 가능한 값들을 미리 다 늘어놓고 그 안에서만 고르게 하는 거죠. 다음 Step 에서 직접 만들어봐요.

💡 튜터의 결론

문자열·숫자는 "아무 값이나" 들어올 수 있어서 컴파일러가 실수를 못 막아요. Enum 은 "정해둔 몇 가지" 만 허용해서, 잘못된 값을 컴파일 단계에서 거부해줘요.


Step 2. Enum 정의와 기본 사용

말로만 들으면 막막하니 바로 만들어봐요. 인스타 게시물에 누를 수 있는 "반응" 을 떠올려보세요. 좋아요, 하트, 웃음, 놀람, 슬픔. 가능한 값이 딱 다섯 개로 정해져 있죠. 이걸 Enum 으로 만들어볼게요.

가장 단순한 Enum 선언

// com/instagram/javabasic/enumbasic/ReactionType.java
package com.instagram.javabasic.enumbasic;

public enum ReactionType {
    LIKE,
    LOVE,
    HAHA,
    WOW,
    SAD
}

보세요, 정말 단순하죠? 클래스를 만들 때 쓰던 class 대신 enum 이라고 적고, 그 안에 가능한 값들을 콤마로 늘어놓기만 하면 끝이에요. 이 다섯 개 — LIKE, LOVE, HAHA, WOW, SAD — 를 enum 상수 라고 불러요. 관례상 대문자로 적어요(상수니까요).

이제 이 ReactionType 은 어엿한 하나의 타입이에요. int, String 처럼 변수의 타입으로 쓸 수 있죠.

ReactionType myReaction = ReactionType.LOVE;

여기서 마법이 일어나요. 만약 ReactionType.LOVEE 처럼 오타를 내면, 컴파일러가 즉시 빨간 줄을 그어요. "그런 상수 없는데?" 하고요. Step 1 에서 그렇게 원하던 게 바로 이거예요. 정해진 값이 아니면 아예 못 쓰게 막아주는 거죠.

Enum 이 기본으로 주는 기능들

Enum 을 만들면 자바가 공짜로 끼워주는 편리한 기능이 몇 개 있어요. 연습장 클래스 ReactionDemo 에서 하나를 살펴볼게요.

// com/instagram/javabasic/enumbasic/ReactionDemo.java
package com.instagram.javabasic.enumbasic;

public class ReactionDemo {

    // 전체 반응 종류가 몇 개인지 — values() 가 모든 상수를 배열로 돌려줘요.
    public static int howManyTypes() {
        return ReactionType.values().length;
    }
}

ReactionType.values() 를 부르면, 우리가 선언한 다섯 상수가 통째로 배열로 돌아와요. [LIKE, LOVE, HAHA, WOW, SAD] 이렇게요. 그래서 .length 를 붙이면 종류가 몇 개인지(5개) 바로 알 수 있죠. 상수를 추가하거나 빼면 이 숫자도 자동으로 따라오니, 일일이 세지 않아도 돼요.

이 밖에도 Enum 상수에 점을 찍으면 쓸 수 있는 기능이 몇 가지 더 있어요. 처음 보는 영어가 많으니 표로 한 번에 정리해둘게요.

기능 하는 일 예시 결과
values() 모든 상수를 배열로 돌려줌 [LIKE, LOVE, HAHA, WOW, SAD]
LOVE.name() 상수의 이름을 문자열로 "LOVE"
LOVE.ordinal() 상수가 몇 번째로 선언됐는지(0부터) 1
ReactionType.valueOf("LOVE") 문자열로 상수를 찾아줌 ReactionType.LOVE

name() 은 상수 이름을 그대로 문자열로 주고, ordinal() 은 선언된 순서를 0부터 알려줘요(LIKE 가 0, LOVE 가 1). valueOf() 는 반대로 "LOVE" 라는 문자열을 받아서 LOVE 상수를 찾아주는데, 만약 없는 이름("LOVEE")을 넣으면 에러를 내요. 외부에서 들어온 문자열을 Enum 으로 바꿀 때 유용하죠.

🙋 학생 질문 — "튜터님, ordinal() 로 나온 숫자를 DB에 저장해서 써도 되나요?"

마음은 알겠지만 그건 위험해요! ordinal() 은 "지금 선언된 순서" 라서, 나중에 상수 순서를 바꾸거나 중간에 새 상수를 끼워 넣으면 숫자가 와르르 밀려버려요. 예를 들어 LIKE(0) 앞에 새 상수를 추가하면 LIKE 가 갑자기 1번이 되죠. 이미 0이라고 저장해둔 데이터가 다 엉켜요. 그래서 ordinal() 숫자에 의존하는 건 피하고, 꼭 저장해야 한다면 name()(상수 이름 문자열)을 쓰는 게 훨씬 안전해요. 지금은 "이런 기능이 있구나" 정도만 알고 넘어가도 충분해요.

💡 튜터의 결론

enum 키워드 뒤에 가능한 값을 콤마로 늘어놓으면 그게 곧 새 타입이에요. values() 로 전체를, name()·ordinal()·valueOf() 로 이름과 순서를 다룰 수 있어요.


Step 3. Enum의 == 비교와 switch

Enum 을 만들었으니 이제 다뤄볼 차례예요. 두 반응이 같은지 비교하고, 반응에 따라 다른 일을 하게 분기해볼게요.

== 로 안전하게 비교하기

Day 8~10 을 떠올려보세요. 문자열을 비교할 때 우리는 꼭 .equals() 를 써야 한다고 했죠? == 로 비교하면 "내용이 같은가" 가 아니라 "메모리 주소가 같은가" 를 보는 거라 엉뚱한 결과가 나올 수 있었어요. 그래서 name1.equals(name2) 처럼 썼었죠.

그런데 Enum 은 달라요. Enum 상수는 세상에 단 하나씩만 존재해요. 무슨 말이냐면, 코드 어디에서 ReactionType.LOVE 를 쓰든 그건 전부 똑같은 하나의 객체를 가리켜요. 마치 우리 동네에 "서울시청" 이 딱 하나만 있는 것처럼요. 누가 "서울시청 가자" 고 해도 다 같은 그 건물이죠.

그래서 Enum 은 == 로 비교해도 완벽하게 안전해요.

// com/instagram/javabasic/enumbasic/ReactionDemo.java

// enum 상수는 세상에 단 하나씩만 존재해요(같은 LOVE 는 어디서 써도 같은 객체).
// 그래서 equals 가 아니라 == 로 안전하게 비교할 수 있어요.
public static boolean isSame(ReactionType a, ReactionType b) {
    return a == b;
}

문자열과 비교하면 차이가 또렷해요.

   String 비교                          Enum 비교
 ┌────────────────────┐              ┌────────────────────┐
 │ "LOVE" 라는 문자열   │              │ ReactionType.LOVE   │
 │ 메모리에 여러 개     │              │ 세상에 단 하나뿐     │
 │ → == 위험, equals 써야│              │ → == 로도 안전!      │
 └────────────────────┘              └────────────────────┘

문자열은 같은 "LOVE" 라도 메모리에 여러 벌 생길 수 있어서 == 가 위험했지만, Enum 상수는 하나뿐이라 == 가 오히려 더 명확하고 빨라요.

switch 로 깔끔하게 분기하기

이번엔 반응 종류에 따라 다른 이모지를 보여줘볼게요. if-else 를 다섯 번 쓸 수도 있지만, 이렇게 "정해진 값 중 하나" 일 때는 switch 가 훨씬 깔끔해요.

// com/instagram/javabasic/enumbasic/ReactionDemo.java

// 반응 종류를 이모지로 바꿔요.
// enum 을 switch 에 넣으면 case 에 상수 이름만 적으면 돼요(ReactionType. 안 붙여도 OK).
// 다섯 상수를 모두 다뤘기 때문에 default 가 없어도 컴파일러가 통과시켜줘요.
public static String emojiOf(ReactionType type) {
    return switch (type) {
        case LIKE -> "👍";
        case LOVE -> "❤️";
        case HAHA -> "😂";
        case WOW  -> "😮";
        case SAD  -> "😢";
    };
}

이 코드에 재미있는 점이 두 가지 있어요.

첫째, caseReactionType.LIKE 가 아니라 그냥 LIKE 라고만 적었죠? Enum 을 switch 에 넣으면 자바가 "아, 이건 ReactionType 종류구나" 하고 알아채서, 앞에 타입 이름을 붙이지 않아도 돼요. 코드가 한결 짧아지죠.

둘째, default 가 하나도 없어요. 보통 switch 에는 "나머지 경우" 를 처리하는 default 를 넣잖아요? 그런데 여기선 다섯 상수를 빠짐없이 다 다뤘기 때문에, 컴파일러가 "더 나올 경우가 없네" 하고 알아서 통과시켜줘요. Enum 은 가능한 값이 정해져 있으니 가능한 일이에요.

이게 또 하나의 숨은 안전장치예요. 만약 나중에 ReactionTypeANGRY(화남) 같은 상수를 추가하면, 이 switch 에 빨간 줄이 떠요. "ANGRY 는 어떻게 처리할 거야?" 하고 컴파일러가 콕 집어주는 거죠. 문자열로 분기했다면 이런 알림은 절대 못 받았을 거예요.

💡 튜터의 결론

Enum 상수는 하나뿐이라 == 로 안전하게 비교해요(문자열은 .equals 가 필요했죠). switch 에 넣으면 case 에 상수 이름만 적으면 되고, 모든 상수를 다루면 default 없이도 동작해요.


Step 4. Enum에 필드와 생성자

지금까지의 Enum 은 그냥 이름표만 가진 상수였어요. 그런데 Enum 의 진짜 힘은 여기서부터예요. 상수마다 자기만의 데이터를 달 수 있거든요.

등급마다 다른 데이터를 들고 다니기

회원 등급을 떠올려보세요. 일반, 프리미엄, 관리자 세 가지죠. 그런데 등급마다 "한글 이름" 도 다르고, 추천 점수에 더해줄 "보너스 점수" 도 달라요. 일반은 보너스가 없고, 프리미엄은 30점, 관리자는 50점이라고 해볼게요.

// com/instagram/javabasic/enumbasic/Grade.java
package com.instagram.javabasic.enumbasic;

public enum Grade {
    NORMAL("일반", 0),
    PREMIUM("프리미엄", 30),
    ADMIN("관리자", 50);

    // 상수마다 채워지는 데이터 — 한 번 정해지면 안 바뀌니 final 로 둬요
    private final String displayName;
    private final int bonusScore;

    // enum 의 생성자는 항상 보이지 않게 private 이에요(밖에서 new Grade(...) 불가).
    // 위에 적은 NORMAL("일반", 0) 의 괄호 값이 여기로 전달돼요.
    Grade(String displayName, int bonusScore) {
        this.displayName = displayName;
        this.bonusScore = bonusScore;
    }

    public String getDisplayName() {
        return displayName;
    }

    public int getBonusScore() {
        return bonusScore;
    }
}

처음 보면 좀 낯설죠? 천천히 뜯어볼게요.

상수 이름 옆 괄호를 보세요. NORMAL("일반", 0) 처럼요. 이 괄호 안의 값들이 생성자로 전달돼요. NORMAL 을 만들 때 "일반"0 이 생성자에 넘어가서, displayName 에는 "일반" 이, bonusScore 에는 0 이 담기는 거죠. ADMIN"관리자"50 을 받고요. 그림으로 보면 이래요.

   NORMAL("일반", 0)  ──┐
   PREMIUM("프리미엄", 30) ─┼──→  Grade(String displayName, int bonusScore)
   ADMIN("관리자", 50) ──┘            │            │
                                     ↓            ↓
                              displayName    bonusScore
                              ("관리자")        (50)

각 상수가 자기만의 데이터를 들고 태어나는 거예요. ADMIN.getBonusScore() 를 부르면 50 이, PREMIUM.getBonusScore() 를 부르면 30 이 나오죠.

왜 생성자가 private 일까?

코드에서 생성자 앞에 아무 키워드도 없는 게 보이시나요? 사실 Enum 의 생성자는 무조건 보이지 않는(private) 생성자 예요. 우리가 public 이라고 적어도 소용없어요. 자바가 강제로 막아두거든요.

왜 그럴까요? Enum 의 핵심은 "정해진 상수만 존재한다" 였잖아요. 그런데 만약 외부에서 new Grade("해커", 9999) 같은 걸 막 만들 수 있다면, 그 약속이 깨지겠죠? 그래서 자바는 "생성자는 Enum 안에서만, 상수를 선언할 때만 쓸 수 있다" 고 막아둔 거예요. NORMAL, PREMIUM, ADMIN 외의 다른 Grade 는 그 누구도 만들 수 없어요.

흩어졌던 등급 차이를 한곳에 모았어요

여기서 잠깐, Day 10·11 을 떠올려볼까요? 그때 우리는 AdminMemberPremiumMember 라는 자식 클래스를 만들었어요. AdminMember 는 점수 보너스 +50, PremiumMember+30 을 줬었죠. 기억나시나요?

그때는 등급별 차이가 자식 클래스마다 흩어져 있었어요. 보너스 점수를 바꾸려면 AdminMember 파일도 열고, PremiumMember 파일도 열어야 했죠. 그런데 지금 Grade 를 보세요. 일반·프리미엄·관리자의 차이가 이 Enum 하나에 깔끔하게 모여 있어요. NORMAL("일반", 0), PREMIUM("프리미엄", 30), ADMIN("관리자", 50). 한눈에 보이죠?

등급 보너스를 바꾸고 싶으면 이 파일 한 곳만 고치면 돼요. 여기저기 흩어진 자식 클래스를 다 뒤질 필요가 없는 거예요. 이게 Enum 에 데이터를 다는 진짜 이유예요.

💡 튜터의 결론

Enum 상수 옆 괄호에 값을 적으면, 그 값이 생성자로 전달돼 상수마다 다른 데이터를 들고 다녀요. 자식 클래스마다 흩어졌던 차이를 Enum 하나에 모으면 관리가 훨씬 쉬워져요.


Step 5. Enum에 메서드 추가

데이터를 달았으니 한 걸음 더 나가볼게요. Enum 안에 데이터뿐 아니라 행동(메서드) 까지 넣을 수 있어요.

상태에 관한 규칙을 한곳에

게시물 상태로 돌아가봐요. Step 1 에서 고민했던 공개/비공개/보관됨이요. 그런데 상태마다 "공유할 수 있는가?", "수정할 수 있는가?" 같은 규칙이 다르죠. 공개 글은 공유되지만 비공개·보관 글은 공유되면 안 되고, 보관된 글은 수정도 못 하게 하고 싶다고 해봐요. 이 규칙들을 Enum 안에 직접 넣어볼게요.

// com/instagram/javabasic/domain/post/PostStatus.java
package com.instagram.javabasic.domain.post;

public enum PostStatus {
    PUBLIC("공개", true),
    PRIVATE("비공개", false),
    ARCHIVED("보관됨", false);

    private final String displayName;
    private final boolean shareable;

    PostStatus(String displayName, boolean shareable) {
        this.displayName = displayName;
        this.shareable = shareable;
    }

    public String getDisplayName() {
        return displayName;
    }

    // 공유할 수 있는 상태인지 — 생성자에서 정해둔 값을 그대로 알려줘요.
    public boolean canShare() {
        return shareable;
    }

    // 수정할 수 있는 상태인지 — 보관된 글만 수정 불가예요.
    // enum 메서드 안에서 this 는 "지금 이 상수" 를 가리켜요(예: PUBLIC).
    public boolean isEditable() {
        return this != ARCHIVED;
    }
}

Step 4 의 Grade 와 구조는 똑같아요. 상수마다 한글 이름과 shareable(공유 가능 여부)을 데이터로 들고 있죠. 그런데 여기에 canShare()isEditable() 이라는 메서드 가 더 붙었어요.

enum 메서드 안의 this

isEditable() 을 자세히 봐요. return this != ARCHIVED; 라고 적혀 있죠. 여기서 this 가 뭘 가리킬까요?

Day 9 에서 배운 this 를 떠올려보세요. 인스턴스 메서드 안에서 this 는 "지금 이 메서드를 부른 그 객체" 를 가리켰죠. Enum 도 똑같아요. enum 메서드 안의 this"지금 이 상수" 를 가리켜요. 그러니까 PUBLIC.isEditable() 을 부르면 그 안의 thisPUBLIC 이고, ARCHIVED.isEditable() 을 부르면 thisARCHIVED 인 거죠.

return this != ARCHIVED; 를 풀어 읽으면 "지금 이 상수가 보관됨(ARCHIVED)이 아니면 수정 가능" 이라는 뜻이에요. PUBLIC.isEditable()true(공개는 보관됨이 아니니까), ARCHIVED.isEditable()false 가 되죠.

   PUBLIC.isEditable()    →  this(PUBLIC)   != ARCHIVED  →  true  (수정 가능)
   PRIVATE.isEditable()   →  this(PRIVATE)  != ARCHIVED  →  true  (수정 가능)
   ARCHIVED.isEditable()  →  this(ARCHIVED) != ARCHIVED  →  false (수정 불가)

규칙을 한곳에 모으면 뭐가 좋나요?

상태 판단 규칙을 PostStatus 안에 모아두면, 이걸 쓰는 쪽 코드가 정말 깔끔해져요. 게시물이 공유 가능한지 알고 싶으면 그냥 status.canShare() 라고 물으면 되거든요. "공개면 되고, 비공개면 안 되고, 보관됨이면 안 되고..." 하는 복잡한 if 를 쓰는 쪽마다 반복할 필요가 없어요.

만약 나중에 "보관된 글도 공유 가능하게 바꾸자" 는 요청이 오면, ARCHIVED("보관됨", false)falsetrue 로 바꾸기만 하면 돼요. 이 규칙을 쓰는 코드가 열 군데 백 군데여도, 고칠 곳은 이 한 곳뿐이에요. 규칙이 한 곳에 모여 있으니까요.

💡 튜터의 결론

Enum 안에 데이터뿐 아니라 메서드도 넣을 수 있어요. enum 메서드 안의 this 는 "지금 이 상수" 를 가리켜요. 상태에 관한 규칙을 Enum 한곳에 모으면, 쓰는 쪽 코드가 단순해지고 고칠 곳도 한 군데로 줄어요.


Step 6. Enum을 도메인에 적용

Enum 에 데이터와 메서드를 다는 법을 익혔으니, 이제 진짜 도메인 클래스에 끼워봐요. 지난 시간들에 만들어온 Post 클래스에 방금 만든 PostStatus 를 달아볼게요.

Post에 상태 필드 달기

Post 클래스에 PostStatus status 라는 필드를 추가했어요. 게시물이 자기 상태를 직접 들고 다니는 거죠. 클래스 전체는 좀 기니까, 오늘 새로 들어온 부분만 발췌해서 볼게요.

// com/instagram/javabasic/domain/post/Post.java
public class Post {

    // 게시물을 이루는 정보 — private 으로 숨겨요
    private String content;       // 게시물 내용
    private String authorName;    // 작성자 이름
    private int likeCount;        // 좋아요 수
    private PostStatus status;    // 게시물 상태(공개/비공개/보관됨) — enum 으로 표현

    // 매개변수 생성자 — 세 가지 정보를 받아요. 상태는 기본값(공개)으로 둬요.
    public Post(String content, String authorName, int likeCount) {
        this.content = content;
        this.authorName = authorName;
        this.likeCount = likeCount;
        this.status = PostStatus.PUBLIC;
    }

    // 이 게시물을 지금 공유할 수 있는지 — 판단 규칙은 상태(enum)에게 맡겨요.
    // Post 는 "공유 가능?" 만 묻고, 진짜 규칙은 PostStatus 안에 모여 있어요.
    public boolean canBeShared() {
        return status.canShare();
    }
}

status 필드의 타입이 PostStatus 인 게 핵심이에요. Step 1 에서 봤던 문자열이나 숫자가 아니라, 우리가 만든 Enum 타입이죠. 그래서 status 에는 PUBLIC, PRIVATE, ARCHIVED 셋 중 하나만 들어갈 수 있어요. 오타 난 "PUBIC" 이나 엉뚱한 5 는 애초에 못 들어오는 거예요. 게시물을 만들 때 상태는 기본값 공개(PostStatus.PUBLIC)로 시작하게 해뒀고요.

묻기만 하는 Post, 규칙을 가진 PostStatus

canBeShared() 메서드를 주목해주세요. 이게 오늘의 진짜 포인트예요.

public boolean canBeShared() {
    return status.canShare();
}

Post 는 "나 지금 공유돼도 돼?" 라고 묻기만 해요. 진짜 판단은 status.canShare() 에게 넘기죠. 공유 가능 여부를 따지는 규칙(공개면 OK, 비공개·보관이면 안 됨)은 전부 PostStatus 안에 있고, Post 는 그걸 외워둘 필요가 없어요. 그냥 자기 상태에게 물어보기만 하면 되거든요.

   Post.canBeShared()
        │  "나 공유돼도 돼?" 하고 묻기만 함
        ↓
   status.canShare()   ← 진짜 규칙은 PostStatus 안에
        │
        ↓
   true / false

이거 지난 시간에 배운 SRP(단일 책임 원칙)와 정확히 이어져요. "게시물의 데이터를 들고 있는 일" 은 Post 의 책임이고, "상태에 따른 규칙을 판단하는 일" 은 PostStatus 의 책임인 거죠. 책임이 깔끔하게 나뉘어 있어요. 만약 공유 규칙이 바뀌어도 Post 는 한 글자도 안 고쳐도 돼요. PostStatus 만 손보면 끝이죠.

💡 튜터의 결론

필드 타입을 Enum 으로 두면 "정해진 값만" 들어올 수 있어 안전해요. Post 는 규칙을 외우지 않고 상태에게 묻기만 하고, 진짜 규칙은 PostStatus 가 가져요. 이게 지난 시간 배운 책임 분리(SRP)예요.


Step 7. Enum으로 전략 고르기

오늘 Enum 의 마지막이자 가장 강력한 사용법이에요. 지금까지는 모든 상수가 같은 메서드 본문을 공유했죠(canShare() 는 데이터만 다르고 코드는 같았어요). 그런데 상수마다 메서드 본문 자체를 다르게 만들 수도 있어요.

피드를 어떤 기준으로 정렬할까?

인스타 피드를 떠올려보세요. 게시물을 보여줄 때 정렬 기준이 여러 가지죠. 최신순, 인기순, 급상승순. 같은 게시물 목록이라도 어떤 정책으로 점수를 매기느냐에 따라 순서가 완전히 달라져요.

  • 최신순: 올라온 지 얼마 안 됐을수록 높은 점수
  • 인기순: 좋아요가 많을수록 높은 점수
  • 급상승순: 좋아요를 크게 반영하되, 오래된 글은 점수를 깎음

상수는 셋으로 정해져 있는데, 점수 계산 방법은 셋이 다 달라요. 이럴 때 Enum 상수마다 메서드 본문을 따로 쓸 수 있어요.

// com/instagram/javabasic/enumbasic/RankingPolicy.java
package com.instagram.javabasic.enumbasic;

public enum RankingPolicy {

    // 최신순 — 올라온 지 얼마 안 됐을수록(시간이 적을수록) 높은 점수
    LATEST {
        @Override
        public int score(int likeCount, int ageHours) {
            return -ageHours;
        }
    },

    // 인기순 — 좋아요가 많을수록 높은 점수
    POPULAR {
        @Override
        public int score(int likeCount, int ageHours) {
            return likeCount;
        }
    },

    // 급상승순 — 좋아요는 크게 반영하되, 오래된 글은 깎아요
    TRENDING {
        @Override
        public int score(int likeCount, int ageHours) {
            return likeCount * 2 - ageHours;
        }
    };

    // 본문 없는 약속(추상 메서드) — 각 상수가 위에서 자기 방식대로 채웠어요.
    public abstract int score(int likeCount, int ageHours);
}

Day 12·11 이 여기서 다시 만나요

이 코드, 어디서 본 느낌이 들지 않나요? 맨 아래 public abstract int score(...) 를 보세요. abstract — Day 12 에서 배운 추상 메서드예요! "본문 없는 약속" 이죠. score 라는 이름과 모양만 정해두고, 실제 계산은 안 적혀 있어요.

그럼 본문은 누가 채울까요? 상수들이 채워요. LATEST 옆 중괄호 { } 안에 자기만의 score 본문이 들어 있죠. LATEST-ageHours(오래될수록 점수 낮음), POPULARlikeCount(좋아요 그대로), TRENDINGlikeCount * 2 - ageHours(좋아요 두 배에서 시간 빼기). 상수마다 같은 약속을 자기 방식대로 채운 거예요.

이건 Day 11 에서 배운 동적 디스패치와도 닿아 있어요. 같은 score() 를 부르는데, 어느 상수가 부르느냐에 따라 다른 본문이 실행되죠.

   같은 score(좋아요 10, 5시간 전) 을 불러도...

   LATEST.score(10, 5)    →  -5         (시간만 보고 깎음)
   POPULAR.score(10, 5)   →  10         (좋아요만 봄)
   TRENDING.score(10, 5)  →  10*2 - 5 = 15  (둘 다 봄)

   상수마다 본문이 달라서 결과도 달라요!

같은 입력(좋아요 10개, 5시간 전 글)을 넣어도 정책마다 점수가 완전히 다르죠. 정렬할 때 어떤 정책을 고르느냐에 따라 피드 순서가 확 바뀌는 거예요.

왜 이렇게 쓸까요?

"그냥 switch 로 정책마다 다르게 계산하면 안 되나요?" 싶을 수 있어요. 물론 돼요. 하지만 이렇게 Enum 상수에 본문을 직접 담으면, 정책 하나하나가 자기 계산법을 스스로 들고 있게 돼요. 새 정책(예: "팔로워 기반 추천순")을 추가하고 싶으면 상수 하나만 더 적으면 되고, 그 안에 본문을 채우면 끝이죠. 계산법이 정책 옆에 딱 붙어 있어서 어디를 봐야 할지도 명확하고요.

💡 튜터의 결론

Enum 상수마다 메서드 본문을 다르게 쓸 수 있어요(상수별 메서드 구현). 맨 아래 추상 메서드로 약속을 정하고, 각 상수가 중괄호 안에서 자기 본문을 채워요. Day 12 추상 메서드와 Day 11 동적 디스패치가 여기서 한 번에 만나요.


Step 8. 어노테이션이란 + 기본 3종

자, 이제 Enum 을 잠시 내려놓고 오늘 두 번째 떡밥을 회수할 시간이에요. 바로 @ 기호의 정체예요.

@ 기호, 사실 너희 정체가 뭐니?

Day 10 에서 상속을 배울 때 우리는 @Override 라는 걸 처음 붙였어요. 그 뒤로도 메서드를 오버라이딩할 때마다 @Override 를 적어왔죠. 그런데 정작 이 @ 가 뭔지는 안 파봤어요.

@ 기호로 시작하는 걸 어노테이션(annotation) 이라고 불러요. 우리말로 풀면 "주석", "메모" 정도예요. 어노테이션은 한마디로 코드에 붙이는 메모지 예요.

비유를 들어볼게요. 책을 읽다가 중요한 페이지에 포스트잇을 붙이잖아요? "여기 중요!", "이 부분 다시 볼 것" 이렇게요. 그 포스트잇이 책 내용을 바꾸는 건 아니에요. 그냥 나중에 볼 사람에게 정보를 주는 메모일 뿐이죠. 어노테이션이 딱 그거예요. 코드에 붙이지만, 코드 자체를 실행하진 않아요. 대신 컴파일러나 개발 도구에게 "이 코드는 이런 거야" 하고 정보를 주죠.

@Override 도 그래요. "이 메서드는 부모 걸 새로 정의한 거야" 라는 메모예요. 이 메모 덕분에 컴파일러가 "정말 부모에 그런 메서드가 있나?" 확인해주고, 오타로 엉뚱한 메서드를 만들면 빨간 줄을 그어줬던 거죠.

자주 쓰는 기본 어노테이션 세 가지

@Override 말고도 자바가 기본으로 주는 어노테이션이 몇 개 있어요. 자주 보는 두 가지를 직접 붙여볼게요.

// com/instagram/javabasic/annotationbasic/AnnotationDemo.java
package com.instagram.javabasic.annotationbasic;

public class AnnotationDemo {

    // @Deprecated — "이건 이제 안 쓰는 게 좋아요" 라는 표시예요.
    // 지우진 않았지만, 이 메서드를 쓰면 IDE 가 취소선으로 경고해줘요.
    @Deprecated
    public String oldShare() {
        return "예전 방식으로 공유했어요";
    }

    // 새로 권장하는 방식 — 같은 일을 하지만 이쪽을 쓰라는 뜻이에요.
    public String newShare() {
        return "새 방식으로 공유했어요";
    }

    // @SuppressWarnings — "이 경고는 알고 있으니 조용히 해줘" 라는 표시예요.
    // 여기선 안 쓰는 지역 변수 경고("unused")를 일부러 눌렀어요.
    @SuppressWarnings("unused")
    public String quietMethod() {
        int draftCount = 0;
        return "경고 없이 처리했어요";
    }
}

@Deprecated(디프리케이티드)는 "이제 그만 쓰는 게 좋다" 는 표시예요. 옛날 메서드를 당장 지우긴 어렵지만(쓰는 데가 있을 수 있으니), 새로 짤 때는 다른 걸 쓰라고 알려주고 싶을 때 붙여요. 이걸 붙이면 IDE 가 그 메서드 이름에 취소선(oldShare)을 그어줘서, "어, 이거 안 쓰는 거구나" 하고 바로 알 수 있죠.

@SuppressWarnings(서프레스워닝스)는 "이 경고는 내가 알고 있으니 조용히 해줘" 라는 표시예요. IDE 가 가끔 "이 변수 안 쓰는데요?" 같은 경고를 노란 줄로 띄우는데, 일부러 그렇게 둔 거라면 이 어노테이션으로 경고를 눌러둘 수 있어요. 괄호 안의 "unused" 는 "안 쓰는 변수에 대한 경고" 를 끄겠다는 뜻이고요.

세 어노테이션을 표로 정리하면 이래요.

어노테이션 의미 누가 읽나
@Override "이건 부모 메서드를 새로 정의한 거야" 컴파일러 (오타 검사)
@Deprecated "이건 이제 안 쓰는 게 좋아" 개발자·IDE (취소선)
@SuppressWarnings "이 경고는 알고 있으니 조용히" IDE (경고 끔)
🙋 학생 질문 — "튜터님, 어노테이션을 빼먹으면 코드가 안 돌아가나요?"

대부분 잘 돌아가요! 어노테이션은 코드를 실행하는 명령이 아니라 "메모" 라서, 빼먹어도 프로그램 동작 자체는 보통 그대로예요. 예를 들어 @Override 를 안 붙여도 오버라이딩은 되긴 해요. 다만 그 경우 컴파일러의 오타 검사 같은 "안전장치" 를 못 받게 되죠. 그러니까 어노테이션은 "없으면 안 도는 것" 이 아니라 "있으면 더 안전하고 친절해지는 것" 으로 생각하면 편해요. @ 가 붙은 메모가 보이면 "아, 이건 컴파일러나 도구에게 주는 정보구나" 하고 읽으면 돼요.

💡 튜터의 결론

어노테이션은 코드에 붙이는 메모지예요. @ 로 시작하고, 코드를 실행하진 않지만 컴파일러·도구에게 정보를 줘요. 우리가 계속 써온 @Override, 그리고 @Deprecated·@SuppressWarnings 가 대표적이에요.


Step 9. 클래스 안의 클래스: 정적 중첩 클래스

오늘의 마지막 주제예요. 살짝 곁가지 같지만, 코드를 읽다 보면 분명 마주칠 패턴이라 한 번 짚고 갈게요. 바로 클래스 안에 또 클래스를 넣는 거예요.

왜 클래스 안에 클래스를 둘까?

프로필 화면을 떠올려보세요. 사용자 이름이 있고, 그 옆에 "게시물 42 · 팔로워 1240" 같은 통계가 붙어 있죠. 이 통계(게시물 수, 팔로워 수)를 묶어서 다루고 싶어요.

물론 Stats 라는 클래스를 따로 파일로 만들 수도 있어요. 그런데 이 Stats 는 사실 UserProfile(프로필) 안에서만 의미가 있는 작은 묶음이에요. 다른 데서는 쓸 일이 거의 없죠. 이렇게 "어떤 클래스 안에서만 쓰이는 작은 묶음" 은 굳이 파일을 따로 만들기보다, 그 클래스 안에 넣어두는 게 깔끔해요. 이걸 중첩 클래스(nested class) 라고 불러요. 클래스가 클래스 안에 둥지를 틀었다는 뜻이에요.

// com/instagram/javabasic/innerclass/UserProfile.java
package com.instagram.javabasic.innerclass;

public class UserProfile {

    private final String username;
    private final Stats stats;

    public UserProfile(String username, Stats stats) {
        this.username = username;
        this.stats = stats;
    }

    public String getUsername() {
        return username;
    }

    public Stats getStats() {
        return stats;
    }

    // 정적 중첩 클래스 — 게시물 수와 팔로워 수를 한 묶음으로 담아요.
    // 바깥(UserProfile)의 인스턴스가 없어도 독립적으로 만들 수 있어요.
    public static class Stats {
        private final int postCount;
        private final int followerCount;

        public Stats(int postCount, int followerCount) {
            this.postCount = postCount;
            this.followerCount = followerCount;
        }

        public int getPostCount() {
            return postCount;
        }

        public int getFollowerCount() {
            return followerCount;
        }

        public String summary() {
            return "게시물 " + postCount + " · 팔로워 " + followerCount;
        }
    }
}

static 이 붙으면 바깥 객체 없이 만들 수 있어요

Stats 클래스 앞에 static 이 붙은 게 보이시죠? 이게 핵심이에요.

static 을 붙인 중첩 클래스를 정적 중첩 클래스(static nested class) 라고 불러요. static 이 붙으면 바깥 클래스(UserProfile)의 객체를 먼저 만들지 않아도, Stats 를 독립적으로 만들 수 있어요. 이렇게요.

UserProfile.Stats stats = new UserProfile.Stats(42, 1240);

UserProfile.Stats 라고 점을 찍어서 "UserProfile 안에 있는 Stats" 라고 콕 집어주고, 바로 new 로 만들죠. UserProfile 객체를 먼저 만들 필요가 없어요. 구조를 그림으로 보면 이래요.

   UserProfile (바깥 클래스)
   ┌─────────────────────────────────┐
   │  username                        │
   │  stats                           │
   │                                  │
   │  ┌────────────────────────────┐  │
   │  │ Stats (정적 중첩 클래스)     │  │  ← UserProfile.Stats 로
   │  │  postCount                  │  │     바깥 객체 없이 생성 가능
   │  │  followerCount              │  │
   │  └────────────────────────────┘  │
   └─────────────────────────────────┘

Stats 의 두 필드는 final 이라 한 번 정해지면 안 바뀌고요(지난 시간에 배운 불변 객체죠!). summary() 를 부르면 "게시물 42 · 팔로워 1240" 같은 문구를 만들어줘요. 프로필 화면에 통계를 보여줄 때 딱 좋겠죠.

static 을 안 붙이면요?

static 을 빼고 그냥 중첩 클래스를 만들 수도 있어요. 그걸 내부 클래스(inner class) 라고 부르는데, 이건 바깥 객체에 딱 붙어 있어서 바깥 객체 없이는 못 만들어요. 동작 방식이 꽤 달라서 헷갈리기 쉬운데, 지금 당장은 안 다뤄도 괜찮아요. 나중에 필요할 때 더 깊게 볼게요. 오늘은 "작은 묶음은 static 중첩 클래스로 안에 넣을 수 있다" 정도만 챙기면 충분해요.

💡 튜터의 결론

어떤 클래스 안에서만 쓰이는 작은 묶음은 중첩 클래스로 안에 넣으면 깔끔해요. static 을 붙이면 바깥 객체 없이 Outer.Inner 로 바로 만들 수 있어요(정적 중첩 클래스).


마무리

오늘 정말 알찬 하루였어요. 새 문법 두 개(Enum, 어노테이션)에 곁가지 하나(중첩 클래스)까지 챙겼죠. 머릿속에 잘 남도록 Step 별로 한 줄씩 정리해볼게요.

  1. Enum 의 필요성 — 문자열·숫자로 상태를 다루면 오타·엉뚱한 값을 컴파일러가 못 막아요. Enum 은 정해진 값만 허용해요.
  2. Enum 기본enum 뒤에 값을 늘어놓으면 새 타입이에요. values()·name()·ordinal()·valueOf() 로 다뤄요.
  3. == 와 switch — Enum 상수는 하나뿐이라 == 로 안전하게 비교하고, switch 에선 모든 상수를 다루면 default 없이도 돼요.
  4. 필드와 생성자 — 상수 옆 괄호 값이 생성자로 전달돼 상수마다 다른 데이터를 들고 다녀요. 생성자는 항상 private 이에요.
  5. 메서드 추가 — Enum 안에 행동도 넣을 수 있고, 메서드 안 this 는 "지금 이 상수" 예요. 규칙을 한곳에 모아요.
  6. 도메인 적용Post 는 묻기만 하고 규칙은 PostStatus 가 가져요. 지난 시간 배운 책임 분리(SRP)예요.
  7. 전략 고르기 — 상수마다 메서드 본문을 다르게(추상 메서드 + 상수별 구현). 같은 입력에 정책마다 다른 점수.
  8. 어노테이션 — 코드에 붙이는 메모지예요. @Override·@Deprecated·@SuppressWarnings.
  9. 정적 중첩 클래스 — 작은 묶음은 클래스 안에 넣고, static 이면 Outer.Inner 로 바깥 객체 없이 만들어요.

다음 시간 예고

여러분, 지금까지 우리가 만들어온 걸 한번 쭉 떠올려볼까요? Member(회원, 등급도 있죠), Post(게시물, 이제 상태도 가졌어요), Comment(불변 댓글), 그리고 Shareable·Commentable 같은 역할 인터페이스들, 오늘 만든 여러 Enum 까지요. 부품을 정말 많이 모았어요.

다음 시간엔 이 부품들을 한데 모아서 "인스타그램 도메인 모델 종합 설계" 를 해봐요. 지금까지 따로따로 배운 클래스·상속·인터페이스·Enum 을 어떻게 하나의 그림으로 엮어 진짜 인스타그램의 "두뇌" 를 만드는지, 큰 그림을 그려볼 거예요. 흩어져 있던 조각들이 하나로 맞춰지는 순간이라 꽤 짜릿할 거예요.

오늘 여러분은 "정해진 선택지" 라는 흔한 골칫거리를 코드로 안전하게 다루는 법을 얻으셨어요. 작아 보이지만 실무에서 정말 자주 쓰는 무기예요. 정말 수고하셨습니다!


과제

오늘 배운 Enum 을 직접 손으로 만들어봐야 진짜 내 것이 돼요. 세 가지 과제를 준비했어요. 모두 오늘까지 배운 문법(클래스·final·Enum·메서드)만으로 충분히 풀 수 있어요.

[기본] 과제 1 — 신고 사유 Enum 만들기

해야 할 일

게시물을 신고할 때 고를 수 있는 "신고 사유" 를 Enum 으로 만들어보세요.

상황

인스타에서 게시물을 신고하면 사유를 골라야 하죠. 스팸, 부적절한 콘텐츠, 사칭, 기타. 이 네 가지로 정해져 있다고 해봐요. 지금처럼 문자열 "스팸" 으로 다루면 오타가 날 수 있으니, Enum 으로 안전하게 만들 차례예요.

요구사항

  • ReportReason 이라는 Enum 을 만드세요. 상수는 SPAM, INAPPROPRIATE, IMPERSONATION, ETC 네 개예요.
  • main 에서 변수 하나에 ReportReason.SPAM 을 담아보고, 화면에 그 이름(name())을 출력해보세요.
  • ReportReason.values().length 로 신고 사유가 총 몇 개인지 출력해보세요.

힌트

  • Step 2 의 ReactionType 이 정확히 이 형태예요. 콤마로 상수만 늘어놓으면 돼요.
  • values()name() 은 Step 2 표를 다시 보면 떠올라요.

[응용] 과제 2 — 알림 종류 Enum + 데이터 + 메서드

해야 할 일

알림 종류를 Enum 으로 만들되, 종류마다 다른 데이터와 행동을 달아보세요.

상황

인스타 알림에는 종류가 있어요. 좋아요 알림, 댓글 알림, 팔로우 알림. 각 알림마다 "한글 이름" 이 다르고, "소리를 낼지 말지" 도 다르다고 해봐요(예: 팔로우는 소리 O, 좋아요는 소리 X).

요구사항

  • NotificationType 이라는 Enum 을 만드세요. 상수는 LIKE, COMMENT, FOLLOW 세 개예요.
  • 상수마다 displayName(한글 이름)과 makesSound(소리 여부, boolean)를 데이터로 갖게 하세요. 생성자와 final 필드를 쓰면 돼요.
  • 소리를 낼 수 있는지 알려주는 playsSound() 메서드를 만드세요.
  • main 에서 세 상수를 values() 로 돌면서, 한글 이름과 소리 여부를 출력해보세요.

힌트

  • Step 4 의 Grade(데이터 달기)와 Step 5 의 PostStatus(메서드 달기)를 섞으면 돼요.
  • 상수 옆 괄호에 LIKE("좋아요", false) 처럼 적으면 생성자로 전달돼요.

[심화] 과제 3 — 정렬 정책을 상수별 메서드 Enum 으로

해야 할 일

피드 정렬 정책을, 상수마다 계산 본문이 다른 Enum 으로 만들어보세요.

상황

Step 7 의 RankingPolicy 를 떠올려보세요. 이번엔 댓글 정렬을 만들어봐요. 댓글을 "좋아요 많은 순" 으로 볼 수도 있고, "최신순" 으로 볼 수도 있죠. 정책마다 점수 계산법이 달라야 해요.

요구사항

  • CommentSortPolicy 라는 Enum 을 만드세요. 상수는 BEST(좋아요순), NEWEST(최신순) 두 개예요.
  • 본문 없는 약속 public abstract int score(int likeCount, int ageMinutes); 를 두세요.
  • BESTlikeCount 를 그대로 점수로, NEWEST-ageMinutes(최근일수록 높은 점수)를 점수로 채우세요.
  • main 에서 같은 입력(예: 좋아요 5개, 30분 전)을 두 정책에 각각 넣어, 점수가 다르게 나오는지 출력해보세요.

힌트

  • Step 7 의 RankingPolicy 가 정확히 이 패턴이에요. 상수 옆 중괄호 안에 @Override 와 본문을 적으면 돼요.
  • 추상 메서드 선언은 맨 아래에, 상수들은 맨 위에 적고 그 사이를 세미콜론(;)으로 끊는 걸 잊지 마세요.

생각해볼 주제

정답이 하나로 정해진 문제가 아니에요. 혼자 고민해보거나, 동료와 이야기 나눠보면 설계를 보는 눈이 한 뼘 더 자라요.

주제 1 — int 상수 vs Enum, 어디까지 Enum 으로 만들까?

Enum 이 나오기 전에는 public static final int PUBLIC = 0; 처럼 정수 상수로 상태를 표현했어요. 지금도 이런 코드를 종종 봐요. 그런데 오늘 우리는 "정수 상수는 엉뚱한 값(5)도 들어올 수 있어 위험하다" 고 배웠죠. 그렇다고 세상의 모든 정수 상수를 다 Enum 으로 바꾸는 게 정답일까요? "값의 개수가 정해져 있고 의미가 분명한 것" 과 "그냥 설정 숫자(최대 길이 100 같은)" 사이에서, 어디까지 Enum 으로 두는 게 적당할지 자기만의 기준을 세워보세요.

주제 2 — Enum 에 메서드를 어디까지 넣어야 할까? (데이터 vs 행동)

오늘 PostStatus 에는 데이터(shareable)뿐 아니라 행동(canShare()·isEditable())까지 넣었어요. 규칙을 한곳에 모으니 편했죠. 그런데 욕심을 내서 Enum 안에 점점 더 많은 메서드를 넣다 보면, Enum 이 거대해질 수 있어요. 지난 시간에 배운 SRP(단일 책임)를 떠올려보세요. "상태가 스스로 판단해야 할 규칙(공유 가능?)" 과 "그 상태를 화면에 어떻게 그릴지 같은 다른 책임" 을 어디서 갈라야 할까요? Enum 에 행동을 넣는 게 좋은 경우와 과한 경우를 구분해보세요.

주제 3 — switch 의 default, 넣는 게 좋을까 빼는 게 좋을까?

오늘 emojiOf 의 switch 는 모든 상수를 다뤄서 default 가 없었어요. 그 덕에 나중에 상수를 추가하면 컴파일러가 "여기도 처리해야지?" 하고 알려주는 안전장치가 생겼죠. 반대로 default 를 넣어두면 어떤 일이 생길까요? 새 상수를 추가해도 컴파일러가 조용히 넘어가서, 빠뜨린 처리를 못 찾을 수도 있어요. "모든 경우를 명시해서 안전장치를 얻는 것" 과 "default 로 안전하게 기본값을 깔아두는 것" 사이의 장단점을 따져보세요.

✅ 예시 답안정답 보기

오늘 배운 Enum 을 직접 손으로 만들어 보는 답안이에요. 정답이 하나뿐인 건 아니에요. 아래 코드는 "이렇게 풀면 깔끔하다" 는 모범 사례 중 하나로 봐주세요. 세 과제 모두 오늘까지 배운 문법(클래스·final·Enum·메서드·@Override)만으로 풀 수 있어요. Step 2 의 ReactionType, Step 4 의 Grade, Step 5 의 PostStatus, Step 7 의 RankingPolicy 를 곁눈질로 참고하며 짜면 돼요.

과제 1 예시답안: 신고 사유 Enum 만들기

게시물 신고 사유 네 가지를, 오타가 날 수 있는 문자열 대신 Enum 으로 안전하게 다루는 과제예요.

핵심 접근

이 과제의 진짜 목표는 "값이 정해진 선택지는 문자열이 아니라 Enum 으로 묶는다" 는 첫 감각을 잡는 거예요. "스팸" 같은 문자열은 "스펨" 처럼 오타가 나도 컴파일러가 못 잡아요. 반면 ReportReason.SPAM 은 철자가 틀리면 컴파일 단계에서 바로 빨간 줄이 떠요. 그래서 콤마로 상수만 늘어놓는 가장 단순한 Enum 형태(Step 2 의 ReactionType 과 똑같아요)면 충분해요. name() 으로 상수 이름을, values().length 로 개수를 꺼내 보는 것까지가 이번 과제의 전부예요.

예시 구현

// com/instagram/javabasic/solution/day15/ReportReason.java
public enum ReportReason {
    SPAM,
    INAPPROPRIATE,
    IMPERSONATION,
    ETC
}

main 에서 변수에 담아 이름을 꺼내고, 총 개수를 출력해 봐요.

// com/instagram/javabasic/solution/day15/Day15SolutionMain.java (과제 1 발췌)
ReportReason reason = ReportReason.SPAM;
System.out.println("신고 사유: " + reason.name());
System.out.println("신고 사유 종류 수: " + ReportReason.values().length);

실행하면 신고 사유: SPAM, 신고 사유 종류 수: 4 가 나와요. 상수 이름이 그대로 문자열로 나오고, values() 가 네 개를 다 담은 배열을 돌려주니 길이가 4 가 돼요.

채점 포인트

포인트 설명 배점 가중
enum 키워드 + 상수 4개 class 가 아니라 enum 으로 선언하고 SPAM/INAPPROPRIATE/IMPERSONATION/ETC 가 다 있는가
name() 사용 상수 이름을 문자열로 꺼내 출력했는가
values().length 사용 상수 개수를 직접 세지 않고 values().length 로 구했는가
상수 네이밍 상수는 대문자 + 언더스코어 관례를 따랐는가

흔한 실수

  • 상수를 "SPAM" 처럼 따옴표로 감쌈 → Enum 상수는 문자열이 아니에요. 따옴표 없이 SPAM 이라고 그냥 적어요.
  • 상수 사이를 세미콜론으로 끊음 → 상수만 있는 단순 Enum 은 콤마로 구분하고, 마지막 상수 뒤에는 아무것도 안 붙여도 돼요. 세미콜론은 상수 아래에 필드·메서드가 더 따라올 때만 필요해요.
  • 개수를 4 라고 직접 적음 → 나중에 상수를 하나 추가하면 45 로 고쳐야 해서 깜빡하기 쉬워요. values().length 면 자동으로 맞춰져요.

실무 개선 포인트 (심화)

  • 지금은 상수 이름(SPAM)이 곧 화면 표시예요. 실제 서비스라면 화면엔 "스팸/광고성 게시물" 같은 한글 설명이 필요하죠. 이때 과제 2 처럼 displayName 같은 데이터를 한 칸 더 달면, 신고 사유가 늘거나 문구가 바뀌어도 이 Enum 한 곳만 고치면 돼요.
  • 신고 사유처럼 "값이 추가될 수 있는" 목록은, 상수를 추가했을 때 처리를 빠뜨리지 않도록 switch 에서 default 를 빼두는 전략(생각해볼 주제 3)을 같이 떠올려두면 좋아요.

과제 2 예시답안: 알림 종류 Enum + 데이터 + 메서드

알림 종류마다 다른 데이터(한글 이름·소리 여부)와 행동(playsSound())을 함께 담는 과제예요. Step 4 의 데이터 달기와 Step 5 의 메서드 달기를 한 Enum 안에서 합쳐 봐요.

핵심 접근

이 과제의 핵심은 "상수마다 딸린 데이터를 생성자로 받아 final 필드에 저장하고, 그 데이터를 해석하는 행동을 메서드로 노출한다" 는 흐름이에요. 상수 옆 괄호 LIKE("좋아요", false) 가 곧 생성자 호출이에요. 그래서 Enum 안에 displayName·makesSoundfinal 필드와, 그 둘을 받는 생성자를 두면 각 상수가 자기 데이터를 들고 태어나요. 바깥에서는 getDisplayName()·playsSound() 로 물어보기만 하면 되고, "소리 여부" 라는 판단은 Enum 안에 모여 있게 돼요. 같은 입력을 values() 로 한 바퀴 돌면서 출력하면, 상수마다 데이터가 다르게 들어 있다는 걸 눈으로 확인할 수 있어요.

예시 구현

// com/instagram/javabasic/solution/day15/NotificationType.java
public enum NotificationType {
    LIKE("좋아요", false),
    COMMENT("댓글", false),
    FOLLOW("팔로우", true);

    private final String displayName;
    private final boolean makesSound;

    NotificationType(String displayName, boolean makesSound) {
        this.displayName = displayName;
        this.makesSound = makesSound;
    }

    public String getDisplayName() {
        return displayName;
    }

    // 이 알림이 소리를 낼 수 있는지 알려줘요.
    public boolean playsSound() {
        return makesSound;
    }
}

main 에서 values() 로 세 상수를 한 바퀴 돌며 출력해 봐요.

// Day15SolutionMain.java (과제 2 발췌)
for (NotificationType type : NotificationType.values()) {
    System.out.println(type.getDisplayName() + " 알림 — 소리: " + type.playsSound());
}

실행하면 좋아요 알림 — 소리: false, 댓글 알림 — 소리: false, 팔로우 알림 — 소리: true 가 차례로 나와요. 상수마다 괄호에 넣어준 데이터가 그대로 따라온 거예요.

채점 포인트

포인트 설명 배점 가중
상수 옆 괄호로 데이터 전달 LIKE("좋아요", false) 처럼 상수마다 생성자 인자를 넘겼는가
final 필드 + 생성자 displayName·makesSoundfinal 필드로 두고 생성자에서 채웠는가
마지막 상수 뒤 세미콜론 상수 나열이 끝나는 FOLLOW(...) 뒤에 ; 를 찍어 필드·메서드와 구분했는가
playsSound() 메서드 데이터를 직접 노출하지 않고 메서드로 답하게 했는가
values() 순회 출력 for 로 모든 상수를 돌며 출력했는가

흔한 실수

  • 생성자를 public 으로 선언하다 막힘 → Enum 의 생성자는 바깥에서 new 로 못 만들어요. public 을 빼고 그냥 NotificationType(...) 으로 두면 돼요. 접근 제어자를 안 붙이는 게 자연스러워요.
  • 마지막 상수 뒤 세미콜론을 빠뜨림 → 상수 아래에 필드·생성자·메서드가 따라올 땐 상수 나열을 ; 로 끊어줘야 해요. 빠뜨리면 컴파일 에러가 나요. 단순 Enum(과제 1)과 가장 크게 다른 부분이에요.
  • 데이터를 public 필드로 그냥 열어둠 → final 이라 바뀌진 않지만, 묻는 통로는 메서드(getDisplayName()·playsSound())로 두는 게 캡슐화에 맞아요(Day 9 에서 배운 결).
  • makesSound 필드를 boolean 이 아니라 문자열 "true" 로 둠 → 소리 여부는 참/거짓 한 칸이니 boolean 이 딱 맞아요.

실무 개선 포인트 (심화)

  • 지금은 "소리를 낼지" 하나만 데이터로 달았지만, 실무 알림은 아이콘·진동 여부·푸시 우선순위처럼 딸린 속성이 더 많아요. 이때도 규칙은 같아요. 상수 옆 괄호에 인자를 늘리고 final 필드를 한 칸씩 더 두면, 알림 종류가 추가돼도 이 Enum 한 곳만 보면 전체가 한눈에 들어와요.
  • playsSound() 처럼 데이터를 그대로 돌려주는 단순한 메서드 말고, 데이터를 조합해 판단하는 메서드(예: "소리도 나고 중요도도 높은가?")를 둘 수도 있어요. 규칙을 Enum 안에 모아두면 바깥 코드가 if 로 종류를 일일이 검사할 필요가 없어져요.

과제 3 예시답안: 정렬 정책을 상수별 메서드 Enum 으로

같은 입력을 넣어도 정책마다 다른 점수를 내는, 상수별 메서드 Enum 을 만드는 과제예요. Step 7 의 RankingPolicy 와 똑같은 패턴이에요.

핵심 접근

이 과제의 핵심은 "약속(추상 메서드)은 한 번만 선언하고, 그 약속을 채우는 방식은 상수마다 다르게 둔다" 는 구조예요. Enum 맨 아래에 본문 없는 public abstract int score(...) 를 약속으로 두면, 각 상수는 자기 옆 중괄호 안에서 @Override 로 그 약속을 자기 방식대로 채워야 해요. BEST 는 좋아요가 많을수록 높은 점수라 likeCount 를 그대로 돌려주고, NEWEST 는 최근일수록(시간이 적을수록) 높은 점수라 -ageMinutes 를 돌려줘요. 그래서 같은 입력 (5, 30) 을 넣어도 BEST5, NEWEST-30 으로 갈려요. 정책을 추가하고 싶으면 새 상수와 그 본문만 적으면 끝이라, 정렬 규칙이 한곳에 모여요.

예시 구현

// com/instagram/javabasic/solution/day15/CommentSortPolicy.java
public enum CommentSortPolicy {

    // 좋아요순 — 좋아요가 많을수록 높은 점수
    BEST {
        @Override
        public int score(int likeCount, int ageMinutes) {
            return likeCount;
        }
    },

    // 최신순 — 최근(시간이 적을수록)일수록 높은 점수
    NEWEST {
        @Override
        public int score(int likeCount, int ageMinutes) {
            return -ageMinutes;
        }
    };

    // 본문 없는 약속 — 각 상수가 위에서 자기 방식대로 채웠어요.
    public abstract int score(int likeCount, int ageMinutes);
}

main 에서 같은 입력을 두 정책에 각각 넣어 점수가 갈리는지 확인해 봐요.

// Day15SolutionMain.java (과제 3 발췌)
int likeCount = 5;
int ageMinutes = 30;
System.out.println("좋아요순 점수: " + CommentSortPolicy.BEST.score(likeCount, ageMinutes));
System.out.println("최신순 점수: " + CommentSortPolicy.NEWEST.score(likeCount, ageMinutes));

실행하면 좋아요순 점수: 5, 최신순 점수: -30 이 나와요. 똑같은 (5, 30) 을 넣었는데 호출한 상수에 따라 다른 본문이 실행돼서 점수가 갈리는 거예요. 이게 상수별 메서드의 핵심 재미예요.

채점 포인트

포인트 설명 배점 가중
추상 메서드 선언 public abstract int score(...) 를 본문 없이 한 번 선언했는가
상수별 @Override 본문 BEST·NEWEST 가 각자 중괄호 안에서 score 를 다르게 채웠는가
상수와 추상 메서드 사이 세미콜론 상수 나열(NEWEST { ... }) 끝에 ; 를 찍어 추상 메서드와 끊었는가
점수 규칙 정확성 BEST=likeCount, NEWEST=-ageMinutes 로 맞게 구현했는가
같은 입력 다른 결과 확인 main 에서 동일 입력으로 두 정책의 차이를 출력했는가

흔한 실수

  • 추상 메서드를 맨 위에 적음 → 상수가 먼저 와야 해요. 약속(abstract score)은 상수 나열이 끝난 뒤 맨 아래에 두는 게 자연스러워요.
  • 상수 나열 끝의 세미콜론을 빠뜨림 → NEWEST { ... } 뒤에 ; 가 없으면, 그 아래 추상 메서드를 만나는 순간 컴파일러가 헷갈려요. "상수는 여기까지" 라는 마침표가 ; 예요.
  • @Override 를 빠뜨림 → 약속을 채우는 거니 @Override 를 붙이는 게 안전해요. 메서드 이름이나 인자를 잘못 적으면 컴파일러가 "재정의할 대상이 없다" 고 알려줘서 실수를 잡아줘요.
  • 정책별로 점수가 안 갈림 → 두 상수가 같은 식을 돌려주면 정렬이 똑같아져요. NEWEST 는 "시간이 적을수록 높게" 라서 부호를 뒤집은 -ageMinutes 라는 점을 놓치기 쉬워요.

실무 개선 포인트 (심화)

  • 지금은 점수 계산이 한 줄이지만, 실제 랭킹은 좋아요·댓글 수·시간 가중치를 섞은 복잡한 식이 돼요. 그래도 구조는 그대로예요. 정책이 늘면 새 상수와 그 본문만 추가하면 되고, 바깥 코드는 policy.score(...) 만 부르니 if 분기가 안 생겨요(Day 11 다형성, Day 14 OCP 와 같은 결).
  • 한발 더 가면, 정렬 정책을 고르는 책임은 바깥에 두고(예: 사용자가 "최신순" 버튼을 누름) Enum 은 점수 계산만 맡게 두면, "무엇을 고를지" 와 "어떻게 계산할지" 가 깔끔하게 나뉘어요. 이게 다음 시간 도메인 종합 설계에서 다시 만날 그림이에요.

생각해볼 주제 예시답안

정답이 하나로 정해진 문제가 아니에요. 아래 해설은 "이렇게 생각해보면 도움이 돼요" 정도의 가이드로 봐주세요.

생각해볼 주제 1 예시답안: int 상수 vs Enum, 어디까지 Enum 으로 만들까?

[문제 상황 요약]

Enum 이 나오기 전에는 public static final int PUBLIC = 0; 같은 정수 상수로 상태를 표현했어요. 지금도 이런 코드를 종종 봐요. 오늘 우리는 정수 상수는 엉뚱한 값(5)도 들어올 수 있어 위험하다고 배웠죠. 그렇다고 세상의 모든 정수 상수를 다 Enum 으로 바꾸는 게 정답일까요? "값의 개수가 정해져 있고 의미가 분명한 것" 과 "그냥 설정 숫자(최대 길이 100 같은)" 사이에서 어디까지 Enum 으로 둘지, 자기만의 기준을 세워보는 주제예요.

[튜터의 가이드 및 해설]

기준을 잡는 가장 쉬운 질문은 이거예요. "이 값이 닫힌 목록인가, 아니면 그냥 숫자 하나인가?"

게시물 공개 범위(공개/친구공개/비공개), 신고 사유, 알림 종류처럼 선택지가 손에 꼽히고 각자 이름과 의미가 있는 것은 Enum 이 잘 맞아요. 반대로 "최대 닉네임 길이 100", "한 페이지 게시물 20개" 같은 설정 숫자는 그냥 static final int 가 자연스러워요. 이건 선택지 목록이 아니라 그냥 하나의 값이거든요.

  • Option A — 정수 상수로 둔다: 가볍고 익숙해요. 다만 메서드가 int 만 받으면 5 같은 엉뚱한 값이 들어와도 컴파일러가 못 막아요. 의미가 분명한 선택지에 쓰면 위험해요.
  • Option B — Enum 으로 바꾼다: 정해진 값만 들어오게 컴파일러가 막아주고, 상수마다 데이터·행동까지 달 수 있어요. 다만 그냥 설정 숫자까지 Enum 으로 만들면 오히려 코드가 무거워져요.
  • 현업에서는 보통: "선택지가 닫혀 있고 이름·의미가 있는가?" 를 기준으로 갈라요. 상태/종류/유형/사유처럼 목록인 것은 Enum, 한도·개수·크기처럼 설정값인 것은 상수. 한 가지 더, 닫혀 있다고 생각했는데 자꾸 값이 추가되는 목록(예: 사용자가 만드는 태그)이라면 Enum 도 부적합해요. 그건 데이터로 다뤄야 해요.

핵심은 "안전하니까 다 Enum" 이 아니라 "이 값의 성격이 무엇인가" 를 먼저 보는 거예요. Enum 은 닫힌 선택지를 위한 도구지, 모든 상수의 대체품은 아니에요.

🎯 면접관을 홀리는 핵심 멘트

"정수 상수를 Enum 으로 바꿀지는 '이 값이 닫힌 선택지인가' 로 판단합니다. 공개 범위나 신고 사유처럼 값이 정해져 있고 각자 의미가 있는 목록은 Enum 으로 두면 컴파일러가 잘못된 값을 막아주고 상수별 데이터·행동까지 담을 수 있습니다. 반면 최대 길이 같은 단순 설정 숫자나, 사용자가 계속 추가하는 목록은 Enum 이 오히려 과합니다. 안전하니까 무조건 Enum 이 아니라, 값의 성격이 닫힌 목록일 때 Enum 을 선택하는 게 기준이라고 봅니다."


생각해볼 주제 2 예시답안: Enum 에 메서드를 어디까지 넣어야 할까? (데이터 vs 행동)

[문제 상황 요약]

오늘 PostStatus 에는 데이터(shareable)뿐 아니라 행동(canShare()·isEditable())까지 넣었어요. 규칙을 한곳에 모으니 편했죠. 그런데 욕심을 내서 Enum 안에 점점 더 많은 메서드를 넣다 보면 Enum 이 거대해질 수 있어요. 지난 시간 배운 SRP(단일 책임)를 떠올리며, "상태가 스스로 판단할 규칙" 과 "그 상태를 화면에 어떻게 그릴지 같은 다른 책임" 을 어디서 갈라야 할지 따져보는 주제예요.

[튜터의 가이드 및 해설]

가르는 기준은 "이 행동이 상태 자신에 대한 판단인가, 아니면 바깥 세상에 대한 일인가" 예요.

canShare() 처럼 "이 상태에서 공유가 되나?" 를 묻는 건 상태 자신의 규칙이에요. 이건 Enum 안에 두는 게 딱 맞아요. 묻는 쪽이 if 로 종류를 일일이 따지지 않아도 되거든요. 반대로 "이 상태를 화면에 빨간색으로 그려라", "이 상태일 때 알림 메일을 보내라" 같은 건 상태 자신의 일이 아니라 화면·메일 같은 바깥 책임이에요. 이런 걸 Enum 에 넣기 시작하면 Enum 하나가 화면도 알고 메일도 알게 돼서 SRP 가 무너져요.

  • Option A — 행동을 최대한 Enum 에 넣는다: 규칙이 한곳에 모여 응집도가 높아요. 다만 화면·외부 연동 같은 무관한 책임까지 끌어들이면 Enum 이 뚱뚱해지고, 그 책임들이 바뀔 때마다 Enum 을 건드려야 해요.
  • Option B — Enum 은 데이터만, 행동은 전부 바깥에서: Enum 이 가벼워요. 다만 canShare() 같은 상태 본연의 규칙까지 바깥으로 빼면, 바깥 코드 곳곳에 if (status == ...) 분기가 흩어져 오히려 관리가 어려워져요.
  • 현업에서는 보통: 상태 자신에 대한 판단(공유 가능?·수정 가능?)은 Enum 안에, 화면 표현·외부 연동처럼 다른 책임은 바깥에 둬요. "이 메서드를 한 문장으로 설명할 때 '상태가 ~인지' 로 끝나면 Enum 안, '~을 화면에/외부에 한다' 로 끝나면 바깥" 이 실무 감각이에요.

오늘 PostStatus.canShare() 가 Enum 안에 잘 들어간 이유가 바로 이거예요. 그건 상태 자신의 규칙이니까요. 화면에 어떻게 그릴지는 다음 시간 도메인 설계에서 다른 책임으로 떼어낼 거예요.

🎯 면접관을 홀리는 핵심 멘트

"Enum 에 메서드를 넣는 기준은 '그 행동이 상태 자신에 대한 판단인가' 입니다. 공유 가능 여부처럼 상태 본연의 규칙은 Enum 안에 두면 응집도가 올라가고 바깥 코드의 if 분기가 사라집니다. 반대로 화면에 어떻게 그릴지, 외부에 무엇을 보낼지 같은 다른 책임까지 Enum 에 넣으면 단일 책임이 깨지고 Enum 이 비대해집니다. 한 문장으로 '상태가 ~인지' 로 끝나면 Enum 안, '~을 화면에/외부에 한다' 로 끝나면 바깥으로 가른다고 설명하겠습니다."


생각해볼 주제 3 예시답안: switch 의 default, 넣는 게 좋을까 빼는 게 좋을까?

[문제 상황 요약]

오늘 emojiOf 의 switch 는 모든 상수를 다뤄서 default 가 없었어요. 그 덕에 나중에 상수를 추가하면 컴파일러가 "여기도 처리해야지?" 하고 알려주는 안전장치가 생겼죠. 반대로 default 를 넣어두면 새 상수를 추가해도 컴파일러가 조용히 넘어가서, 빠뜨린 처리를 못 찾을 수도 있어요. "모든 경우를 명시해 안전장치를 얻는 것" 과 "default 로 기본값을 깔아두는 것" 사이의 장단점을 따져보는 주제예요.

[튜터의 가이드 및 해설]

이건 "컴파일러를 내 편으로 쓸 것인가" 의 문제예요.

Enum 의 모든 상수를 switch 에서 다 적고 default 를 빼두면, 나중에 상수가 하나 추가됐을 때(예: LIKE/COMMENT/FOLLOWMENTION 이 늘면) 그 switch 가 새 상수를 안 다뤘다는 걸 컴파일러가 알려줘요. 즉 "고쳐야 할 곳" 을 컴파일러가 대신 찾아주는 거예요. 반대로 default 를 넣으면, 새 상수가 들어와도 그냥 default 로 빠져버려서 "여기도 고쳐야 하는데" 를 놓치기 쉬워요.

  • Option A — default 를 뺀다 (모든 경우 명시): 상수가 추가될 때 컴파일러가 빠진 곳을 잡아줘요. 우리 손으로 다루는 닫힌 Enum 에 딱 맞아요. 다만 정말 모든 상수를 다 적어야 하고, switch 가 값을 꼭 돌려줘야 하는 형태라면 마지막에 "여기 오면 안 됨" 을 표시하는 처리가 필요할 수 있어요.
  • Option B — default 로 기본값을 깐다: 어떤 값이 와도 일단 안전하게 처리돼요. 외부에서 들어온 못 믿을 값을 다룰 때 안전망이 돼요. 다만 새 상수를 빠뜨려도 조용히 넘어가서, 버그가 늦게 발견될 수 있어요.
  • 현업에서는 보통: 내가 정의한 닫힌 Enum 을 다룰 땐 default 를 빼서 컴파일러 안전장치를 챙기고, 외부에서 들어온 믿을 수 없는 값을 다룰 땐 default 로 안전망을 둬요. 한마디로 "통제 가능한 목록이면 명시, 통제 밖 값이면 default" 예요.

핵심은 default 가 좋다/나쁘다가 아니라 "이 값이 내가 통제하는 닫힌 목록인가" 를 보는 거예요. 우리가 만든 Enum 을 우리가 직접 처리한다면, default 를 빼서 컴파일러를 안전장치로 쓰는 쪽이 실수를 더 잘 막아줘요.

🎯 면접관을 홀리는 핵심 멘트

"Enum 을 다루는 switch 에서 default 를 뺄지는 '컴파일러를 안전장치로 쓸 것인가' 로 판단합니다. 내가 정의한 닫힌 Enum 이라면 default 를 빼서, 나중에 상수가 추가될 때 처리를 빠뜨린 switch 를 컴파일러가 잡아주게 합니다. 반대로 외부에서 들어온 믿을 수 없는 값을 다룰 때는 default 로 안전망을 둡니다. default 가 무조건 좋거나 나쁜 게 아니라, 통제 가능한 목록이면 모든 경우를 명시하고 통제 밖 값이면 default 를 둔다는 기준으로 설명하겠습니다."

더 배우려면

실무 프로젝트까지 가고 싶다면

팀스파르타 백엔드 부트캠프에서 인스타그램 클론을 풀스택으로 완성합니다.