수색…


소개

Stream 은 일련의 요소를 나타내며 이러한 요소에 대한 계산을 수행하는 여러 종류의 연산을 지원합니다. 자바 8에서는 Collection 인터페이스는 두 생성하는 방법이있다 Stream : stream()parallelStream() . Stream 작업은 중간 또는 터미널입니다. 중간 작업은 Stream 반환하므로 Stream 이 닫히기 전에 여러 중간 작업을 연결할 수 있습니다. 터미널 작업은 void이거나 비 스트림 결과를 반환합니다.

통사론

  • collection.stream ()
  • Array.stream (배열)
  • Stream.iterate (firstValue, currentValue -> nextValue)
  • Stream.generate (() -> value)
  • Stream.of (elementOfT [, elementOfT, ...])
  • Stream.empty ()
  • StreamSupport.stream (iterable.spliterator (), false)

스트림 사용

Stream 은 순차 및 병렬 집계 연산을 수행 할 수있는 요소 시퀀스입니다. 모든 Stream 에는 잠재적으로 무제한의 데이터 흐름이있을 수 있습니다. 결과적으로 Stream 에서 수신 한 데이터는 데이터를 일괄 적으로 처리하는 것과는 대조적으로 도착한대로 개별적으로 처리됩니다. 람다 식과 결합하면 기능적 접근 방식을 사용하여 데이터 시퀀스에 대한 연산을 수행하는 간결한 방법을 제공합니다.

예 : ( Ideone에서 작동하는 것을보십시오 )

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .forEach(System.out::println);

산출:

사과
바나나
주황색

위의 코드에 의해 수행 된 작업은 다음과 같이 요약 할 수 있습니다.

  1. 정적 팩토리 메소드 Stream.of(values) 사용하여 시퀀스 된 순서가 지정된 과일 String 요소 Stream 을 포함하는 Stream<String> 을 만듭니다.

  2. filter() 연산은 주어진 술어와 일치하는 요소들만을 유지한다 (술어에 의해 테스트 될 때 참을 반환하는 요소). 이 경우 "a" 포함 된 요소는 유지됩니다. 술어는 람다 식으로 주어집니다.

  3. map() 연산은 매퍼 (mapper)라고 불리는 주어진 함수를 사용하여 각 요소를 변환합니다. 이 경우, 각 fruit Stringmethod-reference String::toUppercase 사용하여 대문자 String 버전에 매핑됩니다.

    점을 유의 map() 매핑 기능은 입력 매개 변수에 대한 다른 유형을 반환하는 경우 작업이 다른 제네릭 형식으로 스트림을 반환합니다. 예를 들어 Stream<String> .map(String::isEmpty) 호출하면 Stream<Boolean>

  4. sorted() 오퍼레이션은, 자연 순서 부에 따라 Stream 의 요소를 sorted() String 의 경우, 사전 식으로).

  5. 마지막으로, forEach(action) 연산은 Stream 의 각 요소에 작용하여 소비자 에게 전달하는 동작을 수행합니다. 이 예에서 각 요소는 단순히 콘솔에 인쇄됩니다. 이 작업은 터미널 작업이므로 다시 작업 할 수 없습니다.

    온 정의 동작 유의 Stream 때문에 단말 동작을 수행한다. 터미널 작업이 없으면 스트림이 처리되지 않습니다. 스트림을 다시 사용할 수 없습니다. 터미널 작업이 호출되면 Stream 객체를 사용할 수 없게됩니다.

연결 작업

전술 한 바와 같이 오퍼레이션은 함께 연결되어 데이터에 대한 쿼리로 볼 수있는 것을 형성합니다.


스트림 닫기

일반적으로 Stream 을 닫을 필요는 없습니다. IO 채널에서 작동하는 스트림을 닫을 때만 필요합니다. 대부분의 Stream 유형은 자원에서 작동하지 않으므로 닫을 필요가 없습니다.

Stream 인터페이스는 AutoCloseable 확장합니다. close 메소드를 호출하거나 try-with-resource 문을 사용하여 스트림을 닫을 수 있습니다.

Stream 을 닫아야하는 예제는 파일에서 행 Stream 을 만들 때 사용합니다.

try (Stream<String> lines = Files.lines(Paths.get("somePath"))) {
    lines.forEach(System.out::println);
}

Stream 인터페이스는 또한 스트림이 닫힐 때 호출 될 Runnable 핸들러를 등록 할 수있는 Stream.onClose() 메소드를 선언합니다. 사용 사례의 예는 스트림을 생성하는 코드가 일부 정리를 수행하는 데 소비되는시기를 알아야하는 경우입니다.

public Stream<String>streamAndDelete(Path path) throws IOException {
    return Files.lines(path).onClose(() -> someClass.deletePath(path));
}

실행 핸들러는 try-with-resources 문에 의해 명시 적으로 또는 암시 적으로 close() 메서드가 호출되면 실행됩니다.


주문 처리

Stream 객체의 처리는 순차적이거나 병렬 적일 수 있습니다.

순차적 인 모드에서 요소는 Stream 의 소스 순서로 처리됩니다. SortedMap 구현이나 List 등, Stream 이 순서 붙일 수 있고있는 경우, 처리는 소스의 순서에 일치하는 것이 보증됩니다. 그러나 다른 경우에는 순서에 의존하지 않도록주의해야합니다 . Java HashMap keySet() 반복 순서가 일관성이 있는지 확인하십시오.

예:

List<Integer> integerList = Arrays.asList(0, 1, 2, 3, 42); 

// sequential 
long howManyOddNumbers = integerList.stream()
                                    .filter(e -> (e % 2) == 1)
                                    .count(); 

System.out.println(howManyOddNumbers); // Output: 2

이데올로기에 살기

