📌 StreamUtils.copyToString() — InputStream을 String으로 쉽게 변환하는 방법

Spring 기반 웹 애플리케이션을 개발하다 보면, 클라이언트로부터 전달받은 HTTP 요청의 InputStream을 문자열로 읽어야 할 때가 있습니다. 이때 유용하게 사용할 수 있는 도구가 바로 StreamUtils.copyToString()입니다.


🔍 StreamUtils.copyToString()이란?

StreamUtils.copyToString()은 Spring Framework에서 제공하는 유틸리티 메서드로, InputStream으로부터 전달받은 바이트 데이터를 문자열(String)로 간편하게 변환해주는 기능을 합니다.

public static String copyToString(InputStream in, Charset charset) throws IOException
  • in: 읽을 대상 InputStream (예: 요청 본문)
  • charset: 변환 시 사용할 문자 인코딩 (예: StandardCharsets.UTF_8)

✅ 어떤 상황에서 쓰일까?

  • 클라이언트가 POST나 PUT 방식으로 JSON, XML, 혹은 일반 텍스트 데이터를 보낼 때
  • 필터나 인터셉터에서 요청 본문을 직접 읽어야 하는 경우
  • 서블릿에서 InputStream을 문자열로 바꾸고 싶을 때

💡 사용 예제

@PostMapping("/example")
public void example(HttpServletRequest request) throws IOException {
    String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
    System.out.println("요청 본문: " + body);
}

위 코드는 다음과 같은 요청에서:

POST /example
Content-Type: application/json

{"name":"철준", "age":35}

다음과 같은 출력 결과를 만듭니다:

요청 본문: {"name":"철준", "age":35}

⚠️ 주의할 점

  1. InputStream은 한 번만 읽을 수 있음!
    • copyToString()을 사용한 후에는 같은 InputStream을 다시 읽을 수 없습니다.
    • 여러 번 사용하려면 RequestWrapper를 만들어 캐싱하거나 별도 처리를 해야 합니다.
  2. 인코딩 설정은 반드시 명시해야 함
    • 클라이언트가 보낸 데이터가 UTF-8이 아닐 경우 StandardCharsets.UTF_8로 읽으면 깨질 수 있습니다.
    • Content-Type의 charset 정보를 확인하거나, UTF-8로 통일되게 처리하는 것이 좋습니다.

📦 어디에 정의되어 있나?

StreamUtils는 Spring Core 모듈에 포함되어 있으며, 다음 패키지에 위치해 있습니다:

org.springframework.util.StreamUtils

의존성은 따로 추가할 필요 없이, 스프링 부트 프로젝트에서는 기본적으로 포함되어 있습니다.


✨ 요약

항목 설명
목적 InputStream → String 변환
위치 org.springframework.util.StreamUtils
주요 사용 예 요청 본문 읽기, JSON 처리, 필터/인터셉터 처리 등
주요 주의 사항 InputStream은 한 번만 읽을 수 있음, 인코딩 주의

✅ 마무리

StreamUtils.copyToString()은 간단하지만 스프링 개발에서 꽤 자주 쓰이는 핵심 유틸 메서드입니다.
특히 요청 본문을 직접 다루거나, 인터셉터에서 로그를 찍을 때 매우 유용하게 사용할 수 있으니 기억해두면 실무에서 큰 도움이 될 것 입니다.

 

 

 

참조:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/StreamUtils.html 

 

 

 

response.getWriter().write() vs return "ok"

Spring MVC를 처음 접하거나, 직접 서블릿 API를 다뤄본 개발자라면 한 번쯤은 다음 두 코드의 차이에 대해 궁금했던 적이 있을 것입니다:

// 방식 1: HttpServletResponse 사용
response.getWriter().write("ok");

// 방식 2: @ResponseBody와 return
@ResponseBody
public String hello() {
    return "ok";
}

이 두 방식은 모두 클라이언트에게 HTTP 응답을 반환하지만, 사용 방식과 처리 흐름에 큰 차이가 있습니다.

