공공부하자개발 · 영어 학습 노트
자바
초급자바의 뼈대0/8 완료
  • 00개발 환경과 첫 프로그램
  • 01변수, 데이터 타입, 형변환
  • 02연산자와 제어문
  • 031차원 · 2차원 배열
  • 04클래스, 객체, 생성자, 오버로딩
  • 05접근 제어자와 캡슐화
  • 06메서드 활용 패턴 (초급)
  • 07enum, 패키지와 import, static 과 final
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 초급 › 07 / 8

enum, 패키지와 import, static 과 final

섹션 7진행 0 / 8
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

3. 코드 예제

소스: java-src/beginner/07_enum_package (OrderStatus, PaymentMethod, Counter, Main, 그리고 패키지 예제 pkg/).

bash
cd java-src/beginner/07_enum_package
javac -encoding UTF-8 *.java && java -Dstdout.encoding=UTF-8 Main

예제 1: 문자열 상수의 버그와 enum 의 기본 메서드

java
String status = "PIAD";                                       // 오타. 컴파일은 통과한다
if (status.equals("PAID")) System.out.println("결제 완료 처리");
else System.out.println("문자열 \"" + status + "\" 는 어떤 분기에도 안 걸림 → 버그가 숨는다");

OrderStatus s = OrderStatus.PAID;                             // OrderStatus.PIAD 는 컴파일 오류
System.out.println("enum: " + s + " / 이름 " + s.name() + " / 설명 " + s.label() + " / 코드 " + s.code() + " / 순서 " + s.ordinal());
System.out.println("values(): " + Arrays.toString(OrderStatus.values()));
try { OrderStatus.valueOf("PIAD"); } catch (IllegalArgumentException e) { System.out.println("valueOf 오타 → " + e.getMessage()); }
System.out.println("PAID == PAID: " + (OrderStatus.PAID == s));
text
문자열 "PIAD" 는 어떤 분기에도 안 걸림 → 버그가 숨는다
enum: PAID / 이름 PAID / 설명 결제 완료 / 코드 20 / 순서 1
values(): [CREATED, PAID, SHIPPED, DELIVERED, CANCELLED]
valueOf("SHIPPED") = SHIPPED, fromCode(40) = DELIVERED
valueOf 오타 → No enum constant OrderStatus.PIAD
PAID == PAID: true (인스턴스가 하나라 == 로 비교 가능)

문자열 오타는 아무 예외 없이 지나갔고, enum 오타는 valueOf 에서 즉시 예외가 났습니다. 코드에 직접 쓴 OrderStatus.PIAD 는 아예 컴파일이 안 됩니다. toString() 기본값이 name() 과 같아서 출력에 그대로 이름이 찍힙니다.

예제 2: 상태 전이, 상수별 동작, EnumMap

java
OrderStatus s = OrderStatus.CREATED;
while (true) {
    System.out.print(s.label() + (s.cancellable() ? "(취소 가능)" : "") + " → ");
    try { s = s.next(); } catch (IllegalStateException e) { System.out.println("끝: " + e.getMessage()); break; }
}
for (PaymentMethod m : PaymentMethod.values())
    System.out.println("  " + m.label() + " 결제 수수료: " + m.fee(new BigDecimal("50000")).setScale(0) + "원");

Map<OrderStatus, Integer> countByStatus = new EnumMap<>(OrderStatus.class);
for (int code : new int[]{10, 20, 20, 30, 90, 20}) countByStatus.merge(OrderStatus.fromCode(code), 1, Integer::sum);
System.out.println("  상태별 건수(EnumMap): " + countByStatus);
text
주문 접수(취소 가능) → 결제 완료(취소 가능) → 배송 중 → 배송 완료 → 끝: DELIVERED 는 다음 단계가 없습니다
  카드 결제 수수료: 1250원
  계좌이체 결제 수수료: 500원
  포인트 결제 수수료: 0원
  상태별 건수(EnumMap): {CREATED=1, PAID=3, SHIPPED=1, CANCELLED=1}

next() 안의 switch 식은 다섯 상수를 전부 다뤄야 컴파일됩니다. 나중에 REFUNDED 를 추가하면 이 switch 가 컴파일 오류를 내서 "전이 규칙을 안 정했다" 를 알려 줍니다. PaymentMethod.fee 는 상수마다 구현이 달라 호출하는 쪽에 분기가 없습니다. EnumMap 출력이 선언 순서(CREATED → CANCELLED)로 나온 것도 보세요. HashMap 이었다면 순서가 보장되지 않습니다.