병렬 모드를 사용하면 여러 코어에서 여러 스레드를 사용할 수 있지만 요소가 처리되는 순서는 보장 할 수 없습니다.

여러 메소드가 순차 Stream 에서 호출되는 경우 모든 메소드가 호출되지 않아도됩니다. 예를 들어, Stream 이 필터링되고 요소 수가 1로 감소되면 sort 과 같은 메소드에 대한 후속 호출이 발생하지 않습니다. 이 순차적의 성능을 향상시킬 수 있습니다 Stream 병렬 불가능 최적화 - Stream .

예:

// parallel
long howManyOddNumbersParallel = integerList.parallelStream()
                                            .filter(e -> (e % 2) == 1)
                                            .count();

System.out.println(howManyOddNumbersParallel); // Output: 2

이데올로기에 살기


컨테이너 (또는 컬렉션 )와의 차이점

컨테이너와 스트림 모두에서 수행 할 수있는 작업이 있지만 궁극적으로는 서로 다른 용도로 사용되며 서로 다른 작업을 지원합니다. 컨테이너는 요소 저장 방법과 요소에 효율적으로 액세스하는 방법에보다 중점을 둡니다. 한편, Stream 은 요소에 직접 액세스 및 조작을 제공하지 않습니다. 그것은 집단 엔티티로서의 객체 그룹에 더 전념하고 그 엔티티 전체에 대한 연산을 수행한다. StreamCollection 은 이러한 다양한 목적을 위해 별도의 고수준 추상화입니다.

콜렉션에 스트림 요소 수집

toList()toSet() 하여 수집하십시오.

Stream 요소는 Stream.collect 작업을 사용하여 컨테이너로 쉽게 수집 할 수 있습니다.

System.out.println(Arrays
    .asList("apple", "banana", "pear", "kiwi", "orange")
    .stream()
    .filter(s -> s.contains("a"))
    .collect(Collectors.toList())
);
// prints: [apple, banana, pear, orange]

Set 와 같은 다른 콜렉션 인스턴스는 다른 Collectors 내장 메소드를 사용하여 만들 수 있습니다. 예를 들어, Collectors.toSet()Stream 의 요소를 Set 으로 수집합니다.


List 또는 Set 구현에 대한 명시 적 제어

Collectors#toList()Collectors#toSet() 문서에 따르면 List 또는 Set 의 유형, 변경 가능성, 직렬 가능성 또는 스레드 안전성에 대한 보장은 없습니다.

반환되는 구현을 명시 적으로 제어하려면, 지정된 공급자가 새롭고 빈 콜렉션을 반환하는 Collectors#toCollection(Supplier) 을 대신 사용할 수 있습니다.

// syntax with method reference
System.out.println(strings
        .stream()
        .filter(s -> s != null && s.length() <= 3)
        .collect(Collectors.toCollection(ArrayList::new))
);

// syntax with lambda
System.out.println(strings
        .stream()
        .filter(s -> s != null && s.length() <= 3)
        .collect(Collectors.toCollection(() -> new LinkedHashSet<>()))
);

toMap을 사용하여 요소 수집하기

수집기는 Map에 요소를 누적합니다. 여기서 key는 학생 ID이고 Value는 학생 값입니다.

  List<Student> students = new ArrayList<Student>(); 
    students.add(new Student(1,"test1"));
    students.add(new Student(2,"test2"));
    students.add(new Student(3,"test3"));
    
    Map<Integer, String> IdToName = students.stream()
        .collect(Collectors.toMap(Student::getId, Student::getName));
    System.out.println(IdToName);

출력 :

{1=test1, 2=test2, 3=test3}

Collector.toMap에는 Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) . mergeFunction은 목록에서 맵에 새 멤버를 추가 할 때 키가 반복 될 경우 새 값을 선택하거나 이전 값을 유지하는 데 주로 사용됩니다.

mergeFunction은 반복 키에 해당하는 값을 유지하려면 (s1, s2) -> s1 과 같거나 반복 키의 새 값을 넣으려면 (s1, s2) -> s2 와 같습니다.

컬렉션지도로 요소 수집

예 : ArrayList에서 Map <String, List <>>

흔히 기본 목록에서 목록의지도를 작성해야합니다. 예 : 목록의 학생에게서, 우리는 각 학생에 대한 과목 목록의지도를 만들어야합니다.

    List<Student> list = new ArrayList<>();
    list.add(new Student("Davis", SUBJECT.MATH, 35.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 12.9));
    list.add(new Student("Davis", SUBJECT.GEOGRAPHY, 37.0));

    list.add(new Student("Sascha", SUBJECT.ENGLISH, 85.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 80.0));
    list.add(new Student("Sascha", SUBJECT.SCIENCE, 12.0));
    list.add(new Student("Sascha", SUBJECT.LITERATURE, 50.0));

    list.add(new Student("Robert", SUBJECT.LITERATURE, 12.0));

    Map<String, List<SUBJECT>> map = new HashMap<>();
    list.stream().forEach(s -> {
                map.computeIfAbsent(s.getName(), x -> new ArrayList<>()).add(s.getSubject());
            });
    System.out.println(map);

산출:

{ Robert=[LITERATURE], 
Sascha=[ENGLISH, MATH, SCIENCE, LITERATURE], 
Davis=[MATH, SCIENCE, GEOGRAPHY] }

예 : ArrayList에서 Map <String, Map <>>

    List<Student> list = new ArrayList<>();
    list.add(new Student("Davis", SUBJECT.MATH, 1, 35.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 2, 12.9));
    list.add(new Student("Davis", SUBJECT.MATH, 3, 37.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 4, 37.0));

    list.add(new Student("Sascha", SUBJECT.ENGLISH, 5, 85.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 1, 80.0));
    list.add(new Student("Sascha", SUBJECT.ENGLISH, 6, 12.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 3, 50.0));

    list.add(new Student("Robert", SUBJECT.ENGLISH, 5, 12.0));

    Map<String, Map<SUBJECT, List<Double>>> map = new HashMap<>();

    list.stream().forEach(student -> {
        map.computeIfAbsent(student.getName(), s -> new HashMap<>())
                .computeIfAbsent(student.getSubject(), s -> new ArrayList<>())
                .add(student.getMarks());
    });

    System.out.println(map);