이번 글에서는 이 차이를 정리하고, 어떤 상황에서 어떤 방식을 사용하는 것이 좋은지 알아보겠습니다.


1. response.getWriter().write("ok"): 서블릿 API 직접 사용

이 방식은 HttpServletResponse 객체를 통해 직접 응답 본문에 값을 써넣는 저수준 방식입니다.

@Controller
public class ResponseController {

    @RequestMapping("/manual-response")
    public void manualResponse(HttpServletResponse response) throws IOException {
        response.getWriter().write("ok");
    }
}

특징

  • HttpServletResponse의 getWriter()를 통해 PrintWriter를 얻고, 직접 문자열을 씁니다.
  • Spring의 뷰 리졸버(ViewResolver)나 HttpMessageConverter를 거치지 않습니다.
  • 스프링의 생태계를 우회하고 직접 응답을 제어해야 할 때 사용합니다.
  • 예: 파일 다운로드, 이미지 응답, 바이너리 응답 등.

2. return "ok" + @ResponseBody: 스프링 방식의 간결한 처리

반면, 스프링 MVC에서는 메소드 반환값을 HTTP 응답으로 보내고 싶을 때 @RestController 또는 @ResponseBody를 활용할 수 있습니다.

@RestController
public class RestApiController {

    @GetMapping("/spring-response")
    public String springResponse() {
        return "ok";
    }
}

 

※ 또는 @Controller에 @ResponseBody를 붙이면 동일한 효과입니다.

@Controller
public class RestApiController {

    @ResponseBody
    @GetMapping("/spring-response")
    public String springResponse() {
        return "ok";
    }
}

특징

  • @ResponseBody가 붙으면 반환값은 뷰 이름으로 인식되지 않고, HTTP 응답 본문으로 전송됩니다.
  • 내부적으로 HttpMessageConverter가 동작하여 문자열, JSON 등으로 자동 변환됩니다.
  • RESTful API나 간단한 문자열 응답을 만들 때 매우 유용합니다.

3. 어떤 방식을 써야 할까?

기준 response.getWriter().write() @ResponseBody + return
처리 수준 저수준 (서블릿 직접 제어) 고수준 (스프링에서 처리)
코드 간결성 비교적 복잡 매우 간결
뷰/메시지 변환기 사용 여부 사용하지 않음 HttpMessageConverter 사용
사용 사례 파일 다운로드, 이미지 응답 등 문자열, JSON 응답 등 일반적인 API 응답
추천 여부 특별한 제어가 필요한 경우만 추천 대부분의 경우 적극 추천

4. 정리

  • 직접 제어가 필요한 특별한 상황(파일 처리, 비동기 스트림 등)이 아니라면, 스프링이 제공하는 @ResponseBody 방식이 훨씬 간편하고 유지보수에 유리합니다.
  • REST API 개발 시에는 @RestController를 사용하면 @ResponseBody가 자동 적용되므로 더욱 편리합니다.
  • 스프링은 단순히 편리함을 넘어, 개발자의 생산성과 유지보수성을 높여주는 강력한 추상화 도구임을 기억하면 좋습니다.

 

참고 링크

1️⃣ 요소 병렬 처리란?

스트림을 사용하면 내부 반복자(Internal Iterator)를 통해 데이터를 자동으로 분할하여 처리할 수 있다.
이러한 병렬 처리(Parallel Processing)는 요소를 여러 개의 스트림으로 나누고, 각 스트림을 여러 개의 스레드에서 동시에 실행하는 방식이다.

🔹 병렬 처리의 목적

  • 대량의 데이터 처리 속도를 향상
  • 멀티코어 CPU의 성능을 극대화
  • 병렬 연산을 통해 빠른 결과 도출

하지만!
항상 병렬 처리가 빠른 것은 아니다.
적절한 상황에서 사용해야 효과적이다.


2️⃣ 동시성과 병렬성의 차이

💡 동시성(Concurrency)과 병렬성(Parallelism)의 개념을 정확히 이해해야 한다.

