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.