소스: java-src/beginner/07_enum_package (OrderStatus, PaymentMethod, Counter, Main, 그리고 패키지 예제 pkg/).
cd java-src/beginner/07_enum_package
javac -encoding UTF-8 *.java && java -Dstdout.encoding=UTF-8 MainString 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));문자열 "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() 과 같아서 출력에 그대로 이름이 찍힙니다.
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);주문 접수(취소 가능) → 결제 완료(취소 가능) → 배송 중 → 배송 완료 → 끝: DELIVERED 는 다음 단계가 없습니다
카드 결제 수수료: 1250원
계좌이체 결제 수수료: 500원
포인트 결제 수수료: 0원
상태별 건수(EnumMap): {CREATED=1, PAID=3, SHIPPED=1, CANCELLED=1}next() 안의 switch 식은 다섯 상수를 전부 다뤄야 컴파일됩니다. 나중에 REFUNDED 를 추가하면 이 switch 가 컴파일 오류를 내서 "전이 규칙을 안 정했다" 를 알려 줍니다. PaymentMethod.fee 는 상수마다 구현이 달라 호출하는 쪽에 분기가 없습니다. EnumMap 출력이 선언 순서(CREATED → CANCELLED)로 나온 것도 보세요. HashMap 이었다면 순서가 보장되지 않습니다.
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[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() 첫 호출 직전에 한 번만 찍힌 것은 클래스 로드 시점이 "처음 쓰일 때" 임을 보여줍니다.
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 불가"); }final List 에 add 가능: [사과, 배]
final 배열 요소 변경 가능: [99, 2, 3]
List.copyOf 는 add 불가: UnsupportedOperationException
String 도 불변: "abc".toUpperCase() 는 새 문자열을 만든다 → ABCpkg/shop/Product.java 는 package shop; 으로 시작하고, pkg/PkgMain.java 가 그것을 import 합니다. 패키지가 있으면 컴파일 결과를 폴더 구조대로 두어야 하므로 -d out 과 -cp out 이 필요합니다.
cd pkg
javac -encoding UTF-8 -d out PkgMain.java shop/Product.java
java -Dstdout.encoding=UTF-8 -cp out PkgMainimport 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 안 한 쪽은 완전한 이름으로노트북 1200000원, 이름 최대 50자
완전한 이름: shop.Product / Date: java.util.Date
java.sql.Date(0) = 1970-01-01 (1970-01-01, 시간대에 따라 다르게 표시)getClass().getName() 이 shop.Product 를 돌려주는 것이 클래스의 진짜 이름입니다. 주석을 풀어 internalCode() 를 호출해 보면 default 접근이 패키지 밖에서 막히는 것을 컴파일 오류로 확인할 수 있습니다.