개념 설명
동시성 (Concurrency) 하나의 코어에서 여러 작업을 번갈아 가며 실행하여 마치 동시에 실행되는 것처럼 보이는 방식
병렬성 (Parallelism) 여러 개의 코어에서 여러 작업을 동시에 실행하는 방식

 

🔸 동시성 예시
→ 하나의 코어에서 작업1 → 작업2 → 작업3 → 작업1 … 순차적으로 실행됨.
→ 여러 작업이 번갈아 실행되므로 실제로는 동시에 실행되지 않지만 빠르게 전환되므로 동시 실행처럼 보임.

 

🔸 병렬성 예시
코어 1에서 작업1 실행, 코어 2에서 작업2 실행
진짜 동시에 여러 작업을 실행하는 방식

 

💡 즉, 병렬 처리는 여러 개의 CPU 코어를 활용하여 작업을 동시에 수행하는 것이며, 성능 향상이 목적이다. 🚀


3️⃣ 병렬 스트림 (Parallel Stream)

자바는 병렬 처리를 위해 **병렬 스트림(Parallel Stream)**을 제공한다.

🔹 병렬 스트림을 사용하는 방법

1. 컬렉션에서 병렬 스트림을 생성

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> parallelStream = list.parallelStream(); // 병렬 스트림 생성

 

2. 기존 스트림을 병렬 스트림으로 변환

Stream<Integer> stream = list.stream();
Stream<Integer> parallelStream = stream.parallel(); // 기존 스트림을 병렬 스트림으로 변환

 

두 방법의 차이

  • parallelStream() → 처음부터 병렬 스트림 생성
  • parallel() → 기존의 스트림을 병렬 스트림으로 변환

💡 기본적으로 스트림은 순차적으로 실행되지만, 위 방법을 사용하면 병렬 처리 가능!


4️⃣ 병렬 스트림의 원리

🔹 병렬 스트림은 내부적으로 포크 조인(Fork-Join) 프레임워크를 사용

포크(Fork) 단계

  • 데이터를 분할하여 여러 개의 작은 데이터 덩어리(서브셋)로 나눈다.
  • 각 덩어리는 별도의 스레드에서 실행된다.

조인(Join) 단계

  • 각각의 결과를 모아서 최종 결과를 생성한다.

🔹 포크 조인(Fork-Join) 처리 과정

1️⃣ 원본 데이터를 여러 개로 분할
2️⃣ 여러 개의 코어에서 동시에 처리
3️⃣ 처리된 결과를 병합하여 최종 결과 생성


5️⃣ 순차 스트림과 병렬 스트림 성능 비교

대량 데이터를 대상으로 순차 스트림과 병렬 스트림의 성능을 비교해 보자.

📝 예제 코드: 1억 개의 난수 평균 구하기

import java.util.*;
import java.util.stream.*;

public class ParallelStreamExample {
    public static void main(String[] args) {
        // 1억 개의 랜덤 숫자 리스트 생성
        Random random = new Random();
        List<Integer> numbers = IntStream.range(0, 100_000_000)
                                         .map(i -> random.nextInt(101)) // 0~100 사이 난수 생성
                                         .boxed()
                                         .collect(Collectors.toList());

        // 순차 스트림 성능 테스트
        long startTime = System.nanoTime();
        double avg1 = numbers.stream()
                             .mapToInt(Integer::intValue)
                             .average()
                             .orElse(0);
        long endTime = System.nanoTime();
        System.out.println("순차 스트림 처리 시간: " + (endTime - startTime) / 1_000_000 + " ms");

        // 병렬 스트림 성능 테스트
        startTime = System.nanoTime();
        double avg2 = numbers.parallelStream()
                             .mapToInt(Integer::intValue)
                             .average()
                             .orElse(0);
        endTime = System.nanoTime();
        System.out.println("병렬 스트림 처리 시간: " + (endTime - startTime) / 1_000_000 + " ms");
    }
}

📌 실행 결과 예시

순차 스트림 처리 시간: 5200 ms
병렬 스트림 처리 시간: 1200 ms

 

병렬 스트림이 훨씬 빠르게 처리됨!


6️⃣ 병렬 스트림 사용 시 고려할 점