산출:

{ Robert={ENGLISH=[12.0]}, 
Sascha={MATH=[80.0, 50.0], ENGLISH=[85.0, 12.0]}, 
Davis={MATH=[35.0, 37.0], SCIENCE=[12.9, 37.0]} }

컨닝 지

암호
List 모으기 Collectors.toList()
미리 할당 된 크기로 ArrayList 수집 Collectors.toCollection(() -> new ArrayList<>(size))
Set 모으기 Collectors.toSet()
보다 나은 반복 성능으로 Set 수집 Collectors.toCollection(() -> new LinkedHashSet<>())
대 / 소문자를 구분하지 않는 Set<String> Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))
EnumSet<AnEnum> (열거 형의 경우 최상의 성능)에 수집 Collectors.toCollection(() -> EnumSet.noneOf(AnEnum.class))
고유 키를 사용하여 Map<K,V> 수집 Collectors.toMap(keyFunc,valFunc)
MyObject.getter ()를 고유 한 MyObject에 매핑 Collectors.toMap(MyObject::getter, Function.identity())
MyObject.getter ()를 여러 MyObject에 매핑 Collectors.groupingBy(MyObject::getter)

무한 스트림

끝나지 않는 Stream 을 생성 할 수 있습니다. 무한의 단말에있어서 통화 Stream 원인 Stream 무한 루프를 입력한다. Streamlimit 메소드는, Java가 처리하는 Stream 의 용어의 수를 제한하기 위해서 사용할 수 있습니다.

이 예는 생성 Stream 의 각 연속적인 용어 숫자 1로 시작, 모든 자연의 숫자를 Stream 이전의 이상 높다. 이것의 한계 방법 호출하여 Stream 만의 처음 다섯 개 용어 Stream 고려 인쇄됩니다.

// Generate infinite stream - 1, 2, 3, 4, 5, 6, 7, ...
IntStream naturalNumbers = IntStream.iterate(1, x -> x + 1);

// Print out only the first 5 terms
naturalNumbers.limit(5).forEach(System.out::println);

산출:

1
2

4
5


무한 스트림을 생성하는 또 다른 방법은 Stream.generate 메서드를 사용하는 입니다. 이 메서드는 Supplier 형식의 람다 를 사용합니다.

// Generate an infinite stream of random numbers
Stream<Double> infiniteRandomNumbers = Stream.generate(Math::random);

// Print out only the first 10 random numbers
infiniteRandomNumbers.limit(10).forEach(System.out::println);

스트림 소비

Streamcount() , collect() 또는 forEach() 와 같은 터미널 작업 이있을 때만 통과합니다. 그렇지 않으면 Stream 에 대한 조작이 수행되지 않습니다.

다음 예제에서는 터미널 작업이 Stream 추가되지 않으므로 peek()터미널 작업 이 아니기 때문에 filter() 작업이 호출되지 않고 출력이 생성되지 않습니다.

IntStream.range(1, 10).filter(a -> a % 2 == 0).peek(System.out::println);

이데올로기에 살기

이것은 유효한 터미널 작업 이있는 Stream 시퀀스이므로 출력이 생성됩니다.

peek 대신 forEach 를 사용할 수도 있습니다.

IntStream.range(1, 10).filter(a -> a % 2 == 0).forEach(System.out::println); 

이데올로기에 살기

산출:

2
4
6
8

터미널 작업이 수행 된 후에는 Stream 이 소비되어 다시 사용할 수 없습니다.


주어진 스트림 객체를 재사용 할 수는 없지만 스트림 파이프 라인에 위임하는 재사용 가능한 Iterable 을 만드는 것은 쉽습니다. 결과를 임시 구조로 수집 할 필요없이 실시간 데이터 세트의 수정 된보기를 반환하는 데 유용 할 수 있습니다.

List<String> list = Arrays.asList("FOO", "BAR");
Iterable<String> iterable = () -> list.stream().map(String::toLowerCase).iterator();

for (String str : iterable) {
    System.out.println(str);
}
for (String str : iterable) {
    System.out.println(str);
}

산출:





이것은 Iterable 이 단일 추상 메소드 Iterator<T> iterator() 선언하기 때문에 작동합니다. 따라서 각 호출에서 새 스트림을 만드는 람다 (lambda)에 의해 구현되는 함수 인터페이스를 효과적으로 만들 수 있습니다.


일반적으로 Stream 은 다음 이미지와 같이 작동합니다.

스트림 동작

참고 : 인수 검사는 터미널 작업 없이도 항상 수행 됩니다 .

try {
    IntStream.range(1, 10).filter(null);
} catch (NullPointerException e) {
    System.out.println("We got a NullPointerException as null was passed as an argument to filter()");
}

이데올로기에 살기

산출:

filter ()에 인수로 null이 전달되면 NullPointerException이 발생합니다.

주파수 맵 생성

groupingBy(classifier, downstream) 콜렉터는 그룹의 각 요소를 분류하고 동일한 그룹으로 분류 된 요소에 대한 다운 스트림 조작을 수행하여 Stream 요소를 Map 으로 수집 할 수있게합니다.

이 원리의 고전적인 예는 Map 을 사용하여 Stream 에서 요소의 발생을 계산하는 것입니다. 이 예에서 분류자는 단순히 요소를 그대로 반환하는 식별 함수입니다. 다운 스트림 작업은 counting() 사용하여 동일한 요소의 수를 counting() 합니다.

