TIL/[TIL]

[TIL]enum, lambda, stream 실무 관점 정리

namerong 2026. 5. 13. 15:34

1. 학습 주제

  • enum을 왜 쓰는지 이해하기
  • lambda를 함수형 인터페이스 관점에서 보기
  • 메서드 참조를 읽는 법 익히기
  • stream을 데이터 가공 파이프라인으로 이해하기
  • 실무에서 많이 쓰는 패턴 중심으로 정리하기

2. enum

2.1 enum이 필요한 이유

예제에서 먼저 int 상수 방식의 문제를 보여줬다.

int subject1 = Subject.JAVA;
int subject2 = Subject.HTML;

문제점:

  • JAVA와 HTML이 둘 다 0이면 다른 과목인데 같다고 비교될 수 있다
  • printSubject(100)처럼 이상한 값도 들어갈 수 있다
  • 숫자만 봐서는 의미를 알기 어렵다
  • 타입 안정성이 없다

즉 int 상수는 “값은 있지만 의미와 안전성이 부족하다”.

그래서 enum을 쓴다.


2.2 enum의 핵심

public enum Subjects {
    JAVA, MYSQL, JDBC, HTML, CSS, JAVASCRIPT
}

enum은 “정해진 값들만 가질 수 있는 타입”이다.

예:

Subjects subject = Subjects.HTML;

이제는:

  • Subjects 타입 안의 값만 넣을 수 있다
  • 100 같은 잘못된 값은 아예 컴파일 에러
  • 코드만 봐도 의미가 분명하다

실무에서 많이 쓰는 곳

enum은 실무에서 꽤 자주 쓴다.

예:

  • 회원 권한: ADMIN, USER, GUEST
  • 주문 상태: READY, PAID, SHIPPED, CANCELLED
  • 결제 수단: CARD, CASH, BANK_TRANSFER
  • 게시글 상태: DRAFT, PUBLISHED, DELETED

즉 “선택지가 정해져 있는 값”에는 enum이 잘 맞는다.


2.3 enum이 실무에서 좋은 이유

1. 타입 안정성

printSubject(Subjects.HTML);
  • Subjects만 받을 수 있다
  • 이상한 값이 들어오는 실수를 줄인다

2. 가독성

숫자 1, 2, 3보다 ADMIN, GUEST, JAVA 같은 이름이 훨씬 읽기 쉽다.

3. 유지보수성

정해진 값 목록이 한곳에 모여 있어서 관리가 쉽다.


2.4 enum은 단순 상수 묶음이 아니다

UserRole 예제가 중요했다.

public enum UserRole {
    GUEST("게스트"),
    CONSUMER("구매자"),
    PRODUCER("판매자"),
    ADMIN("관리자");
}

enum은 단순 이름만 가지는 것이 아니라:

  • 필드도 가질 수 있고
  • 생성자도 가질 수 있고
  • 메서드도 가질 수 있다

즉 enum은 “고정된 객체들의 집합”에 가깝다.

private final String description;

이런 식으로 설명도 넣을 수 있다.

실무에서 이 패턴이 많이 쓰인다.

예:

  • 상태 코드 + 사용자에게 보여줄 설명
  • 권한 이름 + 화면 표시명
  • 주문 상태 + 메시지

2.5 enum을 읽는 감각

UserRole.ADMIN

이건 단순 상수가 아니라
“UserRole 타입의 ADMIN 객체 하나”라고 이해하면 좋다.

그래서 아래도 가능하다.

admin.getDescription();
admin.getNameToLowerCase();

즉 enum은 “정해진 객체를 미리 만들어 둔 특수 클래스”처럼 생각하면 이해가 쉬워진다.


2.6 EnumSet

EnumSet<UserRole> userRoles = EnumSet.of(UserRole.ADMIN, UserRole.GUEST);

실무에서 enum 여러 개를 묶어 다룰 때 유용하다.

예:

  • 어떤 API에 허용된 권한 목록
  • 특정 사용자에게 부여된 역할 목록
  • 제외할 상태 목록

즉 Set<UserRole>보다 “enum 전용 집합”으로 더 의도가 잘 드러난다.


3. lambda

처음 람다가 어려운 이유는 “이게 메서드인지, 객체인지, 함수인지”가 애매하게 느껴지기 때문이다.
핵심은 아주 단순하다.

람다식은:

“메서드가 하나뿐인 인터페이스의 구현을 짧게 쓰는 문법”

이다.


3.1 기본 예제 이해

기존 방식:

Calculator c1 = new CalculatorImpl();

익명 클래스:

Calculator c2 = new CalculatorImpl() {
    @Override
    public int sumTwoNumber(int a, int b) {
        return a+b;
    }
};

람다:

Calculator c3 = (x, y) -> x + y;

세 개는 결국 같은 목적이다.
차이는 “표현 방식”이다.

즉 람다는
“이 인터페이스의 메서드를 이렇게 동작하게 할게”를 짧게 적는 방법이다.


3.2 함수형 인터페이스

