기술나눔

Java Stream API에 대한 자세한 설명: 수집 데이터의 효율적인 처리를 위한 강력한 도구

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

소개

Java 8에는 많은 새로운 기능이 도입되었으며 그 중 가장 주목할만한 기능은 Lambda 표현식과 Stream API입니다. Stream API는 수집 데이터를 처리하는 효율적이고 간결한 방법을 제공하여 코드를 더욱 간결하고 명확하게 만들고 높은 가독성과 유지 관리성을 제공합니다. 이 기사에서는 기본 개념, 일반적인 작업, 병렬 처리, 실제 사례 및 모범 사례를 포함하여 Java Stream API의 사용을 자세히 살펴봅니다.

목차

  1. 스트림 API란?
  2. Stream API의 기본 동작
  3. Stream API의 고급 작업
  4. 병렬 스트림
  5. 스트림 API 실제 사례
  6. 스트림 API 모범 사례
  7. 자주 묻는 질문 및 솔루션
  8. 요약하다

스트림 API란?

Stream API는 데이터를 선언적 방식(SQL 문과 유사)으로 처리할 수 있도록 하는 컬렉션 데이터 처리를 위해 Java 8에 도입된 추상화입니다. Stream API는 컬렉션에 대한 필터링, 정렬, 매핑, 축소 및 기타 작업에 사용할 수 있는 많은 강력한 작업을 제공하여 코드를 크게 단순화합니다.

특징

  • 선언적 프로그래밍: Stream API를 사용하여 선언적 방식으로 코드를 작성하여 상용구 코드를 줄입니다.
  • 체인콜: Stream API의 작업을 체인으로 호출할 수 있어 코드 가독성이 향상됩니다.
  • 게으른 평가: 중간 작업은 지연 평가되며 터미널 작업이 수행될 때만 평가됩니다.
  • 병렬 처리: 병렬 처리를 지원하며 멀티 코어 CPU를 최대한 활용할 수 있습니다.

Stream API의 기본 동작

스트림 생성

Stream API는 Stream을 생성하는 다양한 방법을 제공합니다.

  1. 컬렉션에서 만들기
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
  • 1
  • 2
  1. 배열에서 생성
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
  • 1
  • 2
  1. 사용Stream.of
Stream<String> stream = Stream.of("a", "b", "c");
  • 1
  1. 사용Stream.generate
Stream<Double> stream = Stream.generate(Math::random).limit(10);
  • 1
  1. 사용Stream.iterate
Stream<Integer> stream = Stream.iterate(0, n -> n + 2).limit(10);
  • 1

중간 작업

중간 작업은 Stream을 변환하는 데 사용되며 느리게 평가됩니다. 일반적인 중간 작업은 다음과 같습니다.

  1. filter: 요소를 필터링하는 데 사용됩니다.
Stream<String> stream = list.stream().filter(s -> s.startsWith("a"));
  • 1
  1. map: 각 요소를 해당 결과에 매핑하는 데 사용됩니다.
Stream<String> stream = list.stream().map(String::toUpperCase);
  • 1
  1. flatMap: 각 요소를 Stream으로 변환한 후 하나의 Stream으로 병합하는데 사용됩니다.
Stream<String> stream = list.stream().flatMap(s -> Stream.of(s.split("")));
  • 1
  1. distinct: 중복을 제거하는데 사용됩니다.
Stream<String> stream = list.stream().distinct();
  • 1
  1. sorted: 정렬에 사용됩니다.
Stream<String> stream = list.stream().sorted();
  • 1
  1. peek: 처리 중 각 요소를 확인하는데 사용됩니다.
Stream<String> stream = list.stream().peek(System.out::println);
  • 1

터미널 운영

터미널 작업은 Stream 계산을 시작하고 결과를 생성하는 데 사용됩니다. 일반적인 터미널 작업은 다음과 같습니다.

  1. forEach: 각 요소에 대해 작업을 수행합니다.
list.stream().forEach(System.out::println);
  • 1
  1. collect: Stream을 다른 형태로 변환합니다.
List<String> result = list.stream().collect(Collectors.toList());
  • 1
  1. reduce: 스트림의 요소를 값으로 줄입니다.
