[Codeit Review]/JCF와 Stream API

[04] Stream API의 기본 연산

uzlru 2026. 1. 13. 18:59

01. 스트림의 중간 연산

1. 중간 연산 (Intermediate Operation)

스트림의 중간 연산이란 스트림 생성 이후에 수행되는 연산으로, 연산 결과로 스트림을 반환하기 때문에 여러 개의 연산자를 연결하여 우리가 원하는 데이처 처리를 수행할 수 있다. 즉, 중간 연산은 데이터를 우리가 원하는 형태로 처리 및 가공하는 역할을 수행한다.

 

[사진 4] 코드 도식화

 

 

2. 주요 중간 연산자 종류

주요 중간 연산자 메서드   매개 값
필터링 filter() 조건에 맞는 데이터들만을 정제하여 더 작은 컬렉션을 생성 조건 (람다식을 통해 정의)
매핑 map() 원하는 필드만 추출하거나 특정 형태로 변환 조건 (람다식을 통해 정의)
중복 제거 distinct() 스트림 내 중복 요소를 제거 X
정렬 sorted() 스트림 요소를 정렬
>> 기본 정렬 또는 사용자 정의 정렬 제공
사용자 정의 정렬
>> 별도의 comparator
평탄화 flatMap() 중첩된 스트립 구조를 하나의 스트림으로 펼침  
엿보기 peek() 스트림의 각 요소를 중간에 엿보고 처리
>> 디버깅 또는 로깅을 목적으로 수행
>> side-effect 없이 사용하며, 데이터 조작 용도로 쓰면 안 됨
 
건너뛰기 skip() 앞의 n개의 요소를 건너뜀 건너뛸 요소의 개수
제한 limit() 앞에서부터 최대 n개의 요소만 남김 남길 요소의 최대 개수

 

// 1. 필터링 예제
import java.util.Arrays;
import java.util.List;

public class FilteringExample {
    public static void main(String[] args) throws Exception {

        List<String> names = Arrays.asList("김코딩", "이자바", "박해커", "김코딩", "박해커");

        names.stream()
                .filter(element -> element.charAt(0) == '김')		// 김씨 성을 가진 요소만 필터링
                .forEach(element -> System.out.println(element));
        System.out.println();
    }
}

// 2. 매핑 예제
import java.util.Arrays;
import java.util.List;

public class IntermediateOperationExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("kimcoding", "javalee", "hackerna", "luckyguy");
        names.stream()
                .map(element -> element.toUpperCase())			// 요소들을 하나씩 대문자로 변환
                .forEach(element->System.out.println(element));
    }
}

// 2-1. 매핑 예제 2
import java.util.Arrays;
import java.util.List;

public class IntermediateOperationExample {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 3, 6, 9);

        // 각 요소에 3을 곱한 값을 반환
        list.stream().map(number -> number * 3).forEach(System.out::println);
    }
}

// 3. 중복 예제
List<String> list = Arrays.asList("java", "sql", "mybatis", "jdbc", "spring", "java", "mybatis");

list.stream()
    .distinct()
    .forEach(System.out::println);
    
// 4. 정렬 예제
List<String> list = Arrays.asList("Tiger", "Lion", "Monkey", "Duck", "Horse", "Cow");

list.stream().sorted().forEach(System.out::println);		// 기본 오름차순

list.stream()							// 사용자 정의 정렬 (내림차순)
    .sorted((s1, s2) -> s2.compareTo(s1))
    .forEach(System.out::println);
    
// 5. 평탄화 예제
List<List<String>> nestedList = Arrays.asList(
    Arrays.asList("JAVA", "SPRING"),
    Arrays.asList("java", "spring")
);

nestedList.stream()
    .flatMap(Collection::stream)
    .forEach(System.out::println);
    
// 6. 엿보기 예제
List<String> names = List.of("Alice", "Bob", "Charlie", "David", "Eve");

names.stream()
    .filter(name -> name.length() > 3)
    .peek(name -> System.out.println("필터링 통과: " + name))
    .map(String::toUpperCase)
    .forEach(System.out::println);
    
// 7. 건너뛰기 예제
IntStream.rangeClosed(1, 10)
    .skip(5)
    .forEach(System.out::println);		// 6~10 출력

// 8. 제한 예제

IntStream.rangeClosed(1, 10)
    .limit(5)
    .forEach(System.out::println);		// 1~5 출력

 

 

 

 


 

02. 스트림의 최종 연산

스트림의 최종 연산이란, 스트림 파이프라인 마지막 단계에서 단 한 번 수행되는 연산을 의미한다. 앞서 언급한 것처럼 최종 연산이 끝난 스트림은 재사용이 불가능하며, 주로 결과를 반환하거나 데이터를 소비한다.

 