Stream.of("apple", "orange", "banana", "apple")
      .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
      .entrySet()
      .forEach(System.out::println);

다운 스트림 조작 자체는 String 유형의 요소에서 작동하고 Long 유형의 결과를 생성하는 콜렉터 ( Collectors.counting() )입니다. collect 메소드 호출의 결과는 Map<String, Long> 입니다.

그러면 다음과 같은 출력이 생성됩니다.

바나나 = 1
오렌지 = 1
사과 = 2

병렬 스트림

참고 : 어떤 Stream 을 사용할 지 결정하기 전에 ParallelStream과 Sequential Stream 동작을 살펴보십시오.

Stream 작업을 동시에 수행하려면 이러한 방법 중 하나를 사용할 수 있습니다.

List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five");
Stream<String> aParallelStream = data.stream().parallel();

또는:

Stream<String> aParallelStream = data.parallelStream();

병렬 스트림에 대해 정의 된 작업을 실행하려면 터미널 연산자를 호출하십시오.

aParallelStream.forEach(System.out::println);

병렬 Stream 으로부터의 (가능한) 출력 :



하나

다섯

모든 요소가 병렬로 처리되면 순서가 변경 수 있습니다 (속도가 빨라질 수 있습니다). 순서가 중요하지 않은 경우에는 parallelStream 사용하십시오.

성능 영향

경우 네트워크가 관련된에서는 병렬 Stream 의 응용 프로그램의 전반적인 성능을 저하시킬 수 있기 때문에 모든 병렬 Stream 의 사용 네트워크에 대한 공통의 포크 - 조인 스레드 풀.

반면 병렬 Stream 은 현재 실행중인 CPU의 사용 가능한 코어 수에 따라 다른 많은 경우 성능이 크게 향상 될 수 있습니다.

선택적 스트림을 값 스트림으로 변환

당신은 변환해야 할 수 있습니다 Stream 방출 Optional A를 Stream 기존에서만 값을 방출 값의 Optional . (즉, null 값이없고 Optional.empty() 다루지 않음).

 Optional<String> op1 = Optional.empty();
 Optional<String> op2 = Optional.of("Hello World");

 List<String> result = Stream.of(op1, op2)
                             .filter(Optional::isPresent)
                             .map(Optional::get)
                             .collect(Collectors.toList());

 System.out.println(result); //[Hello World]

스트림 만들기

모든 Java Collection<E> 에는 Stream<E> 를 구축 할 수있는 stream()parallelStream() 메소드가 있습니다.

Collection<String> stringList = new ArrayList<>();
Stream<String> stringStream = stringList.parallelStream();

Stream<E> 는 다음 두 가지 방법 중 하나를 사용하여 배열에서 만들 수 있습니다.

String[] values = { "aaa", "bbbb", "ddd", "cccc" };
Stream<String> stringStream = Arrays.stream(values);
Stream<String> stringStreamAlternative = Stream.of(values);

Arrays.stream()Stream.of() 의 차이점은 Stream.of() 에 varargs 매개 변수가 있으므로 다음과 같이 사용할 수 있습니다.

Stream<Integer> integerStream = Stream.of(1, 2, 3);

또한 사용할 수있는 기본 Stream 이 있습니다. 예 :

IntStream intStream = IntStream.of(1, 2, 3);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);

이러한 기본 스트림은 Arrays.stream() 메서드를 사용하여 구성 할 수도 있습니다.

IntStream intStream = Arrays.stream(new int[]{ 1, 2, 3 });

지정된 범위의 배열에서 Stream 을 만들 수 있습니다.

int[] values= new int[]{1, 2, 3, 4, 5};
IntStream intStram = Arrays.stream(values, 1, 3);

임의의 프리미티브 스트림은 boxed 메서드를 사용하여 boxed 스트림으로 변환 될 수 있습니다.

Stream<Integer> integerStream = intStream.boxed();

원시 스트림에는 Collector 를 인수로 취하는 collect 메소드가 없기 때문에 데이터를 수집하려는 경우 유용 할 수 있습니다.

스트림 체인의 중간 작업 재사용

터미널 작업이 호출 될 때 스트림이 닫힙니다. 터미널 작업 만 다양 할 때 중간 작업 스트림을 다시 사용합니다. 우리는 모든 중간 작업이 이미 설정된 새 스트림을 생성하기 위해 스트림 공급자를 만들 수 있습니다.

Supplier<Stream<String>> streamSupplier = () -> Stream.of("apple", "banana","orange", "grapes", "melon","blueberry","blackberry")
.map(String::toUpperCase).sorted();

  streamSupplier.get().filter(s ->   s.startsWith("A")).forEach(System.out::println);

// APPLE

  streamSupplier.get().filter(s -> s.startsWith("B")).forEach(System.out::println);

  // BANANA
  // BLACKBERRY
  // BLUEBERRY

int[] 배열은 스트림을 사용하여 List<Integer> 로 변환 될 수 있습니다.

int[] ints = {1,2,3};
List<Integer> list = IntStream.of(ints).boxed().collect(Collectors.toList());

숫자 스트림에 대한 통계 찾기

Java 8은 count , min , max , sumaverage 와 같은 통계를 수집하기위한 상태 객체를 제공하는 IntSummaryStatistics , DoubleSummaryStatisticsLongSummaryStatistics 라는 클래스를 제공합니다.

Java SE 8
List<Integer> naturalNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = naturalNumbers.stream()
                                           .mapToInt((x) -> x)
                                           .summaryStatistics();
System.out.println(stats);

결과는 다음과 같습니다.

Java SE 8
IntSummaryStatistics{count=10, sum=55, min=1, max=10, average=5.500000}

스트림 조각 만들기

예 : 컬렉션의 21 번째에서 50 번째까지 포함 된 30 개 요소의 Stream 을 가져옵니다.