Optional<String> result = list.stream().reduce((s1, s2) -> s1 + s2);
  • 1
  1. toArray: 스트림을 배열로 변환합니다.
String[] array = list.stream().toArray(String[]::new);
  • 1
  1. count: 요소의 개수를 셉니다.
long count = list.stream().count();
  • 1
  1. anyMatchallMatchnoneMatch: 매칭 판정에 사용됩니다.
boolean anyMatch = list.stream().anyMatch(s -> s.startsWith("a"));
boolean allMatch = list.stream().allMatch(s -> s.startsWith("a"));
boolean noneMatch = list.stream().noneMatch(s -> s.startsWith("a"));
  • 1
  • 2
  • 3
  1. findFirstfindAny: 요소를 찾는 데 사용됩니다.
Optional<String> first = list.stream().findFirst();
Optional<String> any = list.stream().findAny();
  • 1
  • 2

Stream API의 고급 작업

종류

사용sorted이 메서드는 스트림을 정렬하고 비교기를 전달할 수 있습니다.

List<String> list = Arrays.asList("b", "c", "a");
List<String> sortedList = list.stream().sorted().collect(Collectors.toList());
// 逆序排序
List<String> sortedListDesc = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
  • 1
  • 2
  • 3
  • 4

필터

사용filterStream의 요소를 필터링하는 방법입니다.

List<String> list = Arrays.asList("a", "b", "c");
List<String> filteredList = list.stream().filter(s -> s.startsWith("a")).collect(Collectors.toList());
  • 1
  • 2

매핑

사용map메서드는 스트림의 요소를 매핑합니다.

List<String> list = Arrays.asList("a", "b", "c");
List<String> mappedList = list.stream().map(String::toUpperCase).collect(Collectors.toList());
  • 1
  • 2

법령

사용reduce스트림의 요소를 줄이는 방법입니다.

List<String> list = Arrays.asList("a", "b", "c");
String result = list.stream().reduce("", (s1, s2) -> s1 + s2);
  • 1
  • 2

모으다

사용collect메서드는 Stream을 다른 형식으로 변환합니다.

List<String> list = Arrays.asList("a", "b", "c");
List<String> collectedList = list.stream().collect(Collectors.toList());
Set<String> collectedSet = list.stream().collect(Collectors.toSet());
String joinedString = list.stream().collect(Collectors.joining(","));
  • 1
  • 2
  • 3
  • 4

병렬 스트림

병렬 스트림은 멀티 코어 CPU의 장점을 최대한 활용하여 데이터 처리 효율성을 향상시킬 수 있습니다.사용할 수 있다parallelStream메소드는 병렬 스트림을 생성합니다.

List<String> list = Arrays.asList("a", "b", "c");
List<String> parallelList = list.parallelStream().map(String::toUpperCase).collect(Collectors.toList());
  • 1
  • 2

또한 사용할 수 있습니다parallel메소드는 일반 스트림을 병렬 스트림으로 변환합니다.

List<String> list = Arrays.asList("a", "b", "c");
List<String> parallelList = list.stream().parallel().map(String::toUpperCase).collect(Collectors.toList());
  • 1
  • 2

병렬 스트림이 직렬 스트림보다 항상 빠른 것은 아니며 특정 상황에 따라 테스트해야 한다는 점에 유의해야 합니다.

스트림 API 실제 사례

수집 데이터 처리

사례 1: 컬렉션 필터링 및 변환

문자열 컬렉션이 주어지면 길이가 3보다 작은 문자열을 필터링하고 나머지 문자열을 대문자로 변환합니다.

List<String> list = Arrays.asList("a", "ab", "abc", "abcd");
List<String> result = list.stream()
    .filter(s -> s.length() >= 3)
    .map(String::toUpperCase)
    .collect(Collectors.toList());
System.out.println(result); // 输出:[ABC, ABCD]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
사례 2: 평균 계산

정수 집합이 주어지면 모든 정수의 평균을 계산합니다.

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
OptionalDouble average = list.stream()
    .mapToInt(Integer::intValue)
    .average();
average.ifPresent(System.out::println); // 输出:3.0
  • 1
  • 2
  • 3
  • 4
  • 5

파일 작업

사례 3: 파일 내용 읽기

스트림 API 사용

파일 내용을 읽고 콘솔에 출력합니다.