1️⃣ 요소 개수가 적으면 병렬 스트림을 사용하지 않는 것이 좋다.

  • 병렬 처리는 데이터를 분할하는 과정과 스레드를 관리하는 오버헤드가 발생한다.
  • 데이터 개수가 적을 경우 오히려 순차 스트림이 더 빠를 수 있다.

2️⃣ 요소 하나를 처리하는 시간이 짧다면 병렬 스트림의 효과가 미미하다.

  • 한 요소당 처리 시간이 짧다면 병렬 처리의 효과가 크지 않다.
  • 반대로, 한 요소당 처리 시간이 길다면 병렬 처리가 효과적이다.

3️⃣ 스트림 소스의 종류에 따라 성능이 다르다.

  • ArrayList, 배열(Array) → 인덱스를 사용하여 빠르게 요소 분할 가능 → 병렬 스트림 효과적
  • LinkedList, HashSet, TreeSet → 요소를 분할하는 비용이 크다 → 병렬 처리 효과 적음

4️⃣ CPU 코어 수가 많을수록 병렬 처리가 유리하다.

  • CPU 코어 개수가 많으면 병렬 처리를 할 수 있는 스레드가 증가하여 성능 향상 가능.

정리

개념 설명
동시성 (Concurrency) 하나의 코어에서 여러 작업을 번갈아 실행 (빠르게 교대하며 실행)
병렬성 (Parallelism) 여러 개의 코어에서 여러 작업을 동시에 실행
병렬 스트림 내부적으로 Fork-Join 프레임워크를 이용하여 데이터 분할 후 병렬 처리
사용 방법 parallelStream(), stream().parallel()
주의점 요소 수가 적거나 처리 속도가 빠르면 병렬 처리가 더 느릴 수 있음

 

요소 개수가 많고, 하나의 요소 처리 시간이 길다면 병렬 스트림을 사용하면 성능 향상 효과가 크다! 🚀

 

 

 

 

 

 

 

참조:
이것이 자바다 _ 신용권

 

 

 

1. 요소 기본 집계 (Aggregation)

집계(Aggregation)는 스트림의 요소들을 하나의 값으로 축소하는 기능을 의미하며, 이는 최종 처리 기능에 속한다.

즉, 집계 메소드들은 스트림 연산의 끝에서 실행된다.


스트림 API에서 제공하는 기본 집계 함수들은 다음과 같다:

🔹 기본 집계 메소드:

메소드 설명
count() 요소 개수를 반환 (long)
findFirst() 첫 번째 요소 반환 (Optional)
max(Comparator<T>) 최대값 반환 (Optional)
min(Comparator<T>) 최소값 반환 (Optional)
average() 평균값 반환 (OptionalDouble)
sum() 합 반환 (int, long, double)

 

📌 반환값의 특징

  • count(), sum() 등의 일부 메소드는 기본 데이터 타입(int, long, double)을 반환한다.
  • max(), min(), average() 등의 메소드는 Optional<T> 형태로 값을 반환한다. 이는 집계 대상 요소가 없을 경우 예외를 방지하기 위해 사용된다.

📝 예제 코드

int[] numbers = {2, 4, 6};
IntStream intStream = Arrays.stream(numbers);

// 요소 개수
long count = intStream.count();

// 요소 합
int sum = Arrays.stream(numbers)
                .sum();

// 요소 평균
OptionalDouble average = Arrays.stream(numbers)
                               .average();
double avgValue = average.orElse(0.0); // 값이 없을 경우 기본값 0.0 반환

// 최대값
OptionalInt max = Arrays.stream(numbers)
                        .max();
int maxValue = max.orElseThrow(); // 값이 없으면 예외 발생

 


17.11 요소 커스텀 집계 (Custom Aggregation)

자바의 기본 집계 메소드(sum(), average()) 외에도 사용자가 직접 집계 기능을 구현할 수 있도록 제공되는 메소드가 reduce()이다.

🔹 reduce() 메소드