final long n = 20L; // the number of elements to skip
final long maxSize = 30L; // the number of elements the stream should be limited to
final Stream<T> slice = collection.stream().skip(n).limit(maxSize);

노트:

  • n 가 부의 경우, 또는 maxSize 가 부의 경우, IllegalArgumentException 가 Throw된다
  • skip(long)limit(long) 은 중간 연산입니다.
  • 스트림에 n 개 미만의 요소가 포함되어 있으면 skip(n) 은 빈 스트림을 반환합니다.
  • skip(long)limit(long) 은 순차적 인 스트림 파이프 라인에서 값싼 연산이지만, 정렬 된 병렬 파이프 라인에서는 상당히 비쌀 수 있습니다

스트림 연결

예를 들어 변수 선언 :

Collection<String> abc = Arrays.asList("a", "b", "c");
Collection<String> digits = Arrays.asList("1", "2", "3");
Collection<String> greekAbc = Arrays.asList("alpha", "beta", "gamma");

예제 1 - 두 개의 Stream 연결

final Stream<String> concat1 = Stream.concat(abc.stream(), digits.stream());

concat1.forEach(System.out::print);
// prints: abc123

예 2 - 세 개 이상의 Stream 연결

final Stream<String> concat2 = Stream.concat(
    Stream.concat(abc.stream(), digits.stream()),
    greekAbc.stream());

