주의!
여기서 말하는 '스트림' 은 Java 8 에서부터 추가된 스트림 API 를 뜻한다.
'입출력 스트림'과는 전혀 다른 개념이다.
자바 8 이전에는 배열 또는 Collection 인스턴스를 for 또는 foreach 문을 돌면서 요소 하나씩을 꺼내서 다루는 방법을 사용
자바 8부터 Stream API와 람다식, 함수형 인터페이스 등...을 지원하면서, Java를 이용해 함수형으로 프로그래밍할 수 있는 API 들을 제공해주고 있다.
(원래 Java는 객체지향 언어이기 때문에, 기본적으로 함수형 프로그래밍이 불가능)
람다를 활용할 수 있는 기술 中 하나
Stream 연산들은 매개변수로 함수형 인터페이스를 받고,
람다식은 반환값으로 함수형 인터페이스를 반환한다.
스트림은 한 요소씩 수직적으로(vertically) 실행된다.
참고: 자바 컬렉션 (Java Collection)
참고: 람다식(람다 표현식, Lambda Expression)
List<String> sortedList = nameStream.sorted()
.collect(Collections.toList());
Stream API는 원본의 데이터를 조회하여, 원본의 데이터가 아닌 별도의 요소들로 Stream을 생성
→ 원본의 데이터로부터 읽기만 할 뿐
→ 정렬이나 필터링 등...의 작업은 별도의 Stream 요소들에서 처리
userStream.sorted().forEach(System.out::print);
// 스트림이 이미 사용되어 닫혔으므로 에러 발생
int count = userStream.count();
// IllegalStateException 발생
java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
// 반복문이 forEach라는 함수 내부에 숨겨져 있다.
nameStream.forEach(System.out::println);
병렬처리(multi-threading)가 가능하다
참고: 운영체제 (OS) - 3) 멀티 스레드 (Multi Thread)
Iterator 를 사용할 경우
ArrayList<Integer> list = new ArrayList<Integer>(Arrays.asList(1,2,3));
Iterator<Integer> iter = list.iterator();
while(iter.hasNext()) {
int num = iter.next();
System.out.println("값 : "+num);
}
자바 6이전까지는 ArrayList에서 요소를 순차적으로 처리하기 위해, Iterator라는 반복자를 사용했다.
Stream 을 사용할 경우
ArrayList<Integer> list = new ArrayList<Integer>(Arrays.asList(1,2,3));
Stream<Integer> stream = list.stream(); // 1. stream()메소드로 스트림 객체를 얻은 후
stream.forEach(num -> System.out.println("값 : "+num)); // 2. ArrayList에 있는 요소들을 하나씩 출력
람다식으로 기술 된 부분에는 Stream 이 들어가는 부분이 많다.
장점
데이터를 직접 정렬할 경우
String[] nameArr = {"IronMan", "Captain", "Hulk", "Thor"}
List<String> nameList = Arrays.asList(nameArr);
// 원본의 데이터가 직접 정렬됨
Arrays.sort(nameArr);
Collections.sort(nameList);
for (String str: nameArr) {
System.out.println(str);
}
for (String str : nameList) {
System.out.println(str);
}
Stream 을 사용한 코드
String[] nameArr = {"IronMan", "Captain", "Hulk", "Thor"}
List<String> nameList = Arrays.asList(nameArr);
// 원본의 데이터가 아닌 별도의 Stream을 생성함
Stream<String> nameStream = nameList.stream();
Stream<String> arrayStream = Arrays.stream(nameArr);
// 복사된 데이터를 정렬하여 출력함
nameStream.sorted().forEach(System.out::println);
arrayStream.sorted().forEach(System.out::println);
list.stream()
.filter(el -> {
System.out.println("filter() was called.");
return el.contains("a");
})
.map(el -> {
System.out.println("map() was called.");
return el.toUpperCase();
})
.findFirst();
filter() was called.
filter() was called.
map() was called.
요소는 3개 : filter 두 번, map 이 한 번 출력된다.
모든 요소가 첫 번째 중간 연산을 수행하고 남은 결과가 다음 연산으로 넘어가는 것이 아니라
한 요소가 모든 파이프라인을 거쳐서 결과를 만들어내고, 다음 요소로 넘어가는 순
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
.stream() // 생성하기
.filter(s -> s.startsWith("c")) // 가공하기 : 데이터 필터링
.map(String::toUpperCase) // 가공하기 : 데이터 변형
.sorted() // 가공하기 : 데이터 정렬
.count();
Stream은 데이터를 처리하기 위해 다양한 연산들을 지원한다.
스트림에 대한 연산은 생성하기, 가공하기, 결과 만들기 3가지 단계로 나눌 수 있다.
// List로부터 스트림을 생성
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> listStream = list.stream();
Collection 인터페이스에는 stream()이 정의되어 있기 때문에,
Collection 인터페이스를 구현한 객체들(List, Set 등)은 모두 이 메소드를 이용해 Stream을 생성할 수 있다.
→ stream() : 인터페이스에 추가된 디폴트 메소드
stream()을 사용하면, 해당 Collection의 객체를 소스로 하는 Stream을 반환한다.
// 배열에서 스트림을 생성
Stream<String> stream1 = Arrays.stream(arr);
Stream<String> stream = Stream.of(new String[] {"a", "b", "c"});
Stream<String> stream = Arrays.stream(new String[] {"a", "b", "c"});
Stream<String> stream = Arrays.stream(new String[] {"a", "b", "c"}, 0, 3); // end범위 포함 x
// 배열의 특정 부분만을 이용한 스트림을 생성
Stream<String> stream2 = Arrays.stream(arr, 1, 3);
Stream<String> stream = Stream.of("a", "b", "c");
// range() : 4이상 10 이하의 숫자를 갖는 IntStream
IntStream stream = IntStream.range(4, 10);
// rangeClosed()
IntStream stream2 = IntStream.rangeClosed(1, 4);
// 필요한 경우 boxed 메소드를 이용해서 박싱(boxing)할 수 있다.
Stream<Integer> boxedIntStream = IntStream.range(1, 5).boxed();
(객체를 위한 Stream 외에도) int, long, double 같은 원시 자료형들을 사용하기 위한 특수한 종류의 Stream(IntStream, LongStream, DoubleStream) 들도 사용 가능
제네릭을 사용하지 않기 때문에, 불필요한 오토박싱(auto-boxing)이 일어나지 않는다.
(단, 필요한 경우 boxed 메소드를 이용해서 박싱(boxing)할 수 있다.)
IntStream stream = new Random().ints(4);
// iterate() 메소드 예시 1
IntStream stream = Stream.iterate(2, n -> n + 2); // 2, 4, 6, 8, 10, ...
// iterate() 메소드 예시 2
Stream<Integer> iteratedStream = Stream.iterate(30, n -> n + 2).limit(5); // [30, 32, 34, 36, 38]
// 30이 초기값이고 값이 2씩 증가하는 값
// generate() 메소드
Stream<String> generatedStream = Stream.generate(() -> "gen").limit(5); // [el, el, el, el, el]
// 5개의 “gen” 이 들어간 스트림이 생성됨
iterate()
generate()
String<String> stream = Files.lines(Path path);
// 예시 1
Stream<Object> stream = Stream.empty();
// 예시 2
public Stream<String> streamOf(List<String> list) {
return list == null || list.isEmpty()
? Stream.empty()
: list.stream();
}
Stream<String> builderStream =
Stream.<String>builder()
.add("Eric").add("Elena").add("Java")
.build(); // [Eric, Elena, Java]
// 스트링의 각 문자(char)를 IntStream 으로 변환
IntStream charsStream = "Stream".chars(); // [83, 116, 114, 101, 97, 109]
// 정규표현식(RegEx)을 이용해서 문자열을 자르고, 각 요소들로 스트림을 만듦
Stream<String> stringStream = Pattern.compile(", ").splitAsStream("Eric, Elena, Java"); // [Eric, Elena, Java]
// 병렬 스트림 생성
Stream<Product> parallelStream = productList.parallelStream();
// 병렬 여부 확인
boolean isParallel = parallelStream.isParallel();
boolean isMany = parallelStream
.map(product -> product.getAmount() * 10)
.anyMatch(amount -> amount > 200);
Arrays.stream(arr).parallel();
IntStream intStream = IntStream.range(1, 150).parallel();
boolean isParallel = intStream.isParallel();
// 시퀀셜(sequential) 모드로 돌리고 싶다면, sequential 메소드를 사용
IntStream intStream = intStream.sequential();
boolean isParallel = intStream.isParallel();
Stream<String> stream1 = Stream.of("Java", "Scala", "Groovy");
Stream<String> stream2 = Stream.of("Python", "Go", "Swift");
Stream<String> concat = Stream.concat(stream1, stream2);
// [Java, Scala, Groovy, Python, Go, Swift]
public <T> Stream<T> collectionToStream(Collection<T> collection) {
return Optional
.ofNullable(collection) // 인자로 받은 컬렉션 객체를 이용해 옵셔널 객체를 만들고
.map(Collection::stream) // 스트림을 생성후 리턴
.orElseGet(Stream::empty); // 만약 컬렉션이 비어있는 경우라면, 빈 스트림을 리턴
}
// 예시
List<Integer> intList = Arrays.asList(1, 2, 3);
List<String> strList = Arrays.asList("a", "b", "c");
Stream<Integer> intStream =
collectionToStream(intList); // [1, 2, 3]
Stream<String> strStream =
collectionToStream(strList); // [a, b, c]
NullPointerException 예외가 발생한다면?
→ Optional 을 이용해서 null에 안전한(Null-safe) 스트림을 생성할 수 있다.
제네릭을 이요하므로, 어떤 타입이든 받을 수 있다.
// 예시 1
Stream<String> stream =
list.stream()
.filter(name -> name.contains("a")); // String의 stream에서 a가 들어간 문자열만을 포함하도록 필터링
// 예시 2
Stream<String> stream =
names.stream()
.filter(name -> name.contains("a")); // [Elena, Java]
스트림 내 요소들을 하나씩 평가해서 걸러내는 작업
Stream에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어내는 연산
Java에서는 filter 함수의 인자로 함수형 인터페이스 Predicate를 받고 있기 때문에, boolean을 반환하는 람다식을 작성하여 filter 함수를 구현할 수 있다.
// 예시 1 : 스트림 내 String 의 toUpperCase 메소드를 실행해서 대문자로 변환한 값들이 담긴 스트림을 리턴
Stream<String> stream =
names.stream()
.map(s -> s.toUpperCase()); // [ERIC, ELENA, JAVA]
// 예시 2 : 요소 내 들어있는 Product 개체의 수량을 꺼내올 수도 있다. 각 ‘상품’을 ‘상품의 수량’으로 맵핑
Stream<Integer> stream =
productList.stream()
.map(Product::getAmount); // [23, 14, 13, 23, 13]
기존의 Stream 요소들을 변환하여, 새로운 Stream을 형성하는 연산
스트림 내 요소들을 하나씩 특정 값으로 변환
→ 값을 변환하기 위한 람다를 인자로 받는다.
Java에서는 map 함수의 인자로 함수형 인터페이스 function을 받고 있다.
Stream<File> fileStream = Stream.of(new File("Test1.java"), new File("Test2.java"), new File("Test3.java"));
// 파일의 Stream 을 파일 이름의 Stream 으로 변경
// Stream<File> --> Stream<String> 변환
Stream<String> fileNameStream = fileStream.map(File::getName);
// 중첩된 리스트가 있다.
List<List<String>> list =
Arrays.asList(Arrays.asList("a"),
Arrays.asList("b")); // [[a], [b]]
// 이를 flatMap을 사용해서 중첩 구조를 제거한 후 작업
List<String> flatList =
list.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()); // [a, b]
// 만약, flatMap을 객체에 적용할 경우 : 학생 객체를 가진 스트림에서 학생의 국영수 점수를 뽑아 새로운 스트림을 만들어 평균을 구한다
students.stream()
.flatMapToInt(student ->
IntStream.of(student.getKor(),
student.getEng(),
student.getMath()))
.average().ifPresent(avg ->
System.out.println(Math.round(avg * 10)/10.0));
// 인자 없이 그냥 호출할 경우 : 오름차순으로 정렬
IntStream.of(14, 11, 20, 39, 23)
.sorted()
.boxed()
.collect(Collectors.toList()); // [11, 14, 20, 23, 39]
List<String> list = Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");
// 스트링 리스트에서 알파벳 순으로 정렬한 코드
Stream<String> stream = list.stream()
.sorted() // [Go, Groovy, Java, Python, Scala, Swift]
// Comparator 를 넘겨서 역순으로 정렬한 코드
Stream<String> stream = list.stream()
.sorted(Comparator.reverseOrder()) // [Swift, Scala, Python, Java, Groovy, Go]
Stream의 요소들을 정렬하기 위해서는 sorted 를 사용
파라미터로 Comparator 를 넘길 수도 있다.
lang.stream()
.sorted(Comparator.comparingInt(String::length))
.collect(Collectors.toList()); // [Go, Java, Scala, Swift, Groovy, Python]
lang.stream()
.sorted((s1, s2) -> s2.length() - s1.length())
.collect(Collectors.toList()); // [Groovy, Python, Scala, Swift, Java, Go]
클래스가 하나 존재한다.
public class Employee {
private String name;
public Employee(String name) {
this.name = name;
}
public String getName() {
return name;
}
// equals() 오버라이드
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return Objects.equals(name, employee.name);
}
// hashCode() 오버라이드
@Override
public int hashCode() {
return Objects.hash(name);
}
}
주의!
생성한 클래스를 Stream으로 사용할 경우, equals와 hashCode를 오버라이드 해야만 distinct()를 제대로 적용할 수 있다.
중복을 제거하기 위해 distinct를 사용
public class Main {
public static void main(String[] args) {
Employee e1 = new Employee("MangKyu");
Employee e2 = new Employee("MangKyu");
List<Employee> employees = new ArrayList<>();
employees.add(e1);
employees.add(e2);
int size = employees.stream().distinct().collect(Collectors.toList()).size();
System.out.println(size);
}
}
int sum = IntStream.of(1, 3, 5, 7, 9)
.peek(System.out::println)
.sum();
"peek" = "확인해본다"
Stream의 요소들을 대상으로 Stream에 영향을 주지 않고, 특정 연산을 수행하기 위한 함수
→ Stream의 각각의 요소들에 대해 특정 작업을 수행할 뿐
→ 결과에 영향을 주지 않는다.
peek 함수는 파라미터로 함수형 인터페이스 Consumer를 인자로 받는다.
예시 : 작업을 처리하는 중간에 결과를 확인해볼 때 사용
// IntStream -> Stream<Integer>
IntStream.range(1, 4)
.mapToObj(i -> "a" + i)
// Stream<Double> -> IntStream -> Stream<String>
Stream.of(1.0, 2.0, 3.0)
.mapToInt(Double::intValue)
.mapToObj(i -> "a" + i)
OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min();
int max = IntStream.of().max().orElse(0);
IntStream.of(1, 3, 5, 7, 9).average().ifPresent(System.out::println);
long count = IntStream.of(1, 3, 5, 7, 9).count();
long sum = LongStream.of(1, 3, 5, 7, 9).sum();
collect() : 스트림의 최종연산, 매개변수로 Collector를 필요로 한다.
Collector : 인터페이스, collect의 파라미터는 이 인터페이스를 구현해야한다.
Collectors : 클래스, static메소드로 미리 작성된 컬렉터를 제공한다.
// collect의 파라미터로 Collector의 구현체가 와야 한다.
Object collect(Collector collector)
Stream의 요소들을 List나 Set, Map, 등... 다른 종류의 결과로 수집하고 싶은 경우에는 collect 함수를 이용할 수 있다.
collect 함수는 어떻게 Stream의 요소들을 수집할 것인가를 정의한 Collector 타입을 인자로 받아서 처리
// Stream의 요소들을 Product의 이름으로 변환하여, 그 결과를 List로 반환받고 있다.
List<String> nameList = productList.stream()
.map(Product::getName)
.collect(Collectors.toList());
Collectors.joining()
String listToString = productList.stream()
.map(Product::getName)
.collect(Collectors.joining());
// potatoesorangelemonbreadsugar
String listToString = productList.stream()
.map(Product::getName)
.collect(Collectors.joining(" "));
// potatoes orange lemon bread sugar
String listToString = productList.stream()
.map(Product::getName)
.collect(Collectors.joining(", ", "<", ">"));
// <potatoes, orange, lemon, bread, sugar>
Collectors.averagingInt(), Collectors.summingInt()
Double averageAmount = productList.stream()
.collect(Collectors.averagingInt(Product::getAmount));
// collect 사용
Integer summingAmount = productList.stream()
.collect(Collectors.summingInt(Product::getAmount)); // 86
// mapToInt 사용 : collect 보다 더 간단하게 표현 가능
Integer summingAmount = productList.stream()
.mapToInt(Product::getAmount)
.sum(); // 86
Collectors.summarizingInt()
// IntSummaryStatistics 객체가 반환되며, 필요한 값에 대해 get 메소드를 이용하여 원하는 값을 꺼내면 된다
IntSummaryStatistics statistics = productList.stream()
.collect(Collectors.summarizingInt(Product::getAmount));
//IntSummaryStatistics {count=5, sum=86, min=13, average=17.200000, max=23}
3과 같이 작업한 결과의 평균값이나 총합 등...을 구할 경우
& 1개의 Stream으로부터 갯수, 합계, 평균, 최댓값, 최솟값을 한번에 얻고 싶은 경우에 사용
IntSummaryStatistics 객체에는 다음과 같은 정보가 담겨 있다.
개수: getCount()
합계: getSum()
평균: getAverage()
최소: getMin()
최대: getMax()
Collectors.groupingBy()
Map<Integer, List<Product>> collectorMapOfLists = productList.stream()
.collect(Collectors.groupingBy(Product::getAmount));
/* 결과는 Map 타입으로 나온다
{23=[Product{amount=23, name='potatoes'}, Product{amount=23, name='bread'}],
13=[Product{amount=13, name='lemon'}, Product{amount=13, name='sugar'}],
14=[Product{amount=14, name='orange'}]}
*/
Collectors.partitioningBy()
Map<Boolean, List<Product>> mapPartitioned = productList.stream()
.collect(Collectors.partitioningBy(p -> p.getAmount() > 15));
/* 평가를 하는 함수 boolean 를 통해서, 스트림 내 요소들을 true 와 false 두 가지로 나눌 수 있다.
{false=[Product{amount=14, name='orange'}, Product{amount=13, name='lemon'}, Product{amount=13, name='sugar'}],
true=[Product{amount=23, name='potatoes'}, Product{amount=23, name='bread'}]}
*/
// 결과를 Set 으로 collect 한 후, 수정 불가한 Set 으로 변환하는 작업을 추가로 실행
Set<Product> unmodifiableSet =
productList.stream()
.collect(Collectors.collectingAndThen(Collectors.toSet(),
Collections::unmodifiableSet));
public static<T, R> Collector<T, R, R> of(
Supplier<R> supplier, // new collector 생성
BiConsumer<R, T> accumulator, // 두 값을 가지고 계산 → reduce 에서 살펴본 내용과 동일
BinaryOperator<R> combiner, // 계산한 결과를 수집하는 함수 → reduce 에서 살펴본 내용과 동일
Characteristics... characteristics) { ... }
// 예시
Collector<Product, ?, LinkedList<Product>> toLinkedList =
Collector.of(LinkedList::new, // collector 를 하나 생성 + 컬렉터를 생성하는 supplier 에 LinkedList 의 생성자를 넘겨준다
LinkedList::add, // accumulator 에는 리스트에 추가하는 add 메소드를 넘겨준다
(first, second) -> {
first.addAll(second); // combiner 를 이용해 결과를 조합 → 생성된 리스트들을 하나의 리스트로 합친다.
return first;
});
// collect 메소드에 커스텀 컬렉터를 넘겨줄 수 있고, 결과가 담긴 LinkedList 가 반환된다.
LinkedList<Product> linkedListOfPersons =
productList.stream()
.collect(toLinkedList);
// 모두 true 를 반환
List<String> names = Arrays.asList("Eric", "Elena", "Java");
boolean anyMatch = names.stream()
.anyMatch(name -> name.contains("a"));
boolean allMatch = names.stream()
.allMatch(name -> name.length() > 3);
boolean noneMatch = names.stream()
.noneMatch(name -> name.endsWith("s"));
Stream의 요소들이 특정한 조건을 충족하는지 검사하고 싶은 경우에 사용
함수형 인터페이스 Predicate를 받아서 해당 조건을 만족하는지 검사
검사 결과를 boolean으로 반환
match 함수
names.stream()
.forEach(System.out::println);
// 인자(파라미터)가 1개인 경우 (accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator);
// 예시 : 두 값을 더하는 람다를 넘긴다. 따라서 결과는 6 이다.(1 + 2 + 3 이므로)
OptionalInt reduced =
IntStream.range(1, 4) // [1, 2, 3]
.reduce((a, b) -> {
return Integer.sum(a, b);
});
// 인자(파라미터)가 2개인 경우 (identity)
T reduce(T identity, BinaryOperator<T> accumulator);
// 예시 : 10은 초기값이고, 스트림 내 값을 더해서 결과는 16 이다.(10 + 1 + 2 + 3 이므로)
int reducedTwoParams =
IntStream.range(1, 4) // [1, 2, 3]
.reduce(10, Integer::sum); // method reference : 람다는 메소드 참조(method reference)를 이용해서 넘길 수 있다.
// 인자(파라미터)가 3개인 경우 (combiner)
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
// 예시
Integer reducedParams = Stream.of(1, 2, 3)
.reduce(10, // identity
Integer::sum, // accumulator
(a, b) -> { // 마지막 인자인 combiner 는 실행되지 않는다. (다음 코드 참고)
System.out.println("combiner was called");
return a + b;
});
// Combiner 는 병렬 처리 시 각자 다른 쓰레드에서 실행한 결과를 마지막에 합치는 단계이므로, 병렬 스트림에서만 동작한다.
Integer reducedParallel = Arrays.asList(1, 2, 3)
.parallelStream()
.reduce(10,
Integer::sum,
(a, b) -> {
System.out.println("combiner was called");
return a + b;
});
reduce라는 메소드를 이용해서 결과를 만들어낸다.
reduce 메소드는 총 세 가지의 파라미터를 받을 수 있다.
BinaryOperator< T > : 같은 타입의 인자 두 개를 받아 같은 타입의 결과를 반환하는 함수형 인터페이스
예시
결과는 36이 나온다.
accumulator 는 총 세 번 동작한다. 초기값 10에 각 스트림 값을 더한 세 개의 값(10 + 1 = 11, 10 + 2 = 12, 10 + 3 = 13)을 계산
Combiner 는 identity 와 accumulator 를 가지고 여러 쓰레드에서 나눠 계산한 결과를 합치는 역할이다. 12 + 13 = 25, 25 + 11 = 36 두 번 호출
주의!
간단한 경우에는 부가적인 처리가 필요하기 때문에, 병렬 처리가 오히려 느릴 수도 있다.
기존 코드
list.stream()
.map(el -> {
wasCalled();
return el.substring(0, 3);
})
.skip(2)
.collect(Collectors.toList());
System.out.println(counter); // 3
첫 번째 요소인 "Eric", 다음 요소인 "Elena" : 먼저 문자열을 잘라내고, 다음 skip 메소드 때문에 스킵된다.
마지막 요소인 “Java” : 문자열을 잘라내어 “Jav” 가 된 후, 스킵되지 않고 결과에 포함된다.
→ 여기서 map 메소드는 총 3번 호출된다.
변경된 코드 : skip 메소드가 먼저 실행되도록
List<String> collect = list.stream()
.skip(2)
.map(el -> {
wasCalled();
return el.substring(0, 3);
})
.collect(Collectors.toList());
System.out.println(counter); // 1
skip 을 먼저 하기 때문에, map 메소드는 한 번 밖에 호출되지 않는다.
→ 요소의 범위를 줄이는 작업을 먼저 실행하는 것이 불필요한 연산을 막을 수 있어 성능을 향상시킬 수 있다.
→ 이런 메소드로는 skip, filter, distinct 등...이 있습니다.
기존 코드
Stream<String> stream =
Stream.of("Eric", "Elena", "Java")
.filter(name -> name.contains("a"));
// findFirst 메소드를 실행하면서 스트림이 닫히기 때문에, findAny 하는 순간 런타임 예외(runtime exception)이 발생
Optional<String> firstElement = stream.findFirst();
Optional<String> anyElement = stream.findAny(); // IllegalStateException: stream has already been operated upon or closed
변경된 코드 : 데이터를 List 에 저장하고 필요할 때마다 스트림을 생성해 사용
List<String> names =
Stream.of("Eric", "Elena", "Java")
.filter(name -> name.contains("a"))
.collect(Collectors.toList());
Optional<String> firstElement = names.stream().findFirst();
Optional<String> anyElement = names.stream().findAny();
종료 작업을 하지 않는 한 하나의 인스턴스로서 계속해서 사용이 가능
(단, 종료 작업을 하는 순간 스트림이 닫히기 때문에 재사용은 할 수 없다. 스트림은 저장된 데이터를 꺼내서 처리하는 용도이지 데이터를 저장하려는 목적으로 설계되지 않았기 때문)
findFirst() : stream의 순서를 고려해, 가장 앞쪽에 있는 요소를 반환
findAny() : 멀티 쓰레드에서 가장 먼저 찾은 요소를 반환. stream의 뒤쪽에 있는 요소가 반환될 수도 있다.
(IntelliJ 를 이용함)
collection.stream().forEach()
→ collection.forEach()
collection.stream().toArray()
→ collection.toArray()
Arrays.asList().stream()
→ Arrays.stream() or Stream.of()
Collections.emptyList().stream()
→ Stream.empty()
stream.filter().findFirst().isPresent()
→ stream.anyMatch()
stream.collect(counting())
→ stream.count()
stream.collect(maxBy())
→ stream.max()
stream.collect(mapping())
→ stream.map().collect()
stream.collect(reducing())
→ stream.reduce()
stream.collect(summingInt())
→ stream.mapToInt().sum()
stream.map(x -> {...; return x;})
→ stream.peek(x -> ...)
!stream.anyMatch()
→ stream.noneMatch()
!stream.anyMatch(x -> !(...))
→ stream.allMatch()
stream.map().anyMatch(Boolean::booleanValue)
→ stream.anyMatch()
IntStream.range(expr1, expr2).mapToObj(x -> array[x])
→ Arrays.stream(array, expr1, expr2)
Collection.nCopies(count, ...)
→ Stream.generate().limit(count)
stream.sorted(comparator).findFirst()
→ Stream.min(comparator)
참고: [Java] 자바 스트림(Stream) 사용법 & 예제
참고: [Java] Stream API에 대한 이해 - (1/5)
참고: [Java] Stream API의 활용 및 사용법 - 기초 (3/5)
참고: 스트림의 생성
참고: Java 스트림 Stream (1) 총정리
참고: Java 스트림 Stream (2) 고급
참고: 자바 - 스트림(Stream)