// bad case
public class Report {
public String generateReport() {
// 보고서 생성 로직
return "report content";
}
public void saveToFile(String content) {
// 파일 저장 로직
}
}
// 한 클래스가 보고서 생성과 파일 저장 두 가지 책임을 가짐 → SRP 위반
// good case
public class ReportGenerator {
public String generateReport() {
// 보고서 생성 로직
return "report content";
}
}
public class FileSaver {
public void saveToFile(String content) {
// 파일 저장 로직
}
}
// 두 클래스가 각자 한 가지 책임만 가짐 → SRP 준수
// bad case
public class ReportExporter {
public void export(String content, String format) {
if (format.equals("PDF")) {
System.out.println("Exporting as PDF: " + content);
} else if (format.equals("HTML")) {
System.out.println("Exporting as HTML: " + content);
} else {
throw new IllegalArgumentException("지원하지 않는 포맷입니다.");
}
}
}
// 새로운 포맷을 추가하려면 if-else에 코드를 계속 추가해야 함 → OCP 위반
// good case
public interface Exporter {
void export(String content);
}
public class PdfExporter implements Exporter {
public void export(String content) {
System.out.println("Exporting as PDF: " + content);
}
}
public class HtmlExporter implements Exporter {
public void export(String content) {
System.out.println("Exporting as HTML: " + content);
}
}
public class ReportExporter {
public void export(String content, Exporter exporter) {
exporter.export(content);
}
}
// ReportExporter는 Exporter 인터페이스에만 의존
// 새로운 포맷이 필요하면 기존 코드 수정 없이 Exporter 구현체만 추가하면 확장됨 → OCP 준수
map(Function<T, R>) 각 요소를 1:1로 변환@Override
public <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};
}flatMap(Function<T, Stream<R>> 각 요소를 Stream<R>로 변환 후 모든 Stream을 하나로 평탄화(flatten): map + flattenflatMap(inner -> inner.stream())로 flatten만 하는 경우가 많음@Override
public <R> Stream<R> flatMap(Function<? super P_OUT, ? extends Stream<? extends R>> mapper) {
Objects.requireNonNull(mapper);
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
try (Stream<? extends R> result = mapper.apply(u)) {
if (result != null)
result.sequential().forEach(downstream::accept);
}
}
};
}
};
}| 구분 | map | flatMap |
|---|---|---|
| 변환 | 1:1 변환 (입력 1개 → 출력 1개) | 1:N 변환 (입력 1개 → 0개~N개로 펼침) |
| 함수 | T → R | T → Stream<R> 또는 T → IntStream 등 |
| 결과 | Stream<R> (중첩 스트림이 있을 수 있음) | Stream<R> (중첩 스트림을 평탄화) |
| 사용 | 일반적인 값 변환 | 내부가 리스트, 배열 등으로 이루어져 있어 평탄화가 필요한 경우 |
| 계층 | 계층 구조 유지 (예: 2차원 → 2차원) | 계층 구조 제거 (예: n차원 → n - 1 차원) |
map 활용 사례: 문자열 리스트 → char[] 리스트로 변환
List<String> words = Arrays.asList("a", "bb", "ccc");
List<char[]> mapped = words.stream()
.map(String::toCharArray) // String → char[]
.collect(Collectors.toList());
for (char[] arr : mapped) {
System.out.println(Arrays.toString(arr));
}
// 출력:
// [a]
// [b, b]
// [c, c, c]
flatMap 활용 사례: List<List<String>>(2차원) → 1차원 리스트로 평탄화
List<List<String>> list2d = Arrays.asList(
Arrays.asList("a", "bb"),
Arrays.asList("ccc", "dddd")
);
List<String> flatMapped = list2d.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(flatMapped)
// 결과: [a, bb, ccc, dddd]