System.out.println(concat2.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

또는 중첩 된 concat() 구문을 단순화하기 위해 StreamflatMap() 과 연결할 수도 있습니다.

final Stream<String> concat3 = Stream.of(
    abc.stream(), digits.stream(), greekAbc.stream())
    .flatMap(s -> s);
    // or `.flatMap(Function.identity());` (java.util.function.Function)

System.out.println(concat3.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

깊은 연결 Stream 의 요소에 액세스하면 딥 콜 체인 또는 StackOverflowException 발생할 수 있으므로 반복 된 연결에서 Stream 생성 할 때는주의해야합니다.

IntStream에서 String으로

자바는 숯불 스트림을 가지고 있지 않기 때문에, 작업 할 때 String 의와 건설 StreamCharacter 들, 옵션은 얻을 것입니다 IntStream 사용하여 코드 포인트의 String.codePoints() 하는 방법. 그래서 IntStream 은 다음과 같이 얻을 수 있습니다 :

public IntStream stringToIntStream(String in) {
  return in.codePoints();
}

즉, IntStreamToString과 같은 다른 방법으로 변환을 수행하는 것은 좀 더 복잡합니다. 그것은 다음과 같이 할 수 있습니다 :

public String intStreamToString(IntStream intStream) {
  return intStream.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
}

스트림을 사용하여 정렬

List<String> data = new ArrayList<>();
data.add("Sydney");
data.add("London");
data.add("New York");
data.add("Amsterdam");
data.add("Mumbai");
data.add("California");

System.out.println(data);

List<String> sortedData = data.stream().sorted().collect(Collectors.toList());

System.out.println(sortedData);

산출:

[Sydney, London, New York, Amsterdam, Mumbai, California]
[Amsterdam, California, London, Mumbai, New York, Sydney]

비교자를 인수로 사용하는 오버로드 sorted 버전이 있으므로 다른 비교 메커니즘을 사용할 수도 있습니다.

또한 정렬을 위해 람다 식을 사용할 수 있습니다.

List<String> sortedData2 = data.stream().sorted((s1,s2) -> s2.compareTo(s1)).collect(Collectors.toList());

이것은 [Sydney, New York, Mumbai, London, California, Amsterdam]

Comparator.reverseOrder() 를 사용하면 Comparator.reverseOrder() , 자연 순서 부의 reverse 을 부과하는 콤퍼레이터를 가질 수가 있습니다.

List<String> reverseSortedData = data.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());

프리미티브의 스트림

Java는 IntStream ( int ), LongStream ( long ) 및 DoubleStream ( double 의 3 종류의 프리미티브 (primitive)에 대해 특수화 된 Stream 제공합니다. 각각의 프리미티브에 대한 최적화 된 구현 외에도, 일반적으로 수학 연산을위한 몇 가지 특정 터미널 방법을 제공합니다. 예 :

IntStream is = IntStream.of(10, 20, 30);
double average = is.average().getAsDouble(); // average is 20.0

배열로 스트림의 결과 수집

Analog는 collect() 의해 Stream 을위한 콜렉션을 얻으려고하는데, 배열은 Stream.toArray() 메소드로 얻을 수 있습니다 :

List<String> fruits = Arrays.asList("apple", "banana", "pear", "kiwi", "orange");

String[] filteredFruits = fruits.stream()
    .filter(s -> s.contains("a"))
    .toArray(String[]::new);     

// prints: [apple, banana, pear, orange]
System.out.println(Arrays.toString(filteredFruits));

String[]::new 는 특별한 종류의 메소드 참조입니다 : 생성자 참조.

술어와 일치하는 첫 번째 요소 찾기

조건과 일치하는 Stream 의 첫 번째 요소를 찾을 수 있습니다.

이 예제에서는 평방이 50000 넘는 첫 번째 Integer 를 찾습니다.

IntStream.iterate(1, i -> i + 1) // Generate an infinite stream 1,2,3,4...
    .filter(i -> (i*i) > 50000) // Filter to find elements where the square is >50000
    .findFirst(); // Find the first filtered element

이 표현식은 결과와 함께 OptionalInt 를 반환합니다.

무한 Stream 경우 Java는 결과를 찾을 때까지 각 요소를 계속 점검합니다. 유한 Stream 의 경우 Java에서 요소가 부족하지만 결과를 찾을 수없는 경우 빈 OptionalInt 반환합니다.

IntStream을 사용하여 인덱스 반복 수행

요소의 Stream 은 일반적으로 현재 항목의 색인 값에 대한 액세스를 허용하지 않습니다. 인덱스에 액세스하는 동안 배열이나 ArrayList 를 반복하려면 IntStream.range(start, endExclusive) 합니다.

String[] names = { "Jon", "Darin", "Bauke", "Hans", "Marc" };

IntStream.range(0, names.length)
    .mapToObj(i -> String.format("#%d %s", i + 1, names[i]))
    .forEach(System.out::println);

range(start, endExclusive) 메서드는 또 다른 ÌntStream 반환하고 mapToObj(mapper)String 스트림을 반환합니다.

산출:

# 1 Jon
# 2 Darin
# 3 Bauke
# 4 한스
# 5 Marc

이는 일반적인 for 루프를 카운터와 함께 사용하는 것과 매우 유사하지만 파이프 라이닝 및 병렬 처리의 이점이 있습니다.

for (int i = 0; i < names.length; i++) {
    String newName = String.format("#%d %s", i + 1, names[i]);
    System.out.println(newName);
}

flatMap ()으로 스트림 병합

순차적으로 스트리밍 가능한 항목의 Stream 은 단일 연속 Stream 으로 병합 될 수 있습니다.

항목 목록의 배열을 단일 목록으로 변환 할 수 있습니다.

List<String> list1 =  Arrays.asList("one", "two");
      List<String> list2 =  Arrays.asList("three","four","five");
      List<String> list3 =  Arrays.asList("six");
          List<String> finalList = Stream.of(list1, list2, list3).flatMap(Collection::stream).collect(Collectors.toList());
System.out.println(finalList);

// [one, two, three, four, five, six]

항목 목록을 값으로 포함하는지도를 결합 목록으로 병합 할 수 있습니다.

Map<String, List<Integer>> map = new LinkedHashMap<>();
map.put("a", Arrays.asList(1, 2, 3));
map.put("b", Arrays.asList(4, 5, 6));

List<Integer> allValues = map.values() // Collection<List<Integer>>
        .stream()                      // Stream<List<Integer>>
        .flatMap(List::stream)         // Stream<Integer>
        .collect(Collectors.toList());

System.out.println(allValues);
// [1, 2, 3, 4, 5, 6]

Map List 을 단일 연속 Stream 으로 병합 할 수 있습니다.

List<Map<String, String>> list = new ArrayList<>();
Map<String,String> map1 = new HashMap();
map1.put("1", "one");
map1.put("2", "two");

Map<String,String> map2 = new HashMap();
map2.put("3", "three");
map2.put("4", "four");
list.add(map1);
list.add(map2);


Set<String> output= list.stream()  //  Stream<Map<String, String>>
    .map(Map::values)              // Stream<List<String>>
    .flatMap(Collection::stream)   // Stream<String>
    .collect(Collectors.toSet());  //Set<String>
// [one, two, three,four]

스트림을 기반으로지도 만들기

중복 키가없는 단순한 케이스

Stream<String> characters = Stream.of("A", "B", "C");

Map<Integer, String> map = characters
            .collect(Collectors.toMap(element -> element.hashCode(), element -> element));
// map = {65=A, 66=B, 67=C}

더 선언적으로 만들려면 Function 인터페이스 - Function.identity() 에서 정적 메서드를 사용할 수 있습니다. 이 람다 element -> elementFunction.identity() 대체 할 수 있습니다.

중복 키가있는 경우

Collectors.toMap 대한 javadoc 은 다음과 같이 설명합니다.

맵 된 키에 Object.equals(Object) 에 따라 중복이 포함되는 경우, 콜렉션 조작의 실행시에 IllegalStateException 가 Throw됩니다. 매핑 된 키에 중복이있는 경우 대신 toMap(Function, Function, BinaryOperator) 사용하십시오.

Stream<String> characters = Stream.of("A", "B", "B", "C");

Map<Integer, String> map = characters
            .collect(Collectors.toMap(
                element -> element.hashCode(),
                element -> element,
                (existingVal, newVal) -> (existingVal + newVal)));

// map = {65=A, 66=BB, 67=C}

Collectors.toMap(...) 전달 된 BinaryOperator 는 충돌시 저장할 값을 생성합니다. 그것은 할 수있다 :

  • 스트림의 최초의 값이 우선하도록 (듯이), 낡은 값을 돌려줍니다.
  • 스트림의 마지막 값이 우선하도록 새 값을 반환하거나
  • 이전 값과 새 값을 결합하다

값으로 그룹화하기

계단식 데이터베이스 계단식 "그룹화"작업을 수행해야하는 경우 Collectors.groupingBy 를 사용할 수 있습니다. 예를 들어, 다음은 사람들의 이름이성에 매핑 된지도를 만듭니다.

List<Person> people = Arrays.asList(
    new Person("Sam", "Rossi"),
    new Person("Sam", "Verdi"),
    new Person("John", "Bianchi"),
    new Person("John", "Rossi"),
    new Person("John", "Verdi")
);

Map<String, List<String>> map = people.stream()
        .collect(
                // function mapping input elements to keys
                Collectors.groupingBy(Person::getName, 
                // function mapping input elements to values,
                // how to store values
                Collectors.mapping(Person::getSurname, Collectors.toList()))
        );

// map = {John=[Bianchi, Rossi, Verdi], Sam=[Rossi, Verdi]}

이데올로기에 살기

스트림을 사용하여 임의의 문자열 생성

웹 서비스의 경우 세션 ID 또는 응용 프로그램 등록 후 초기 암호로 임의의 Strings 을 만드는 것이 유용 할 때도 있습니다. 이 작업은 Stream 을 사용하여 쉽게 수행 할 수 있습니다.

먼저 난수 생성기를 초기화해야합니다. 생성 된 String 의 보안을 높이려면 SecureRandom 을 사용하는 것이 좋습니다.

: SecureRandom 작성하는 것은 꽤 비싸므로, 한번만 이것을 setSeed() 수시로 setSeed() 메소드의 1 개를 호출 해 재 시드하는 것이 가장 좋은 방법입니다.

private static final SecureRandom rng = new SecureRandom(SecureRandom.generateSeed(20)); 
//20 Bytes as a seed is rather arbitrary, it is the number used in the JavaDoc example

임의의 String 생성 할 때, 일반적으로, 특정의 문자 (문자 및 숫자 만)를 사용하는 것을 원합니다. 따라서 나중에 Stream 을 필터링하는 데 사용할 수있는 boolean 을 반환하는 메서드를 만들 수 있습니다.

//returns true for all chars in 0-9, a-z and A-Z
boolean useThisCharacter(char c){
    //check for range to avoid using all unicode Letter (e.g. some chinese symbols)
    return c >= '0' && c <= 'z' && Character.isLetterOrDigit(c);
}

다음으로 RNG를 사용하여 useThisCharacter 검사를 통과하는 charset을 포함하는 특정 길이의 임의의 String을 생성 할 수 있습니다.

public String generateRandomString(long length){
    //Since there is no native CharStream, we use an IntStream instead
    //and convert it to a Stream<Character> using mapToObj.
    //We need to specify the boundaries for the int values to ensure they can safely be cast to char
    Stream<Character> randomCharStream = rng.ints(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT).mapToObj(i -> (char)i).filter(c -> this::useThisCharacter).limit(length);

    //now we can use this Stream to build a String utilizing the collect method.
    String randomString = randomCharStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
    return randomString;
}

스트림을 사용하여 수학 함수 구현

Stream 의, 특히 IntStream 들, 합계 용어 (Σ)를 구현하는 우아한 방법입니다. Stream 의 범위는 합계의 범위로 사용할 수 있습니다.

예를 들어, Madhava의 근사 Pi는 수식 (출처 : wikipedia )으로 표시됩니다. Madhava의 근사치

이것은 임의의 정밀도로 계산 될 수 있습니다. 예를 들어, 101 용어 :

double pi = Math.sqrt(12) * 
            IntStream.rangeClosed(0, 100)
                     .mapToDouble(k -> Math.pow(-3, -1 * k) / (2 * k + 1))
                     .sum();

참고 : double 의 정밀도로 29의 상한선을 선택하면 Math.Pi 구별 할 수없는 결과를 얻을 수 있습니다.

스트림 및 메소드 참조를 사용하여 자체 기록 프로세스 작성

와 방법 참조는 우수한 자기 문서화 코드를, 그리고 사용 방법 참조를 만들 Stream 의 읽고 이해하기 간단한 복잡한 프로세스를합니다. 다음 코드를 살펴보십시오.

public interface Ordered {
    default int getOrder(){
        return 0;
    }
}

public interface Valued<V extends Ordered> {
    boolean hasPropertyTwo();
    V getValue();
}

public interface Thing<V extends Ordered> {
    boolean hasPropertyOne();
    Valued<V> getValuedProperty();
}

public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
    List<V> results = new ArrayList<V>();
    for (Thing<V> thing : things) {
        if (thing.hasPropertyOne()) {
            Valued<V> valued = thing.getValuedProperty();
            if (valued != null && valued.hasPropertyTwo()){
                V value = valued.getValue();
                if (value != null){
                    results.add(value);
                }
            }
        }
    }
    results.sort((a, b)->{
        return Integer.compare(a.getOrder(), b.getOrder());
    });
    return results;
}

Stream 과 메서드 참조를 사용하여 다시 작성된이 마지막 메서드는 훨씬 더 읽기 쉽고 프로세스의 각 단계를 빠르고 쉽게 이해할 수 있습니다. 더 짧은 것은 아니며 각 단계의 코드를 담당하는 인터페이스와 클래스를 한눈에 알 수 있습니다.

public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
    return things.stream()
        .filter(Thing::hasPropertyOne)
        .map(Thing::getValuedProperty)
        .filter(Objects::nonNull)
        .filter(Valued::hasPropertyTwo)
        .map(Valued::getValue)
        .filter(Objects::nonNull)
        .sorted(Comparator.comparing(Ordered::getOrder))
        .collect(Collectors.toList());
}    

