
"모던 자바 인 액션"은 스트림, 람다, 함수형 프로그래밍 등
자바의 최신 기능을 깊이 있게 다룬 책인데요.
읽으면서 자연스럽게 떠오른 질문들을 하나씩 풀어보려 합니다.

CHAPTER 02 동작 파라미터화 코드 전달하기
🤔 외부 반복과 내부 반복은 어떤 차이가 있을까?
Q. for-each 에서 사용되는 반복 방식을 외부 반복,
스트림에서 사용되는 반복 방식을 내부 반복이라고 합니다.
이 둘의 차이점에는 어떤 것이 있을까요?
외부 반복(external iteration) :
개발자가 직접 요소를 하나씩 가져오며 반복을 제어하는 방식입니다.
ex. for, for-each
내부 반복(internal iteration) :
반복의 제어권을 스트림 내부에 넘기고,
“무엇을 할지”만 정의하면 스트림이 알아서 반복을 처리합니다.
외부 반복에서는 병렬성을 직접 관리해야 하지만,
(synchronized 등을 사용해 스레드 안전성을 고려해야 함)
내부 반복을 사용하면 작업을 스트림이 반복을 관리하므로
투명하게 병렬적으로 처리하거나 최적화된 다양한 순서로 처리가 가능합니다.
🤔 동작 파라미터화로 얻을 수 있는 이점은 무엇일까?
Q. 자바 8에서는 코드를 넘겨주는 일이 가능해집니다.
(ex. 실행될 메서드의 인수로 코드를 넘겨줄 수 있음)
자바 8에서 해당 개념이 추가된 이유와 중요성은 무엇일까요?
동작 파라미터화는 메서드를 다른 메서드의 인수로 넘겨주는 것을 의미합니다.
자바 8 이전에는 메서드를 값처럼 넘길 수 없어
조건이 바뀔 때마다 메서드를 새로 만들어야 했습니다.
하지만 자바 8부터는 람다와 메서드 레퍼런스를 통해
필요한 동작만 외부에서 주입 할 수 있게 되었습니다.
첫번째는 병렬성을 안전하게 실행할 수 있기 때문입니다.
메서드를 다른 메서드의 인수로 넘겨줌으로써 공유되지 않은 가변 데이터 요구사항이 됩니다.
왜 그럴까요? 전달된 메서드가 상호작용을 하지 않기 때문입니다.
그 결과, 병렬성을 안전하게 실행할 수 있게 됩니다.
두번째는 자주 바뀌는 요구사항에 유연하게 대응할 수 있습니다.
이전에는 색 필터, 무게 필터 등
필터 조건마다 다른 메서드를 따로 만들어야 했습니다.
하지만 자바 8에서는 조건을 함수로 전달할 수 있으므로
필터 로직을 하나로 통합할 수 있습니다.
public static boolean isGreenApple(Apple apple) {
return "green".equals(apple.getColor());
}
public static boolean isHeavyApple(APple apple) {
return apple.getWeight() > 150;
}
public interface Predicate<T> {
boolean test(T t);
}
public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if(p.test(apple)) {
result.add(apple);
}
}
return result;
}
filterApples(inventory, Apple::isGreenApple);
filterApples(inventory, Apple::isHeavyApple);
조건(동작)만 바꿔서 전달하면,
필터 메서드는 재사용 가능하며 중복 코드도 줄어듭니다.
🤔 자바 8부터 추가된 일급시민과 람다란 무엇인가?
Q. 자바 8은 왜 메서드를 일급 시민처럼 다룰 수 있도록 바꾼 걸까요?
먼저 일급 시민이란, 어떤 대상을 값처럼 다룰 수 있는지를 기준으로 판단하며 다음 세 가지 조건을 만족합니다.
- 변수에 담을 수 있고
- 함수의 인자로 전달할 수 있고
- 함수의 반환값으로 전달할 수 있다
자바 8 이전까지는 메서드를 값처럼 다루는 것이 불가능했습니다.
메서드 자체를 변수에 넣거나 전달하는 방식이 막혀 있었기 때문입니다.
자바 8에서 람다와 메서드 레퍼런스가 도입되면서,
자바는 비로소 메서드를 일급 시민처럼 다룰 수 있게 되었습니다.
Q. 메서드를 일급 시민으로 만들면 무엇이 좋을까요?
간단히 말하면, 함수를 값처럼 자유롭게 다룰 수 있게 된다는 의미입니다.
그 결과:
- 특정 동작(메서드)을 인자로 넘겨서 원하는 시점에 실행할 수 있고
- 동작 자체를 조합하거나 변형해
- 작업을 2차, 3차로 고도화한 로직을 만들 수 있게 됩니다.
예를 들어:
- 리스트 필터링 조건을 함수로 전달
- 실행 흐름을 외부에서 주입(Behavior Parameterization)
- map → filter → reduce 같은 고차원 연산 가능
즉, 자바가 보다 함수형 패러다임을 받아들이기 위한 기반이 마련된 것입니다.
CHAPTER 03 람다 표현식
🤔 람다는 무엇이길래 매개변수로 넘길 수 있는가?
Q. 책의 예시에 보면 ApplePredicate가 들어가야하는 자리에 그냥 람다식을 넣고 있습니다.
람다는 왜 이렇게 자유롭게 들어갈 수 있을까요?
자바8에서 람다 표현식은
익명 객체를 더 간단한 문법으로 표현한 것입니다.
그래서 원래라면 ApplePredicate 같은 인터페이스 구현 객체가 들어가야 하는 자리에
람다식을 바로 전달할 수 있습니다.
그 이유는 다음과 같습니다.
- 람다식은 사실상 하나의 익명 객체처럼 동작한다.
- 단, 그 람다가 전달될 위치의 인터페이스가
- 추상 메서드가 하나만 있는 함수형 인터페이스일 것.
- 그리고 람다의 매개변수, 반환 타입, 시그니처가 그 메서드와 일치할 것.
이 조건이 맞으면, 자바는 람다식을 자동으로 해당 인터페이스 인스턴스로 변환해줍니다.
ex. 기존 익명 클래스 사용
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple) {
return RED.equals(apple.getColor());
}
});
ex. 동일한 로직을 람다로 대체
List<Apple> result = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor()));
람다 하나만으로 익명 클래스를 완전히 대체할 수 있기 때문에
불필요한 코드가 줄고, 동작(behavior)만 깔끔하게 전달할 수 있습니다.
CHAPTER 04 스트림 소개
🤔 스트림은 만능일까?
Q. 스트림의 단점에 대해 알아봅시다.
스트림이 제공하는 선언적 스타일과 다양한 연산은 분명 강력하지만, 모든 상황에서 스트림이 더 좋은 선택은 아닙니다.
일부 경우에는 성능과 가독성이 오히려 떨어질 수 있습니다.
예를 들어, 아래 블로그 글에서 소개된 것처럼 단순 반복문을 Stream.forEach()로 억지로 바꾸면 다음 문제가 생길 수 있습니다.
for-loop 를 Stream.forEach() 로 바꾸지 말아야 할 3가지 이유
- 반복 흐름을 한눈에 파악하기 어려워지고
- 디버깅이 복잡해지고
- 불필요한 객체 생성으로 오히려 성능이 떨어지는 결과가 발생합니다.
즉, “스트림이니까 무조건 더 좋다”는 생각은 위험하며, 스트림이 잘 맞는 문제인지 먼저 판단하는 과정이 필요합니다.
🤔 스트림이 게으르게 계산하면서 얻는 이점은 무엇일까?
Q. 스트림이 게으르게 계산하면서 얻는 이점이 뭘까요?
기존의 Java 컬렉션 기반 처리에서는
모든 데이터를 메모리에 올리고 난 뒤에 로직을 수행하는 방식이 기본이었습니다.
데이터가 많아질수록 메모리 부담과 연산 비용이 커질 수밖에 없었죠.
반면 스트림은 게으르게(Lazy) 계산한다는 특징 덕분에 다음 장점을 얻습니다.
1. 필요한 데이터만 계산한다
- 스트림 중간 연산은 실제로 실행되지 않고, 터미널 연산 시점에 한 번에 계산됩니다.
- 조건에 따라 앞부분만 처리하고 종료할 수 있어 불필요한 연산을 줄입니다.
2. 메모리를 덜 사용한다
- 데이터를 모두 메모리에 담아두지 않아도 되며,
- 필요한 요소를 하나씩 흘려보내며 처리할 수 있습니다.
3. 파이프라인 최적화가 가능하다
- JVM이 스트림 파이프라인 전체를 보고 내부적으로 최적화를 수행할 수 있어,
- 단순 반복문보다 효율적인 실행 계획을 만들 수도 있습니다.
결과적으로 스트림의 지연 계산은
“덜 계산하고 필요한 만큼만 계산한다”는 장점을 가져오며,
데이터 처리 비용을 낮추고 코드의 선언적 흐름을 유지하는 데 큰 기여를 합니다.
마무리
“모던 자바 인 액션”을 읽다 보면
처음엔 낯선 개념들도
“왜 이런 방식이 필요한지?”에 집중하면 훨씬 쉽게 이해됩니다.
이번 글은 CHAPTER 2~4를 읽으며
제가 직접 스스로에게 던졌던 질문들을 중심으로 풀어본 정리였는데요.
2탄에서는 스트림을 더 실전적인 관점에서 다뤄보겠습니다.
읽어주셔서 감사합니다! 😊
'Java-Spring' 카테고리의 다른 글
| "모던 자바 인 액션" 궁금했던 것들을 질문으로 정리해봤습니다 (2탄) (0) | 2025.11.24 |
|---|---|
| Java 8에 추가된 Optional에 대해 알아보기 (0) | 2024.11.10 |