for
또는 foreach
문을 돌면서 요소 하나씩을 꺼내서 다루는 방법이었다.데이터의 흐름
이다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있다.병렬처리
가 가능해진다.스트림에 대한 내용은 크게 세 가지로 나눌 수 있다.
Arrays.stream
메서드를 이용한다. String[] strings = new String[]{"a","b","c"};
Stream<String> stream = Arrays.stream(strings);
Stream<String> stream1 = Arrays.stream(strings,1,3); // 1~2 요소 [b,c]
stream
을 이용해서 스트림을 만들 수 있다. List<String> list = new ArrayList<>(Arrays.asList("a","b","c"));
Stream<String> stringStream = list.stream();
Stream<String> stringStream1 = list.parallelStream(); // 병렬처리 스트림
null
대신 사용할 수 있다. public Stream<String> streamOf(List<String> list){
return list==null || list.isEmpty()
? Stream.empty() : list.stream();
}
build
메서드로 스트림을 리턴한다. Stream<String> builderStream =
Stream.<String>builder()
.add("Eric").add("Elena").add("Java")
.build();
generate
메서드를 사용하면 Supplier<T>
에 해당하는 람다로 값을 넣을 수 있다.Supplier<T>
는 인자는 없고 리턴값만 있는 함수형 인터페이스이며 람다에서 리턴하는 값이 들어간다. public static<T> Stream<T> generate(Supplier<T> s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}
Stream<String> generatedStream =
Stream.generate(()->"gen").limit(5); // ["gen","gen","gen","gen","gen"]
Stream<Integer> iteratorStream =
Stream.iterate(30, i -> i+2).limit(5); // [30,32,34,36,38]
iterate
메서드를 이용하면 초기값과 해당 값을 다루는 람다를 이용해서 스트림에 들어가 요소를 만든다.range
와 rangedClosed
는 범위의 차이이다.IntStream intStream = IntStream.range(1,5); // [1,2,3,4]
LongStream longStream = LongStream.rangeClosed(1,5); // [1,2,3,4,5]
boxed
메서드를 이용해서 박싱(boxing)할 수 있다.Stream<Integer> boxStream = IntStream.range(1,5).boxed(); // [1,2,3,4]
Random
클래스는 난수를 가지고 세 가지 타입의 스트림(IntStream,LongStream,DoubleStream)을 만들어낼 수 있다.DoubleStream doubleStream = new Random().doubles(3); // 난수 3개 생성
IntStream stringStream2 = "Stream".chars(); // [83, 116, 114, 101, 97, 109]
다음은 정규표현식(RegEx)를 이용해서 문자열을 자리고, 각 요소들로 스트림을 만든 예제이다.
Stream<String> stringStream3 =
Pattern.compile(" ,").splitAsStream("Eric,Elena,Java"); // [Eric, Elena, Java]
lines
메서드는 해당 파일의 각 라인을 스트링 타입의 스트림으로 만들어준다.Stream<String> stringStream4 =
Files.lines(Paths.get("file.txt"),Charset.forName("UTF-8"));
stream
대신 parallelStream
메서드를 사용해서 병렬 스트림을 쉽게 생할 수 있다.Fork/Join framework
를 사용한다.// 병렬 스트림 생성
Stream<Product> parallelStream = productList.parallelStream();
// 병렬 여부 확인
boolean isParallel = parallelStream.isParallel()
boolean isMany = parallelStream
.map(product -> product.getAmount()*10)
.anyMatch(amount-> amount>200);
Arrays.stream(list).parallel();
parallel
메서드를 이용해서 처리한다.IntStream intStream = IntStream.range(1, 150).parallel();
boolean isParallel = intStream.isParallel();
sequential
모드로 돌리고 싶다면 다음처럼 sequential
메서드를 사용한다.IntStream intStream = intStream.sequential();
boolean isParallel = intStream.isParallel();
Stream.concat
메서드를 이용해 두 개의 스트림을 연결해서 새로운 스트림을 만들어낼 수 있다.
Stream<String> s1 = Stream.of("JAVA","Scala","Groovy");
Stream<String> s2 = Stream.of("Python","go","Swift");
Stream<String> s3 = Stream.concat(s1,s2);
Stream<T> filter(Predicate<? super T> predicate);
List<String> names = Arrays.asList("Eric" , "Java" , "Elena");
Stream<String> stream = names.stream()
.filter(name-> name.contains("a")); // Java, Elena
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Stream<String> stream1 = names.stream()
.map(String::toUpperCase);
// ERIC, JAVA , ELENA
위 예제는 스트링의 toUpperCase 메서드를 실행해서 대문자로 변환한 값들이 담긴 스트림을 리턴한다.
다음처럼 요소 내 들어있는 Product 개체의 수량을 꺼내올수도있다. 각 상품을 상품의 수량으로 맵핑한다.
Stream<Integer> stream =
productList.stream()
.map(Product::getAmount);
// [23, 14, 13, 23, 13]
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
인자로 mapper
을 받고 있는데, 리턴타입이 Stream이다.
즉, 새로운 스트림을 생성해서 리턴하는 람다를 넘겨야 한다.
flatMap
은 중첩구조를 한 단계 제거하고 단일 컬렉션으로 만들어주는 역할을 한다.
이러한 작업을 플래트닝 이라고 한다.
다음과 같은 중첩된 리스트가 있다.
List<List<String>> list =
Arrays.asList(Arrays.asList("a"),Arrays.asList("b"));
// [ [a] , [b] ]
List<String> flatList=
list.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
// [a] , [b]
List<Student> students = new ArrayList<Student>();
students.stream()
.flatMapToInt(student ->
IntStream.of(student.getEnglishGrade() , student.getKoreanGrade(), student.getMathGrade()))
.average().ifPresent(avg -> System.out.println(Math.round((avg*10)/10.0)));
map
메서드 자체만으로는 한번에 할수 없는 기능이다.Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
인자없이 그냥 호출할 경우 오름차순으로 정렬한다.
IntStream.of(14,11,20,29,23)
.sorted()
.boxed()
.collect(Collectors.toList());
// [11, 14 , 20 , 23 ,29]
List<String> list1 = Arrays.asList("Java", "Scala", "Groovy", "Python", "Go", "Swift");
list1.stream()
.sorted()
.collect(Collectors.toList());
// [Go, Groovy, Java, Python, Scala, Swift]
list1.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// [Swift, Scala, Python, Java, Groovy, Go]
int compare(T o1, T o2)
list1.stream()
.sorted(Comparator.comparingInt(String::length))
.collect(Collectors.toList());
// [Go, Java, Scala, Swift, Groovy, Python]
list1.stream()
.sorted((s1,s2)-> s2.length()-s1.length())
.collect(Collectors.toList());
// [Groovy, Python, Scala, Swift, Java, Go]
peek
가 있다peek
은 그냥 확인 해본다는 단어 뜻처럼 특정 결과를 반환하지 않는 함수형 Consumer를 인자로 받는다.Stream<T> peek(Consumer<? super T> action);
int sum = IntStream.of(1,3,5,7,9)
.peek(i -> System.out.println(i))
.sum();
System.out.println("sum = " + sum);
output
1
3
5
7
9
sum = 25
int sum = IntStream.of(1,3,5,7,9)
.sum();
long count = IntStream.of(1,3,5,7,9)
.count();
System.out.println("sum = " + sum);
System.out.println("long = " + count);
output
sum = 25
long = 5
Optional
을 이용해 리턴한다. OptionalInt min = IntStream.of(1,3,5,7,9).min();
OptionalInt max = IntStream.of(1,3,5,7,9). max();
System.out.println(min + " " + max);
output
sum = 25
long = 5
OptionalInt[1] OptionalInt[9]
ifPresent
메서드를 이용해서 Optional을 처리할수있다. DoubleStream.of(1.1,2.2,3.3,4.4,5.5)
.average()
.ifPresent(System.out::println);
output
3.3
reduce
와 collect
메서드를 제공한다.스트림은 reduce
라는 메서드를 이용해서 결과를 만들어낸다.
reduce 메서드는 총 세가지의 파라미터를 받을수있다.
// 1개 (accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator);
// 2개 (identity)
T reduce(T identity, BinaryOperator<T> accumulator);
// 3개 (combiner)
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
BinaryOperator<T>
는 같은 타입의 인자 두 개를 받아 같은 타입의 결과를 반환하는 함수형 인터페이스이다.
OptionalInt reduced =
IntStream.of(1,4)
.reduce((a,b)->{return Integer.sum(a,b); });
System.out.println(reduced);
output
OptionalInt[5]
int reducedStream =
IntStream.range(1,4)
.reduce(10,Integer::sum);
System.out.println(reducedStream);
output
16
Integer integer = Stream.of(1,2,3)
.reduce(10,
Integer::sum,
(a,b) ->{
System.out.println("combiner was called");
return a+b;
});
System.out.println(integer);
output
16
Integer integer = Arrays.asList(1,2,3).parallelStream()
.reduce(10,
Integer::sum,
(a,b)->{
System.out.println("combiner call");
return a+b;
});
System.out.println(integer);
output
36
collect
메서드는 또 다른 종료 작업이다.Collector
타입의 인자를 받아서 처리하는데, 자주 사용 하는 작업은 Collections
객체에서 제공한다. List<Product> productList =
Arrays.asList(new Product(1,"감자")
, new Product(2,"오렌지")
, new Product(3,"레몬")
, new Product(4,"빵")
, new Product(5,"설탕")
);
map
으로 각 요소의 이름을 가져온 후 Collectors.toList()
를 이용해서 리스트로 결과를 가져온다. List<String> collectorCollection =
productList.stream()
.map(Product::getName)
.collect(Collectors.toList());
System.out.println(collectorCollection);
output
[감자, 오렌지, 레몬, 빵, 설탕]
String listToString =
productList.stream()
.map(Product::getName)
.collect(Collectors.joining());
System.out.println(listToString);
output
감자오렌지레몬빵설탕
String listToString =
productList.stream()
.map(Product::getName)
.collect(Collectors.joining(" , " , "< " , ">"));
System.out.println(listToString);
output
< 감자 , 오렌지 , 레몬 , 빵 , 설탕>
Double average =
productList.stream()
.collect(Collectors.averagingInt(Product::getIndex));
System.out.println(average);
output
8.6
Integer sum =
productList.stream()
.collect(Collectors.summingInt(Product::getIndex));
System.out.println(sum);
output
43
mapToInt
메서드를 사용해서 좀 더 간단하게 표현할 수도 있다. Integer sum =
productList.stream()
.mapToInt(Product::getIndex)
.sum();
System.out.println(sum);
43
Collectors.summarizingInt()
IntSummaryStatistics summaryStatistics =
productList.stream()
.collect(Collectors.summarizingInt(Product::getIndex));
System.out.println(summaryStatistics);
output
IntSummaryStatistics{count=5, sum=43, min=2, average=8.600000, max=23}
collect
전에 이런 통계작업을 위한 map
을 호출할 필요가 없게 된다. List<Product> productList =
Arrays.asList(new Product(23,"potato")
, new Product(14,"orange")
, new Product(13,"lemon")
, new Product(23,"bread")
, new Product(13,"sugar")
);
Map<Integer,List<Product>> collectorMapOfLists =
productList.stream()
.collect(Collectors.groupingBy(Product::getIndex));
output
{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'}]}
groupingBy
는 함수형 인터페이스 Function을 이용해서 특정 값을 기준으로 스트림으로 스트림 내 요소들을 묵었다면, partitioningBy()
는 함수형 인터페이스 Predicate를 받는다. List<Product> productList =
Arrays.asList(new Product(23,"potato")
, new Product(14,"orange")
, new Product(13,"lemon")
, new Product(23,"bread")
, new Product(13,"sugar")
);
Map<Boolean,List<Product>> collectorMapOfLists =
productList.stream()
.collect(Collectors.partitioningBy(i -> i.getIndex()>15));
output
{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'}]}
collect
한 이후에 추가작업이 필요한 경우에 사용할 수 있다.public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(
Collector<T,A,R> downstream,
Function<R,RR> finisher) { ... }
finisher
가 추가되었는데, 이것은 collect 한후에 실행할 작업을 의미한다. Set<Product> productSet =
productList.stream()
.collect(Collectors.collectingAndThen(Collectors.toSet(),Collections::unmodifiableSet));
Collectors.toSet
을 이용해서 결과를 Set으로 collect 한후 수정불가한 Set으로 변환하는 작업을 추가로 실행하는 코드이다.public static<T, R> Collector<T, R, R> of(
Supplier<R> supplier, // new collector 생성
BiConsumer<R, T> accumulator, // 두 값을 가지고 계산
BinaryOperator<R> combiner, // 계산한 결과를 수집하는 함수.
Characteristics... characteristics) { ... }
Collector<Product, ?, LinkedList<Product>> toLinkedList =
Collector.of(LinkedList::new,
LinkedList::add,
(first, second) -> {
first.addAll(second);
return first;
});
collect
메서드에 우리가 만든 커스텀 컬렉터를 넘겨줄수 있고, 결과가 담긴 LinkedList가 반환된다. LinkedList<Product> linkedList =
productList
.stream().collect(toLinkedList);
System.out.println(linkedList);
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
List<String> list = Arrays.asList("Eric","Elena","Java");
boolean any = list.stream().anyMatch( a-> a.contains("a"));
System.out.println(any);
boolean all = list.stream().allMatch( a->a.length()>3);
System.out.println(any);
boolean none = list.stream().noneMatch( a-> a.endsWith("z"));
System.out.println(any);
output
true
true
true
[스트림참고]https://futurecreator.github.io/2018/08/26/java-8-streams/