Map.Entry의 스트림을 사용하여 매핑 후 초기 값 보존

맵핑해야 할 Stream 이 있지만 초기 값도 보존하려는 경우 다음과 같은 유틸리티 메소드를 사용하여 StreamMap.Entry<K,V> 매핑 할 수 있습니다.

public static <K, V> Function<K, Map.Entry<K, V>> entryMapper(Function<K, V> mapper){
    return (k)->new AbstractMap.SimpleEntry<>(k, mapper.apply(k));
}

그런 다음 변환기를 사용하여 원래 값과 매핑 된 값에 모두 액세스 할 수있는 Stream 을 처리 할 수 ​​있습니다.

Set<K> mySet;
Function<K, V> transformer = SomeClass::transformerMethod;
Stream<Map.Entry<K, V>> entryStream = mySet.stream()
    .map(entryMapper(transformer));

그런 다음 해당 Stream 을 정상적으로 계속 처리 할 수 ​​있습니다. 이렇게하면 중간 컬렉션을 만드는 오버 헤드를 피할 수 있습니다.

스트림 작업 범주

스트림 작업은 중간 및 터미널 작업의 두 가지 주요 범주와 상태 비 저장 및 상태 저장이라는 두 가지 하위 범주로 분류됩니다.


중급 운영 :

중간 작업은 간단한 Stream.map 과 같이 항상 게으른 작업입니다. 스트림이 실제로 소비 될 때까지는 불려 가지 않습니다. 이는 쉽게 검증 할 수 있습니다.

