Java Language
스트림
수색…
소개
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);
산출:
사과
바나나
주황색
배
위의 코드에 의해 수행 된 작업은 다음과 같이 요약 할 수 있습니다.
정적 팩토리 메소드
Stream.of(values)사용하여 시퀀스 된 순서가 지정된 과일String요소Stream을 포함하는Stream<String>을 만듭니다.filter()연산은 주어진 술어와 일치하는 요소들만을 유지한다 (술어에 의해 테스트 될 때 참을 반환하는 요소). 이 경우"a"포함 된 요소는 유지됩니다. 술어는 람다 식으로 주어집니다.map()연산은 매퍼 (mapper)라고 불리는 주어진 함수를 사용하여 각 요소를 변환합니다. 이 경우, 각 fruitString은 method-referenceString::toUppercase사용하여 대문자String버전에 매핑됩니다.점을 유의
map()매핑 기능은 입력 매개 변수에 대한 다른 유형을 반환하는 경우 작업이 다른 제네릭 형식으로 스트림을 반환합니다. 예를 들어Stream<String>.map(String::isEmpty)호출하면Stream<Boolean>sorted()오퍼레이션은, 자연 순서 부에 따라Stream의 요소를sorted()String의 경우, 사전 식으로).마지막으로,
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 은 요소에 직접 액세스 및 조작을 제공하지 않습니다. 그것은 집단 엔티티로서의 객체 그룹에 더 전념하고 그 엔티티 전체에 대한 연산을 수행한다. Stream 과 Collection 은 이러한 다양한 목적을 위해 별도의 고수준 추상화입니다.
콜렉션에 스트림 요소 수집
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 무한 루프를 입력한다. Stream 의 limit 메소드는, 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);
스트림 소비
Stream 은 count() , 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 , sum 및 average 와 같은 통계를 수집하기위한 상태 객체를 제공하는 IntSummaryStatistics , DoubleSummaryStatistics 및 LongSummaryStatistics 라는 클래스를 제공합니다.
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);
결과는 다음과 같습니다.
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() 구문을 단순화하기 위해 Stream 을 flatMap() 과 연결할 수도 있습니다.
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 의와 건설 Stream 의 Character 들, 옵션은 얻을 것입니다 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 -> element 를 Function.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 )으로 표시됩니다.
이것은 임의의 정밀도로 계산 될 수 있습니다. 예를 들어, 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 이 있지만 초기 값도 보존하려는 경우 다음과 같은 유틸리티 메소드를 사용하여 Stream 을 Map.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.map 및 Stream.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은 스트림의 모든 요소에 이항 연산자를 적용하여 하나의 값을 얻는 프로세스입니다.
IntStream 의 sum() 메서드는 감소의 예입니다. 스트림의 모든 항목에 덧셈을 적용하여 하나의 최종 값을 얻습니다.
이것은 (((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.


