포스트

Enum, Lambda, Stream: 선언적 코드와 객체지향적 책임의 결합

Enum, Lambda, Stream: 선언적 코드와 객체지향적 책임의 결합

“한 줄 요약: 값 중심의 프로그래밍에서 타입과 의도 중심의 프로그래밍으로 전환하기”

1. Enum: 상수 집합을 넘어선 ‘상태 도메인 모델’

단순히 String이나 int 상수를 사용하는 것은 ‘값’을 전달할 뿐 ‘의도(Type)’를 전달하지 못합니. Enum은 인스턴스 수가 고정된 특별한 클래스로써 타입 안정성(Type Safety)을 보장합니다.

1-1. 실무형 Enum의 조건

실무에서는 상수 이름 외에 DB 코드, 설명, 상태 전이 규칙 등을 Enum 내부에 응집시켜야 합니다.

  • 데이터 응집: 코드값, 설명, 상태 종료 여부 등을 필드로 관리.
  • 행위 포함: 상태 전이 규칙(canTransitionTo) 등을 내부에 두어 서비스 로직의 비대화를 방지.
  • 주의사항: ordinal()은 선언 순서에 의존하므로 절대 DB나 외부 API 계약에 사용하지 말 것.

1-2. Enum과 전략 패턴 (Strategy Pattern)

Enum 내부에 추상 메서드를 선언하고 상수별로 구현하면 if-else 분기 없이 다형성을 활용한 전략 패턴을 구현할 수 있습니다. 이는 새로운 정책 추가 시 기존 코드를 수정하지 않는 OCP(개방-폐쇄 원칙) 를 준수할 수 있습니다.

여기서 잠깐! 전통적인 추상 메서드 방식도 훌륭하지만, 함수형 인터페이스와 람다(Lambda) 를 필드로 활용하면 코드가 한결 더 간결해집니다. 추상 메서드를 직접 구현하는 방식과 람다를 주입하는 방식 중 어떤 것이 더 유리한지, 실제 구현 코드를 통한 비교 분석은 제 블로그의 Java Enum의 추상 메서드와 람다(Lambda) 활용 포스팅에서 더 자세히 다루고 있습니다.


2. 람다(Lambda): 동작을 값처럼 다루기

람다는 단순히 for-loop를 줄이는 도구가 아닙니다. 핵심은 “동작(Behavior)을 파라미터로 전달할 수 있다” 는 점에 있습니다.

  • 함수형 인터페이스: 람다는 반드시 하나의 추상 메서드를 가진 인터페이스 문맥에서만 동작한다. (Predicate, Function, Consumer, Supplier 등)
  • 의도의 분리: “무엇을 반복할 것인가(반복 로직)”와 “어떻게 처리할 것인가(동작 로직)”를 분리할 수 있게 해준다.

3. 스트림(Stream): 데이터 변환의 파이프라인

스트림은 데이터를 저장하는 자료구조가 아니라 데이터 소스 위에서 동작하는 연산 파이프라인입니다.

3-1. 스트림의 3단계 구조

  1. Source: 데이터 시작 (list.stream())
  2. Intermediate Operation: 연산 설계 (filter, map, sorted). 지연 연산(Lazy Evaluation) 특성 덕분에 터미널 연산 전까지 실행되지 않는다.
  3. Terminal Operation: 실제 실행 및 결과 생성 (toList, collect, count).

3-2. 스트림의 핵심 동작 원리

  • Lazy Evaluation: 필요한 시점까지 계산을 미루어 효율성을 극대화한다.
  • Short-circuit: findFirst(), anyMatch() 등은 조건 충족 시 나머지 원소를 보지 않고 즉시 종료한다.
  • 일회성: 한 번 소비된 스트림은 재사용할 수 없다.

4. 실무적 선택: Stream vs for-loop

현대적인 자바 코딩에서 스트림은 강력하지만 무조건적인 정답은 아닙니다. 상황에 맞는 선택이 필요합니다.

비교 항목Stream 활용for-loop 활용
강점데이터 변환, 필터링, 집계복잡한 제어 흐름, 예외 처리
가독성비즈니스 로직(의도)이 명확히 드러남구현 세부사항(어떻게)이 노출됨
적합한 상황선언적 파이프라인이 필요할 때Checked Exception 처리, 부수 효과(Side Effect) 중심일 때

5. 참고 자료 및 신뢰성 뒷받침 (References)

본 포스팅에서 다룬 설계 방식은 자바 생태계의 표준 가이드라인과 기술적 근거를 바탕으로 합니다.

  • Effective Java (3rd Edition) - Joshua Bloch
    • Item 34: “int 상수 대신 열거 타입(Enum)을 사용하라” - Enum의 타입 안정성과 데이터 응집의 근거.
    • Item 42: “익명 클래스보다는 람다를 사용하라” - 함수형 인터페이스 활용의 당위성.
    • Item 45: “스트림은 주의해서 사용하라” - 스트림과 반복문 사이의 균형 있는 선택 가이드.
  • Oracle Java Documentation
  • 성능적 관점 (Performance)
    • 스트림은 내부적으로 Lazy EvaluationShort-circuit 최적화를 수행하지만 아주 작은 규모의 데이터셋에서는 for-loop보다 오버헤드가 발생할 수 있습니다.
    • 하지만 유지보수성과 가독성 측면에서의 이점이 크기 때문에 성능이 극도로 민감한 루프가 아니라면 스트림 사용을 우선적으로 고려하는 것이 현대 자바의 추세입니다.

6. 📝 배운 점 및 다짐

  • 응집도의 중요성: Enum을 단순히 상수로만 쓰지 않고, 관련 로직(상태 전이, 할인 정책 등)을 내부에 포함시켜 도메인 모델을 설계할 수 있다는것도 알게되었습니다.
  • 가독성의 기준: 스트림을 한 줄로 길게 쓰는 것보다 중간 연산을 의미 있는 메서드로 추출(Method Reference 활용)하여 읽는 사람이 비즈니스 흐름을 바로 파악하게 하는 것이 진정한 ‘클린 코드’임을 깨달았습니다.
  • 책임의 분리: 반복문 안에 숨겨져 있던 조건과 변환 로직을 람다와 스트림으로 분리함으로써 각 객체와 메서드가 단일 책임(SRP) 을 지도록 설계하는 연습을 계속 해봐야겠습니다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.