스트림의 요소들을 하나의 값으로 축소(리덕션)하기 위해 사용된다.

 
  • accumulator: 두 개의 요소를 받아 하나로 줄이는 함수 (람다식으로 구현)
  • identity: 초기값 (집계 대상이 없을 때 기본값으로 사용)
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)

 

📌 reduce()를 사용한 합계 계산 예제

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 요소들의 합 구하기
int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b);
System.out.println("합계: " + sum); // 출력: 15

// 요소들의 곱 구하기
int product = numbers.stream()
                     .reduce(1, (a, b) -> a * b);
System.out.println("곱셈 결과: " + product); // 출력: 120

// 최소값 구하기
Optional<Integer> min = numbers.stream()
                               .reduce(Integer::min);
System.out.println("최소값: " + min.orElse(-1)); // 출력: 1

 

💡 reduce()의 동작 방식

  1. 초기값(Identity)을 먼저 설정 (없으면 Optional로 반환)
  2. 첫 번째와 두 번째 요소를 연산하여 결과를 저장
  3. 결과와 세 번째 요소를 연산하여 새로운 결과를 저장
  4. 반복적으로 진행하여 최종 결과 도출

2. 요소 수집 (Collecting)

스트림의 요소들을 다른 컬렉션(List, Set, Map 등)으로 변환하거나, 집계 연산을 수행할 때 사용된다.

🔹 collect() 메소드

<R> R collect(Collector<? super T, A, R> collector)
  • Collector를 사용하여 스트림의 요소를 리스트, 집합, 맵 등으로 변환 가능
  • Collectors 유틸리티 클래스에서 다양한 Collector를 제공

1️⃣ 리스트 또는 셋으로 변환

List<String> names = students.stream()
                             .map(Student::getName)
                             .collect(Collectors.toList());

Set<Integer> scores = students.stream()
                              .map(Student::getScore)
                              .collect(Collectors.toSet());

2️⃣ Map으로 변환

Map<String, Integer> studentMap = students.stream()
                                          .collect(Collectors.toMap(
                                              Student::getName,   // Key: 학생 이름
                                              Student::getScore    // Value: 학생 점수
                                          ));

주의: toMap()을 사용할 때 중복된 키가 발생하면 IllegalStateException이 발생할 수 있다.
해결 방법: toMap()의 세 번째 인자로 (oldValue, newValue) -> newValue 제공.

 


3️⃣ 그룹핑 (GroupBy)

요소들을 특정 그룹으로 묶을 때 Collectors.groupingBy()를 사용한다.

Map<String, List<Student>> studentsByGender = students.stream()
                                                      .collect(Collectors.groupingBy(Student::getGender));

 

결과

{
    "남자": [Student1, Student2],
    "여자": [Student3, Student4]
}

4️⃣ 그룹별 집계

Map<String, Double> avgScoreByGender = students.stream()
                                               .collect(Collectors.groupingBy(
                                                   Student::getGender,
                                                   Collectors.averagingDouble(Student::getScore)
                                               ));

 

{
    "남자": 90.5,
    "여자": 88.0
}

정리

기능 메소드 설명
기본 집계 count(), sum(), average(), max(), min() 요소 개수, 합, 평균, 최댓값, 최솟값
커스텀 집계 reduce() 사용자 정의 방식으로 요소 축소
요소 수집 collect() 리스트, 셋, 맵 등으로 변환
그룹핑 groupingBy() 특정 기준으로 그룹핑
그룹별 집계 groupingBy() + averagingDouble() 그룹별 평균, 합, 개수 구하기

 

💡 스트림을 활용하면 데이터를 더욱 직관적으로 필터링, 변환, 집계할 수 있다!

 

 

 

 

 

 

 

 

참조:
이것이 자바다 _ 신용권

 

 

 

자바의 스트림(Stream) API는 데이터를 다룰 때 효율적이고 간결한 처리를 가능하게 합니다. 이번 글에서는 스트림 요소 정렬, 요소를 하나씩 처리하는 루핑, 요소의 조건 만족 여부 검사에 대해 살펴보겠습니다.


🔹 1. 요소 정렬 (Sorting)