람다를 쓰려면 인터페이스에 메서드가 하나만 있어야 한다.

@FunctionalInterface
public interface Calculator {
    int sumTwoNumber(int a, int b);
}

이런 인터페이스를 함수형 인터페이스라고 한다.

실무에서는 직접 만들기도 하지만,
대부분은 자바가 제공하는 표준 함수형 인터페이스를 많이 쓴다.


3.3 실무에서 많이 쓰는 표준 함수형 인터페이스

Consumer

받아서 사용만 하고 끝

Consumer<String> printName = name -> System.out.println(name);

많이 쓰는 곳:

  • forEach
  • 로그 출력
  • 알림 처리

Supplier

아무것도 안 받고 값을 만들어 줌

Supplier<LocalDateTime> timeSupplier = () -> LocalDateTime.now();

많이 쓰는 곳:

  • 객체 생성
  • 기본값 제공
  • 지연 생성

Function<T, R>

하나 받아서 다른 값으로 변환

Function<String, Integer> parseInt = Integer::parseInt;

많이 쓰는 곳:

  • DTO 변환
  • 문자열 -> 숫자
  • 객체 -> 특정 필드 추출

Predicate

조건 검사 후 true/false 반환

Predicate<String> isNotEmpty = str -> str != null && !str.isEmpty();

많이 쓰는 곳:

  • 필터링
  • 검증
  • 조건 분기

실무에서는 Consumer, Function, Predicate가 특히 많이 보인다.


3.4 람다는 보통 어디서 많이 보이나

실무에서 람다는 단독으로 쓰이기보다
다음과 함께 자주 나온다.

  • forEach
  • sort
  • stream().filter(...)
  • stream().map(...)
  • Optional
  • 이벤트 처리
  • 콜백 처리

즉 “컬렉션 다루는 코드”에서 특히 자주 등장한다.


3.5 람다를 읽는 법

예:

name -> System.out.println("환영합니다 " + name + "님")

이걸 읽을 때는 문법보다 뜻으로 읽는 게 낫다.

“name을 받아서 환영 메시지를 출력한다”

예:

(x, y) -> x + y

“x와 y를 받아서 더한 값을 반환한다”

즉 람다는 “짧은 함수 설명문”처럼 읽으면 된다.


4. 메서드 참조

메서드 참조는 람다를 더 줄인 문법이다.

System.out::println
Integer::parseInt
String::equals
Account::new

핵심 기준은 하나다.

“받은 값을 그대로 어떤 메서드에 전달만 하는가?”

그렇다면 메서드 참조로 줄일 수 있다.

예:

name -> System.out.println(name)

이건 그냥

System.out::println

로 줄일 수 있다.


4.1 실무에서 자주 보는 메서드 참조

정적 메서드 참조

Integer::parseInt

특정 객체의 인스턴스 메서드 참조

System.out::println

불특정 객체의 인스턴스 메서드 참조

String::equals

생성자 참조

Account::new

실무에서는 특히 아래 두 개가 진짜 자주 보인다.

  • System.out::println
  • 클래스명::getter/변환메서드

예:

.map(OnlineCourse::getTitle)

5. stream

스트림은 처음 배우면 가장 추상적으로 느껴지기 쉽다.
핵심은 이것이다.

스트림은:

“여러 데이터를 조건에 따라 거르고, 바꾸고, 모으는 작업을 읽기 좋게 연결해서 쓰는 방식”

이다.

즉 스트림은 데이터를 저장하는 자료구조가 아니라
“데이터를 처리하는 파이프라인”이다.


5.1 실무에서 왜 많이 쓰나

예를 들어 이런 요구가 있다고 하자.

  • 강의 목록에서
  • 프로그래밍 카테고리만 고르고
  • 200분 초과인 것만 찾고
  • 제목만 뽑아서 출력

기존 방식은 for, if가 길어진다.

스트림은 이렇게 쓴다.

courses.stream()
    .filter(c -> "Programing".equals(c.getCategory()))
    .filter(c -> c.getDuration() > 200)
    .map(OnlineCourse::getTitle)
    .forEach(System.out::println);

실무에서는 이런 “목록 가공”이 엄청 많다.
그래서 stream이 자주 쓰인다.


5.2 스트림은 어떻게 읽으면 되나

스트림 코드는 왼쪽에서 오른쪽으로 읽으면 된다.

names.stream()
    .filter(name -> name.length() == 3)
    .map(name -> "[" + name + "]")
    .forEach(System.out::println);

읽는 법:

  1. names를 스트림으로 만든다
  2. 길이가 3인 이름만 남긴다
  3. 이름을 [이름] 형태로 바꾼다
  4. 하나씩 출력한다

즉 스트림은 “데이터가 흐르면서 변형되는 과정”이라고 보면 된다.


5.3 스트림에서 실무적으로 자주 쓰는 것

filter

조건에 맞는 것만 남김

.filter(c -> c.isFree())

실무에서 가장 많이 쓴다.

map

다른 값으로 변환

.map(OnlineCourse::getTitle)