try (Stream<String> lines = Files.lines(Paths.get("example.txt"))) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
사례 4: 단어 발생 횟수 계산

파일 내용을 읽고 각 단어의 발생 횟수를 계산합니다.

try (Stream<String> lines = Files.lines(Paths.get("example.txt"))) {
    Map<String, Long> wordCount = lines
        .flatMap(line -> Arrays.stream(line.split("\W+")))
        .collect(Collectors.groupingBy(String::toLowerCase, Collectors.counting()));
    wordCount.forEach((word, count) -> System.out.println(word + ": " + count));
} catch (IOException e) {
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

데이터베이스 작업

사례 5: 데이터베이스 쿼리 결과 처리

데이터베이스 테이블이 있다고 가정해 보겠습니다.users, 필드 포함idname그리고age . Stream API를 사용하여 쿼리 결과를 처리할 수 있습니다.

List<User> users = queryDatabase();
List<String> names = users.stream()
    .filter(user -> user.getAge() > 18)
    .map(User::getName)
    .collect(Collectors.toList());
System.out.println(names);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

스트림 API 모범 사례

  1. 불필요한 병렬화 방지: 병렬 스트림이 항상 더 빠른 것은 아니므로 사례별로 선택해야 합니다.
  2. 중간작업과 단말작업의 올바른 활용: 중간 작업은 지연 평가되며 터미널 작업이 수행될 때만 평가됩니다.
  3. Stream의 재사용성에 주의: 한 번 사용된 스트림은 다시 사용할 수 없습니다. 재사용이 필요한 경우 사용하기 전에 스트림을 컬렉션으로 변환하는 것을 고려해 보세요.
  4. 올바른 수집기를 사용하세요Collectors이 클래스에서는 다양한 컬렉터를 제공하며, 특정 요구 사항에 따라 적절한 컬렉터를 선택할 수 있습니다.
  5. 예외 처리: Stream API를 사용하는 경우 특히 파일 작업 및 데이터베이스 작업에서 발생할 수 있는 예외를 처리해야 합니다.

자주 묻는 질문 및 솔루션

스트림이 닫혔습니다.

스트림을 한 번 사용하면 다시 사용할 수 없습니다. 재사용해야 하는 경우 사용하기 전에 스트림을 컬렉션으로 변환하는 것이 좋습니다.

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println); // 会抛出IllegalStateException
  • 1
  • 2
  • 3
  • 4

성능 문제

병렬 스트림은 직렬 스트림보다 항상 빠른 것은 아니며 사례별로 테스트해야 합니다.사용할 수 있다ForkJoinPool병렬 스트림의 성능을 최적화합니다.

ForkJoinPool customThreadPool = new ForkJoinPool(4);
customThreadPool.submit(() ->
    list.parallelStream().forEach(System.out::println)
).get();
  • 1
  • 2
  • 3
  • 4

메모리 누수

Stream API를 사용하여 대량의 데이터를 처리할 경우 메모리 누수 문제에 주의할 필요가 있습니다.사용할 수 있다close스트림을 닫는 방법 또는 사용try-with-resources문은 스트림을 자동으로 닫습니다.

try (Stream<String> lines = Files.lines(Paths.get("example.txt"))) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
  • 1
  • 2
  • 3
  • 4
  • 5

요약하다

이 기사에서는 기본 작업, 고급 작업, 병렬 처리, 실제 사례 및 모범 사례를 포함하여 Java Stream API의 사용을 자세히 소개합니다. Stream API를 합리적으로 활용함으로써 개발자는 코드를 크게 단순화하고 코드의 가독성과 유지 관리성을 향상시키며 데이터 처리 효율성도 향상시킬 수 있습니다. 이 기사가 Java 개발에서 Stream API를 사용하는 데 도움이 되기를 바랍니다.

Java Stream API는 다양한 연산을 유연하게 활용하여 효율적인 데이터 처리와 스트리밍 컴퓨팅을 달성할 수 있는 강력한 컬렉션 데이터 처리 도구입니다. Stream API를 사용해 본 적이 없다면, 이 강력한 도구를 가능한 한 빨리 배우고 익히고 프로젝트에 적용하여 개발 효율성과 코드 품질을 향상시키는 것이 좋습니다.