스트림에서 요소를 정렬하려면 sorted() 메소드를 사용합니다.

📌 sorted() 메소드 종류

메소드 설명
sorted() 요소가 Comparable을 구현한 경우 자동 정렬
sorted(Comparator<T> comparator) Comparator를 사용하여 맞춤형 정렬

📌 예제 1: 기본 정렬 (Comparable이 구현된 경우)

List<String> names = List.of("Java", "Python", "C++", "Kotlin");

names.stream()
     .sorted() // 알파벳순 정렬
     .forEach(System.out::println);

// 결과:
// C++
// Java
// Kotlin
// Python
  • String 클래스는 Comparable을 구현하고 있어 sorted()만 호출해도 정렬됩니다.

📌 예제 2: Comparator를 사용한 정렬 (내림차순)

List<String> names = List.of("Java", "Python", "C++", "Kotlin");

names.stream()
     .sorted(Comparator.reverseOrder()) // 내림차순 정렬
     .forEach(System.out::println);

// 결과:
// Python
// Kotlin
// Java
// C++

📌 예제 3: 객체 정렬 (Comparable 구현)

객체가 Comparable을 구현하면 sorted()로 정렬할 수 있습니다.

class Student implements Comparable<Student> {
    String name;
    int score;
    
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.score, other.score); // 점수 오름차순 정렬
    }
}

List<Student> students = List.of(
    new Student("홍길동", 85),
    new Student("이순신", 90),
    new Student("강감찬", 80)
);

students.stream()
        .sorted() // 점수 오름차순 정렬
        .forEach(s -> System.out.println(s.name + ": " + s.score));

// 결과:
// 강감찬: 80
// 홍길동: 85
// 이순신: 90

📌 예제 4: Comparator를 사용한 정렬

객체가 Comparable을 구현하지 않아도 Comparator를 직접 제공하면 정렬이 가능합니다.

students.stream()
        .sorted(Comparator.comparingInt(s -> s.score)) // 점수 오름차순 정렬
        .forEach(s -> System.out.println(s.name + ": " + s.score));

// 내림차순 정렬
students.stream()
        .sorted((s1, s2) -> Integer.compare(s2.score, s1.score))
        .forEach(s -> System.out.println(s.name + ": " + s.score));

 


🔹 2. 요소를 하나씩 처리 (루핑)

스트림에서 요소를 하나씩 처리하는 방법은 크게 두 가지입니다.

📌 forEach() vs peek()

메소드 설명
forEach(Consumer<T>) 최종 처리 메소드 (출력, 저장 등 최종 작업)
peek(Consumer<T>) 중간 처리 메소드 (디버깅, 중간 확인 용도)

📌 예제 1: forEach() - 최종 처리

List<String> names = List.of("Java", "Python", "C++", "Kotlin");

names.stream()
     .forEach(System.out::println); // 요소 하나씩 출력
  • forEach()는 최종 처리 메소드이므로 스트림이 끝납니다.

📌 예제 2: peek() - 중간 처리 (디버깅 용도)

List<String> names = List.of("Java", "Python", "C++", "Kotlin");

names.stream()
     .peek(name -> System.out.println("처리 전: " + name)) // 중간 출력
     .sorted()
     .forEach(name -> System.out.println("처리 후: " + name));
  • peek()은 스트림 요소를 변환하지 않고 중간 상태를 확인하는 데 사용됩니다.
  • 반드시 forEach() 같은 최종 처리 메소드가 뒤에 와야 동작합니다.

📌 예제 3: peek() 활용 (짝수 필터링 후 합산)

int sum = IntStream.of(1, 2, 3, 4, 5)
                   .filter(n -> n % 2 == 0) // 짝수 필터링
                   .peek(n -> System.out.println("필터링된 값: " + n))
                   .sum(); // 최종 연산

// 결과:
// 필터링된 값: 2
// 필터링된 값: 4
// sum = 6

 


🔹 3. 요소 조건 만족 여부 (매칭)

스트림의 모든 요소가 특정 조건을 만족하는지 확인하는 기능입니다.

📌 매칭 메소드 종류

메소드 설명
allMatch(Predicate<T>) 모든 요소가 조건을 만족하는지 검사
anyMatch(Predicate<T>) 하나라도 조건을 만족하는 요소가 있는지 검사
noneMatch(Predicate<T>) 모든 요소가 조건을 만족하지 않는지 검사

📌 예제 1: allMatch() - 모든 요소가 조건을 만족하는지 검사

boolean allEven = IntStream.of(2, 4, 6, 8, 10)
                           .allMatch(n -> n % 2 == 0); // 모든 요소가 짝수인가?

System.out.println(allEven); // true

📌 예제 2: anyMatch() - 하나라도 조건을 만족하는 요소가 있는지 검사

boolean hasOdd = IntStream.of(2, 4, 6, 7, 10)
                          .anyMatch(n -> n % 2 != 0); // 홀수가 하나라도 있는가?

System.out.println(hasOdd); // true

📌 예제 3: noneMatch() - 모든 요소가 조건을 만족하지 않는지 검사

boolean noNegative = IntStream.of(2, 4, 6, 8, 10)
                              .noneMatch(n -> n < 0); // 음수가 없는가?

System.out.println(noNegative); // true

🎯 정리

기능 메소드 설명
정렬 sorted() 기본 정렬 (Comparable 필요)
sorted(Comparator<T>) 커스텀 정렬
요소 개별 처리 forEach() 최종 처리 (출력, 저장 등)
peek() 중간 처리 (디버깅, 확인 용도)
조건 검사 allMatch(Predicate<T>) 모든 요소가 조건을 만족하는지 확인
anyMatch(Predicate<T>) 하나라도 조건을 만족하는지 확인
noneMatch(Predicate<T>) 모든 요소가 조건을 만족하지 않는지 확인

 

스트림을 활용하면 데이터를 보다 효율적으로 정렬하고, 개별 요소를 처리하고, 조건을 검사할 수 있습니다. 이를 잘 활용하면 복잡한 루프를 최소화하면서도 가독성이 좋은 코드를 작성할 수 있습니다. 🚀

 

 

 

 

 

 

참조:
이것이 자바다 _ 신용권

 

 

 

자바의 스트림(Stream)은 컬렉션, 배열, 파일 등의 데이터를 다룰 때 매우 강력한 기능을 제공합니다.

이번 글에서는 리소스로부터 스트림을 얻는 방법, 요소를 필터링하는 방법, 그리고 요소를 변환하는 방법(매핑) 에 대해 다룹니다.


🔹 1. 리소스로부터 스트림 얻기

스트림은 꼭 컬렉션(Collection)에서만 생성할 수 있는 것이 아닙니다. 다양한 리소스(데이터를 저장하는 객체 또는 파일)에서 스트림을 생성할 수 있습니다.

📌 스트림 인터페이스 개요

자바의 스트림 패키지에는 BaseStream 인터페이스가 있고, 이를 상속하여 여러 스트림이 만들어집니다.

스트림 타입 설명
Stream<T> 객체 요소를 처리하는 스트림
IntStream int 기본 타입 요소가 흘러가는 스트림
LongStream long 기본 타입 요소가 흘러가는 스트림
DoubleStream double 기본 타입 요소가 흘러가는 스트림

📌 리소스별 스트림 생성 방법

자바에서 스트림을 생성할 수 있는 대표적인 리소스는 다음과 같습니다.

리소스 스트림 생성 방법
컬렉션(Collection) stream(), parallelStream()
배열(Array) Arrays.stream(array), Stream.of(array)
숫자 범위 IntStream.range(start, end), LongStream.rangeClosed(start, end)
파일(File) Files.lines(path, charset) (한 줄씩 읽어서 스트림 생성)
랜덤(Random) 값 new Random().ints(), new Random().doubles()

📌 컬렉션에서 스트림 얻기

List<String> list = List.of("Java", "Python", "C++", "Java");
Stream<String> stream = list.stream(); // 순차 스트림
Stream<String> parallelStream = list.parallelStream(); // 병렬 스트림

📌 배열에서 스트림 얻기

String[] array = {"Java", "Python", "C++"};
Stream<String> stream1 = Arrays.stream(array);
Stream<String> stream2 = Stream.of(array);

📌 숫자 범위에서 스트림 얻기

IntStream intStream = IntStream.range(1, 10); // 1~9 (끝 포함 X)
IntStream intStreamClosed = IntStream.rangeClosed(1, 10); // 1~10 (끝 포함 O)

📌 파일에서 스트림 얻기

Path path = Paths.get("data.txt");
Stream<String> fileStream = Files.lines(path, StandardCharsets.UTF_8);
fileStream.forEach(System.out::println);

🔹 2. 요소 걸러내기(필터링)

필터링은 스트림에서 특정 조건을 만족하는 요소만 걸러내는 중간 처리 작업입니다.

📌 필터링 메소드

메소드 설명
distinct() 중복 제거
filter(Predicate<T> predicate) 특정 조건에 맞는 요소만 선택

📌 예제 1: 중복 제거

List<String> names = List.of("Java", "Python", "Java", "C++", "Python");
names.stream()
     .distinct()
     .forEach(System.out::println); 
// 결과: Java, Python, C++

📌 예제 2: 특정 조건의 요소 필터링

List<String> names = List.of("신용권", "홍길동", "신사임당", "이순신");
names.stream()
     .filter(name -> name.startsWith("신"))
     .forEach(System.out::println);
// 결과: 신용권, 신사임당

📌 예제 3: 숫자 필터링

IntStream.rangeClosed(1, 10)
         .filter(n -> n % 2 == 0) // 짝수만 선택
         .forEach(System.out::println);
// 결과: 2, 4, 6, 8, 10

🔹 3. 요소 변환(매핑)

맵핑은 스트림의 요소를 변환하여 새로운 스트림을 만드는 과정입니다.

📌 변환 메소드

메소드 설명
map(Function<T, R> mapper) 객체 -> 다른 객체 변환
mapToInt(ToIntFunction<T> mapper) 객체 -> int 변환
mapToLong(ToLongFunction<T> mapper) 객체 -> long 변환
mapToDouble(ToDoubleFunction<T> mapper) 객체 -> double 변환
flatMap(Function<T, Stream<R>> mapper) 하나의 요소 -> 여러 개의 요소 변환

📌 예제 1: 객체를 특정 값으로 변환

 
class Student {
    String name;
    int score;
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    public int getScore() {
        return score;
    }
}

List<Student> students = List.of(
    new Student("홍길동", 85),
    new Student("이순신", 90),
    new Student("강감찬", 80)
);

students.stream()
        .mapToInt(Student::getScore) // 객체 -> int 변환
        .forEach(System.out::println);
// 결과: 85, 90, 80

📌 예제 2: 문자열을 단어 단위로 변환 (flatMap)

List<String> sentences = List.of("I am a Java Developer", "Learning Java Streams");

sentences.stream()
         .flatMap(sentence -> Arrays.stream(sentence.split(" "))) // 문장을 단어로 분리
         .forEach(System.out::println);
// 결과: I, am, a, Java, Developer, Learning, Java, Streams

🎯 정리

 

기능
메소드 설명
스트림 생성 stream() 컬렉션에서 스트림 생성
Arrays.stream() 배열에서 스트림 생성
Files.lines() 파일에서 스트림 생성
필터링 distinct() 중복 제거
filter() 조건에 맞는 요소만 선택
변환(매핑) map() 요소를 변환하여 새로운 스트림 생성
flatMap() 하나의 요소를 여러 개로 변환

 

자바 스트림을 활용하면 데이터를 보다 효율적으로 처리할 수 있습니다.

특히 컬렉션, 배열, 파일 등의 데이터를 스트림으로 변환하여 필터링 및 변환하는 과정을 잘 이해하면 더 강력한 프로그래밍이 가능합니다.

 

 

 

 

 

 

참조:
이것이 자바다 _ 신용권

 

 

 

+ Recent posts