객체 -> 제목
객체 -> DTO
문자열 -> 숫자
이런 변환에 자주 쓰인다.

sorted

정렬

.sorted((c1, c2) -> Integer.compare(c1.getDuration(), c2.getDuration()))

distinct

중복 제거

.map(OnlineCourse::getCategory)
.distinct()

collect

최종 결과를 리스트나 맵으로 모음

.collect(Collectors.toList())

count

개수 세기

.count()

anyMatch / allMatch

조건 검사

.anyMatch(name -> name.equals("장보고"))
.allMatch(name -> name.length() == 3)

groupingBy

카테고리별 분류

.collect(Collectors.groupingBy(OnlineCourse::getCategory))

이건 실무에서 꽤 많이 쓴다.
“카테고리별”, “상태별”, “작성자별” 묶기가 필요할 때 유용하다.


5.4 stream이 특히 잘 맞는 상황

다음 같은 작업에서 스트림이 강하다.

  • 목록에서 일부만 걸러내기
  • 특정 필드만 뽑기
  • 정렬하기
  • 그룹으로 묶기
  • 합계, 개수, 평균 구하기
  • 중복 제거하기

즉 “리스트를 가공하는 일”에는 스트림이 아주 잘 맞는다.


5.5 stream을 무조건 써야 하나

그건 아니다.

스트림이 좋은 경우:

  • 데이터 가공 흐름이 명확할 때
  • 필터/변환/수집이 연속적으로 이어질 때
  • 읽는 사람이 “무엇을 하는지” 한눈에 알 수 있을 때

그냥 for문이 더 나은 경우:

  • 로직이 매우 단순할 때
  • 중간 상태를 세밀하게 제어해야 할 때
  • 디버깅이 더 쉬워야 할 때

실무에서도 “무조건 stream”보다
“읽기 좋은 쪽 선택”이 더 중요하다.


6. enum, lambda, stream을 연결해서 이해하기

이 셋은 따로 보이지만 실무에서는 함께 자주 등장한다.

enum

정해진 상태/권한/타입을 안전하게 표현

lambda

짧은 동작을 전달

stream

컬렉션 데이터를 선언적으로 가공

예를 들면 실무에서 이런 코드가 자연스럽다.

  • UserRole enum으로 권한 표현
  • 사용자 목록을 stream으로 필터링
  • 조건식은 lambda로 작성

즉 세 개가 따로따로 있는 기술이 아니라
“안전한 값 표현(enum) + 간결한 동작 전달(lambda) + 데이터 가공(stream)” 조합으로 많이 쓰인다.


7. 실무 또는 많이 쓰는 방식으로 다시 핵심만 정리

enum

가장 많이 쓰는 곳:

  • 상태값
  • 권한
  • 카테고리
  • 타입 구분

실무 감각:

  • 문자열/숫자 상수 대신 enum 선호
  • 상태별 설명이 필요하면 필드 추가
  • 권한 목록 묶을 때 EnumSet도 유용

lambda

가장 많이 쓰는 곳:

  • forEach
  • 정렬
  • 필터링
  • 이벤트/콜백 처리

실무 감각:

  • 직접 함수형 인터페이스 만들기보다
  • Consumer, Function, Predicate 같은 표준 인터페이스를 자주 사용

stream

가장 많이 쓰는 곳:

  • 목록 필터링
  • DTO 변환
  • 정렬
  • 그룹핑
  • 통계

실무 감각:

  • filter -> map -> collect
  • groupingBy
  • count, sum, anyMatch 이 조합이 특히 자주 보인다

8. 흐름 정리

8.1 enum

  • 잘못된 상수 사용을 막고
  • 의미 있는 고정값을 타입으로 다루기 위해 사용
  • 상태, 권한, 카테고리 표현에 적합

8.2 lambda

  • 메서드 하나짜리 인터페이스 구현을 짧게 쓰는 문법
  • 컬렉션 처리, 정렬, 스트림과 자주 함께 사용

8.3 stream

  • 여러 데이터를 조건에 따라 걸러내고 바꾸고 모으는 API
  • 실무에서는 목록 가공 코드에 매우 자주 등장

9. 핵심 정리

  1. enum은 정해진 값만 허용하는 안전한 타입이다.
  2. 실무에서는 상태값, 권한, 카테고리처럼 선택지가 고정된 경우 enum을 많이 쓴다.
  3. lambda는 함수형 인터페이스 구현을 짧게 쓰는 문법이다.
  4. 실무에서는 Consumer, Function, Predicate 같은 표준 함수형 인터페이스를 자주 사용한다.
  5. 메서드 참조는 람다를 더 간단히 줄인 문법이다.
  6. stream은 컬렉션 데이터를 선언적으로 가공하는 방식이다.
  7. 실무에서 가장 많이 보이는 스트림 패턴은 filter, map, collect이다.
  8. groupingBy, count, sum, distinct도 활용도가 높다.
  9. enum, lambda, stream은 따로 외우기보다 “안전한 값 표현 + 간결한 동작 전달 + 데이터 가공” 흐름으로 같이 이해하면 편하다.