주요 최종 연산자    
순회 forEach() 스트림의 각 요소를 순회하면서 지정된 작업을 수행
>> 내부적으로 Consumer<T> 함수형 인터페이스를 사용
>> 중간 연산자와 달리 데이터를 가공하지 않고 소비
수집 Collect() 스트림 결과를 List, Set. Map 등의 컬렉션으로 수집
>> Collector 유틸리티 클래스의 정적 메서드를 활용
조건 매칭 allMatch() / anyMatch() / noneMatch() 스트릠의 요소들이 조건을 만족하는지 boolean 값으로 검사
단일 요소 반환 findFirst() / findAny() 스트림에서 하나의 요소를 Optional로 반환
>> findAny()는 병렬 처리 시 빠른 요소 반환
배열 반환 toArray() 스트림을 배열로 반환

 

// 1. 순회 예제
import java.util.List;

public class ForEachExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Anna");

        names.stream()
            .forEach(name -> System.out.println("이름: " + name));
    }
}

// 2. 수집 예제
List<String> list = Stream.of("A", "B", "C")
    .collect(Collectors.toList());

// 2-1. toMap() 예제
List<Student> totalList = Arrays.asList(
    new Student("김코딩", 100, Student.Gender.Male),
    new Student("박해커", 80, Student.Gender.Male),
    new Student("이자바", 90, Student.Gender.Female),
    new Student("나미녀", 60, Student.Gender.Female)
);

Map<String, Integer> maleMap = totalList.stream()
    .filter(s -> s.getGender() == Student.Gender.Male)
    .collect(Collectors.toMap(Student::getName, Student::getScore));

System.out.println(maleMap); // {김코딩=100, 박해커=80}

// 2-2. joining() 사용
List<Member> members = List.of(
    new Member("m1", "홍길동"),
    new Member("m2", "이몽룡")
);

String result = members.stream()
    .map(Member::getMemberName)
    .collect(Collectors.joining(", ", "[", "]"));

System.out.println(result);

// 3. 조건 매칭 예제
int[] nums = {2, 4, 6};

System.out.println(Arrays.stream(nums).allMatch(n -> n % 2 == 0));	// true
System.out.println(Arrays.stream(nums).anyMatch(n -> n % 3 == 0));	// true
System.out.println(Arrays.stream(nums).noneMatch(n -> n < 0));		// true

// 4. 단일 요소 반환 예제
Optional<String> first = Stream.of("a", "b", "c").findFirst();
Optional<String> any = Stream.of("a", "b", "c").findAny();

// 5. 배열 변환 예제
int[] arr = IntStream.rangeClosed(1, 5).toArray();
System.out.println(Arrays.toString(arr));

 

1. 지연 평가 (Lazy Evaluation)

지연 평가란 스트림 중간 연산은 최종 연산이 호출되기 전까지 실제로 실행되지 않음을 의미한다. 즉, 각 요소는 파이프라인을 통과하면서 최종 연산까지 한 번에 처리됨을 의미한다. 이를 통해 불필요한 연산을 피할 수 있을 뿐만 아니라 성능 최적화가 가능해진다.

 

public class LazyEvaluationExample {
    
    public static void main(String[] args) {
        
        List<String> names = List.of("Alice", "Bob", "Charlie", "Anna");

        // 스트림 생성시 중간 연산에서 출력은 발생하지 않음
        var stream = names.stream()
            .filter(name -> {
                System.out.println("Filtering: " + name);
                return name.startsWith("A");
            });

	// 최종 연산이 호출되어야 중간 연산이 실행됨
        System.out.println("최종 연산 시작");
        // 이 예제에서는 가장 대표적인 forEach()를 최종 연산으로 사용
        stream.forEach(System.out::println);
    }
}

 


 

03. 익명 클래스를 활용한 기본 연산

1. filter()와 Predicate<T>

// 1. 익명 클래스 사용
public class FilterExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Anna");

        names.stream()
            .filter(new Predicate<String>() {
                @Override
                public boolean test(String name) {
                    return name.startsWith("A");
                }
            })
            .forEach(System.out::println);
    }
}

// 2. 람다 표현식 사용
public class FilterExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Anna");

        names.stream()
            .filter(name -> name.startsWith("A"))
            .forEach(System.out::println);
    }
}

 

2. map()과 Function<T, R>

// 1. 익명 클래스 사용
public class MapExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Anna");

        names.stream()
            .map(new Function<String, Integer>() {
                @Override
                public Integer apply(String name) {
                    return name.length();
                }
            })
            .forEach(System.out::println);
    }
}

// 2. 람다 표현식 사용
public class MapExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Anna");

        names.stream()
            .map(name -> name.length())
            .forEach(System.out::println);
    }
}

 

3. forEach()와 Consumer<T>

// 1. 익명 클래스 사용
public class ForEachExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Anna");

        names.stream()
            .forEach(new Consumer<String>() {
                @Override
                public void accept(String name) {
                    System.out.println("이름: " + name);
                }
            });
    }
}

// 2. 람다 표현식 사용
public class ForEachExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Anna");

        names.stream()
            .forEach(name -> System.out.println("이름: " + name));
    }
}