빠른 비교
names.sort((a, b) -> a.compareToIgnoreCase(b));
names.forEach(System.out::println);갈리는 기준
어떤 함수형 표현을 먼저 떠올리면 되나
| 상황 | 먼저 떠올릴 선택 |
|---|---|
| 동작을 짧게 전달 | 람다 (x) -> ... |
| 기존 메서드 호출만 연결 | method reference Class::method |
| 컬렉션 정렬 기준 전달 | Comparator와 람다 조합 |
| 스트림 변환/필터링 | map/filter + 람다 |
| 람다 본문이 길어짐 | 이름 있는 메서드로 분리 후 method reference |
람다: 익명 클래스 보일러플레이트 줄이기
람다 이전에는 "동작 하나"를 전달하려면 익명 클래스를 써야 했습니다. 람다는 함수형 인터페이스를 구현하는 익명 클래스와 동일하지만, 불필요한 문법을 제거해 의도를 더 직접적으로 드러냅니다.
// ❌ 익명 클래스 — 동작보다 구조가 더 눈에 띔
names.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareToIgnoreCase(b);
}
});
// ✅ 람다 — 비교 로직만 남음
names.sort((a, b) -> a.compareToIgnoreCase(b));method reference: 4가지 형태
람다가 "기존 메서드를 그냥 호출하는 것"만 한다면 method reference로 더 짧게 쓸 수 있습니다. 형태는 4가지입니다.
// 1. 정적 메서드 참조 — ClassName::staticMethod
List<Integer> numbers = tokens.stream()
.map(Integer::parseInt) // Integer.parseInt(token)
.toList();
// 2. 특정 인스턴스의 메서드 참조 — instance::method
PrintStream ps = System.out;
names.forEach(ps::println); // System.out.println(name)
// 3. 임의 인스턴스의 메서드 참조 — ClassName::instanceMethod
// 첫 번째 인수가 메서드를 호출하는 객체가 됨
names.stream()
.map(String::toUpperCase) // name.toUpperCase()
.toList();
// 4. 생성자 참조 — ClassName::new
List<String> strs = List.of("a", "b", "c");
List<StringBuilder> builders = strs.stream()
.map(StringBuilder::new) // new StringBuilder(str)
.toList();람다 vs method reference
기존 메서드를 한 번 호출하는 표현이면 method reference가 더 짧고 읽기 쉽습니다. 반대로 인수를 재배치하거나 추가 로직이 있으면 람다가 더 직접적입니다.
// ✅ 기존 메서드 호출만 연결
names.forEach(System.out::println);
names.stream().map(String::trim).toList();
// ✅ 인수 조합이나 추가 로직이 있으면 람다
names.sort((a, b) -> a.compareToIgnoreCase(b));
users.stream()
.filter(user -> user.getScore() >= 80 && user.isActive())
.toList();동작을 값처럼 전달하기
람다의 진짜 가치는 짧음이 아니라 "동작을 값처럼 전달한다"는 사고를 가능하게 하는 것입니다. 이 감각이 Stream, Comparator 조합, 이벤트 처리로 자연스럽게 이어집니다. 단, 본문이 길거나 복잡한 조건이 있다면 람다 안에 욱여넣는 것보다 별도 메서드로 빼는 편이 더 읽기 좋습니다.
// 복잡한 로직은 메서드로 분리 후 참조
users.stream()
.filter(this::isEligibleForPromotion) // 람다 대신 메서드 참조
.forEach(this::sendPromoEmail);선택 기준
| 형태 | 문법 | 예시 |
|---|---|---|
| 정적 메서드 참조 | ClassName::staticMethod | Integer::parseInt |
| 특정 인스턴스 메서드 참조 | instance::method | System.out::println |
| 임의 인스턴스 메서드 참조 | ClassName::instanceMethod | String::toUpperCase |
| 생성자 참조 | ClassName::new | StringBuilder::new |
| 짧은 동작 인라인 | 람다 (a, b) -> ... | (a, b) -> a.compareTo(b) |
| 복잡한 로직 | 별도 메서드 분리 후 참조 | this::isEligibleUser |
주의할 점
람다 본문이 길어지면 오히려 읽기 어려워집니다. 복잡한 로직은 메서드로 분리하세요.
// ❌ 람다 안에 복잡한 로직이 뭉쳐 있는 경우
users.stream()
.filter(u -> {
if (u.getAge() < 18) return false;
if (u.getStatus() == Status.INACTIVE) return false;
return u.getScore() >= 80;
})
.forEach(...);
// ✅ 의도를 이름이 있는 메서드로 표현
users.stream()
.filter(this::isEligibleUser)
.forEach(...);forEach(this::save)처럼 side-effect만 늘어나는 흐름은 Stream보다 일반 loop가 더 명확할 때가 많습니다.
// ❌ 저장, 로깅, 에러 처리까지 한 줄 체인에 몰아넣음
users.stream()
.filter(User::isActive)
.forEach(this::saveUser);
// ✅ 부작용이 중심이면 loop가 더 읽기 쉬움
for (User user : users) {
if (user.isActive()) {
saveUser(user);
}
}참고 링크
2 sources