예제 3: static 필드는 공유, 인스턴스 필드는 각자

java
System.out.println("생성 전 created = " + Counter.created());
List<Counter> list = new ArrayList<>();
for (int i = 0; i < 3; i++) list.add(new Counter());
for (Counter c : list) System.out.println("  객체 id=" + c.id() + ", 공유 created=" + Counter.created());
System.out.println("MAX = " + Counter.MAX + ", max(3, 7) = " + max(3, 7));   // import static java.lang.Math.max
text
[static 블록] Counter 클래스가 처음 로드될 때 딱 한 번 실행
생성 전 created = 0 (객체 없이 클래스 이름으로 호출)
  객체 id=1, 공유 created=3
  객체 id=2, 공유 created=3
  객체 id=3, 공유 created=3
MAX = 1000, max(3, 7) = 7 (import static 으로 Math. 생략)

id 는 객체마다 1, 2, 3 으로 다르고 created 는 어느 객체를 통해 봐도 3 입니다. static 블록이 Counter.created() 첫 호출 직전에 한 번만 찍힌 것은 클래스 로드 시점이 "처음 쓰일 때" 임을 보여줍니다.

예제 4: final 은 재대입 금지이지 불변이 아니다

java
final List<String> items = new ArrayList<>();
items.add("사과"); items.add("배");                              // 내용은 바뀐다
// items = new ArrayList<>();                                   // 컴파일 오류: cannot assign a value to final variable items
final int[] arr = {1, 2, 3};
arr[0] = 99;                                                    // 배열 요소도 바뀐다
List<String> frozen = List.copyOf(items);                       // 진짜 불변
try { frozen.add("포도"); } catch (UnsupportedOperationException e) { System.out.println("List.copyOf 는 add 불가"); }
text
final List 에 add 가능: [사과, 배]
final 배열 요소 변경 가능: [99, 2, 3]
List.copyOf 는 add 불가: UnsupportedOperationException
String 도 불변: "abc".toUpperCase() 는 새 문자열을 만든다 → ABC

예제 5: 패키지와 import, 접근 제어

pkg/shop/Product.java 는 package shop; 으로 시작하고, pkg/PkgMain.java 가 그것을 import 합니다. 패키지가 있으면 컴파일 결과를 폴더 구조대로 두어야 하므로 -d out 과 -cp out 이 필요합니다.

bash
cd pkg
javac -encoding UTF-8 -d out PkgMain.java shop/Product.java
java -Dstdout.encoding=UTF-8 -cp out PkgMain
java
import shop.Product;
import java.util.Date;                              // java.sql.Date 와 이름이 같으므로 하나만 import
Product p = new Product("노트북", 1_200_000);
System.out.println("완전한 이름: " + p.getClass().getName() + " / Date: " + new Date().getClass().getName());
// p.internalCode();                                // 컴파일 오류: internalCode() is not public in shop.Product; cannot be accessed from outside package
java.sql.Date sqlDate = new java.sql.Date(0);       // import 안 한 쪽은 완전한 이름으로
text
노트북 1200000원, 이름 최대 50자
완전한 이름: shop.Product / Date: java.util.Date
java.sql.Date(0) = 1970-01-01 (1970-01-01, 시간대에 따라 다르게 표시)

getClass().getName() 이 shop.Product 를 돌려주는 것이 클래스의 진짜 이름입니다. 주석을 풀어 internalCode() 를 호출해 보면 default 접근이 패키지 밖에서 막히는 것을 컴파일 오류로 확인할 수 있습니다.

예제 직접 실행

아래 폴더를 JDK 21 로 컴파일하고 실행합니다.

cd java-src\beginner\07_enum_package
javac -encoding UTF-8 *.java && java Main
코드 예제
  • 예제 1: 문자열 상수의 버그와 enum 의 기본 메서드
  • 예제 2: 상태 전이, 상수별 동작, EnumMap
  • 예제 3: static 필드는 공유, 인스턴스 필드는 각자
  • 예제 4: final 은 재대입 금지이지 불변이 아니다
  • 예제 5: 패키지와 import, 접근 제어
이전 섹션2 핵심 원리3 / 7다음 섹션4 응용 변형 예제