Arrays.asList(1, 2 ,3).stream().map(i -> {
    throw new RuntimeException("not gonna happen");
    return i;
});

중개 연산은 스트림의 공통 빌딩 블록으로, 소스 뒤에 연결되며 대개 스트림 체인을 트리거하는 터미널 작업이 뒤 따른다.


터미널 운영

터미널 작업은 스트림 소비를 유발하는 요소입니다. 좀 더 일반적인 것은 Stream.forEach 또는 Stream.collect 입니다. 그것들은 일반적으로 중개 작업의 사슬을 따라 배치되며 거의 항상 열심 입니다.


무국적 운영

무국적자 란 각 항목이 다른 항목의 컨텍스트없이 처리된다는 의미입니다. Stateless 연산은 메모리 효율적인 스트림 처리를 가능하게합니다. 같은 운영 Stream.mapStream.filter 스트림의 다른 항목에 대한 정보를 필요로하지 않는 비 상태로 간주됩니다.


상태 저장 작업

Statefulness는 각 항목의 작업이 스트림의 다른 항목에 의존한다는 것을 의미합니다. 이렇게하려면 상태를 보존해야합니다. 상태 보존 조작은 길거나 무한한 스트림으로 중단 될 수 있습니다. Stream.sorted 와 같은 작업을 수행하면 항목이 방출되기 전에 스트림 전체가 처리되어야하므로 충분히 길어진 항목 스트림에서 중단됩니다. 이것은 ( 자신의 책임하에 실행 되는) 긴 스트림으로 증명할 수 있습니다.

// works - stateless stream
long BIG_ENOUGH_NUMBER = 999999999;
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).forEach(System.out::println);

이로 인해 Stream.sorted 상태 저장으로 인해 메모리 부족이 발생합니다.

// Out of memory - stateful stream
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).sorted().forEach(System.out::println);

이터레이터를 스트림으로 변환하기

반복기를 스트림으로 변환하려면 Spliterators.spliterator() 또는 Spliterators.spliteratorUnknownSize() 를 사용하십시오.

Iterator<String> iterator = Arrays.asList("A", "B", "C").iterator();    
Spliterator<String> spliterator = Spliterators.spliteratorUnknownSize(iterator, 0);
Stream<String> stream = StreamSupport.stream(spliterator, false);

스트림으로 축소

Reduction은 스트림의 모든 요소에 이항 연산자를 적용하여 하나의 값을 얻는 프로세스입니다.

IntStreamsum() 메서드는 감소의 예입니다. 스트림의 모든 항목에 덧셈을 적용하여 하나의 최종 값을 얻습니다. 합계 감소

이것은 (((1+2)+3)+4)

스트림의 reduce 메소드를 사용하면 사용자 정의 축소를 작성할 수 있습니다. reduce 메서드를 사용하여 sum() 메서드를 구현할 수 있습니다.

IntStream istr;
    
//Initialize istr
    
OptionalInt istr.reduce((a,b)->a+b);

빈 버전의 스트림을 적절하게 처리 할 수 ​​있도록 Optional 버전이 리턴됩니다.

축소의 또 다른 예는 Stream<LinkedList<T>> 를 단일 LinkedList<T> .

Stream<LinkedList<T>> listStream;
    
//Create a Stream<LinkedList<T>>
    
Optional<LinkedList<T>> bigList = listStream.reduce((LinkedList<T> list1, LinkedList<T> list2)->{
    LinkedList<T> retList = new LinkedList<T>();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

신원 요소를 제공 할 수도 있습니다. 예를 들어, 덧셈의 항등 요소는 x+0==x 와 같이 x+0==x 입니다. 곱셈의 경우, identity 요소는 1이고 x*1==x 입니다. 위의 경우, 빈 목록을 다른 목록에 추가하면 "추가"하는 목록이 변경되지 않기 때문에 identity 요소는 빈 LinkedList<T> .

Stream<LinkedList<T>> listStream;

//Create a Stream<LinkedList<T>>

LinkedList<T> bigList = listStream.reduce(new LinkedList<T>(), (LinkedList<T> list1, LinkedList<T> list2)->{
    LinkedList<T> retList = new LinkedList<T>();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

identity 요소가 제공되면 반환 값은 빈 스트림에 대해 호출 된 Optional If로 래핑되지 않습니다. reduce() 는 identity 요소를 반환합니다.

이진 연산자는 연관성이 있어야합니다. 즉, (a+b)+c==a+(b+c) 합니다. 이는 요소가 임의의 순서로 감소 될 수 있기 때문입니다. 예를 들어, 위의 추가 감소는 다음과 같이 수행 될 수 있습니다.

기타 합계 감소

이 감소는 ((1+2)+(3+4)) 쓰기와 동일합니다. 또한 연관성의 속성을 통해 Java는 Stream 을 병렬로 줄일 수 있습니다. 스트림의 일부는 각 프로세서에서 줄여 줄 수 있으며 결과적으로 각 프로세서의 결과가 결합됩니다.

스트림을 단일 문자열에 조인하기

빈번히 발생하는 유스 케이스는 스트림에서 String 을 생성하는 것으로 스트림 항목은 특정 문자로 구분됩니다. 다음 예제와 같이 Collectors.joining() 메서드를 사용할 수 있습니다.

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

String result = fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .collect(Collectors.joining(", "));
           
System.out.println(result);

산출:

APPLE, BANANA, ORANGE, PEAR

Collectors.joining() 메서드는 prefix와 postfix를 처리 할 수 ​​있습니다.

String result = fruitStream.filter(s -> s.contains("e"))
           .map(String::toUpperCase)
           .sorted()
           .collect(Collectors.joining(", ", "Fruits: ", "."));
           
System.out.println(result);

산출:

과일 : APPLE, ORANGE, PEAR.

이데올로기에 살기



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow