자바8 인 액션 스터디 정리 (5장 스트림 활용)
As-Is (데이터 컬렉션 반복을 명시적으로 관리하는 외부 반복을 수행)
List<Dish> vegetarianDishes = new ArrayList<>();
for( Dish d : menu) {
if( d. isVegetarianDishes ( )) {
vegetarianDishs . add ( d);
}
}
To-Be (filter와 collect 연산을 지원하는 스트림 API를 이용해서 데이터 켈렉션 반복을 내부적으로 처리)
List<Dish> vegetarianMenu = menu. stream( )
. filter ( Dish:: isVegetarian )
. collect ( toList ( ));
System. out. println ( vegetarianMenu . toString());
5.1 Filtering and Slicing
5.1.1 Filtering with a predicate
stream 인터페이스는 filter 기능을 지원한다. predicate 를 파라미터로 받아서 필터링된 요소들을 stream 형식으로 반환한다.
List<Dish> vegetarianMenu = menu. stream( )
. filter(Dish::isVegetarian) // 채식에 가까운 Dish 인지 여부
.collect ( toList ( ));
System. out. println ( vegetarianMenu . toString());
5.1.2 Filtering unique elements
고유한 요소를 반환하는 distinct 함수를 제공한다.
List<Integer> numbers = Arrays. asList ( 1,2,1,3,3,2,4);
numbers. stream( )
. filter ( i -> i%2 == 0)
. distinct ( )
. forEach ( System. out:: println );
5.1.3 Truncating a stream
반환되는 요소의 갯수를 제한 할 수 있는 limit(n) 함수를 제공한다. 그 이외의 요소들은 처리되지 않는다.
List<Dish> dishes = menu. stream( )
. filter ( d -> d. getCalories ( ) > 300)
. limit ( 3)
. collect ( toList ( ));
System. out. println ( dishes. toString());
5.1.4 Skipping elements
skip(n)은 n만큼의 요소를 제외한 나머지를 반환하는 함수이다. limit(n)과는 상호보완적인 관계이다.
List<Dish> dishes = menu. stream( )
. filter ( d -> d. getCalories ( ) > 300)
. skip ( 2)
. collect ( toList ( ));
System. out. println ( dishes. toString());
Quiz 5.1 처음 2개의 고기요리를 고르시오.
List<Dish> dishes =
menu. stream( )
. filter ( d -> d. getType ( ) == Dish. Type. MEAT)
. limit ( 2)
. collect ( toList ( ));
System. out. println ( dishes. toString());
5.2 Mapping
stream은 SQL 의 select 문 처럼 stream에서 특정 컬럼을 추출하는 함수를 제공한다(map, flatMap).
5.2.1 map
map 는 함수를 인수로 받는 메소드이다. 인수로 받은 함수를 stream의 매 요소에 적용되어 새로는 stream을 생성한다.
List<String> dishNames =
menu. stream( )
. map ( Dish:: getName )
. collect ( toList ( ));
System. out. println ( dishNames );
Dish.getName()의 리턴타입이 String 이기 때문에 map메소드의 리턴타입 역시 Stream\
아래의 경우에는 String.length() 의 리턴타입이 Integer이므로 map메소드의 리턴타입은 Stream\
List<String> words = Arrays. asList ( "Java8", "Lambdas", "In", "Action");
List<Integer> wordLenghs = words. stream( )
. map ( String:: length)
. collect ( toList ( ));
System. out. println ( wordLenghs );
그러면, 메뉴의 각 요리이름을 구한 후 그 이름들의 길이를 구하고 싶다면 아래와 같이 할 수 있다.
List<Integer> dishNameLengths = menu. stream( )
. map ( Dish:: getName )
. map ( String:: length)
. collect ( toList ( ));
System. out. println ( dishNameLengths );
만일 단어의 목록에서 유일한 문자들(unique characters)을 추출하려면 (예를 들어 ["Hello","World"] 라는 list 가 있다면,
["H", "e", "l", "o", "W", "r", "d"] 라는 리턴을 원한다면) 아래와 같이 코딩이 가능할 것 이다.
List<String> hello = Arrays.asList("Hello", "World");
List<String[]> uniqueWord = hello.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());
System.out.println(uniqueWord.get(0)[0]);
하지만 리턴값으로 원하는것이 Stream\<string[]> 이 아니고 Stream\
이 과정을 차례대로 따라가 보자.
Attempt using Arrays.stream and flatMap
먼저, 배열의 stream대신 문자들의 stream이 필요하다. Arrays.stream()은 배열을 받아 stream으로 처리한다.
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
flatMap은 Arrays.stream()을 통해 여러개로 나눠진 stream들을 하나로 모아 새로운 stream 을 생성한다.
방금 전 예제에 적용해보자
List<String> hello = Arrays.asList("Hello", "World");
List<String> words = hello.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
System.out.println(words);
output :
[H, e, l, o, W, r, d]
그림으로 비교해 보면 아래와 같다.
vs.
Quiz 5-2
1.Give a list of numbers, how would you return a list of the square of each number? For example, given [1,2,3,4,5] you should return [1,4,9,16,25].
List<Integer> num = Arrays.asList(1,2,3,4,5);
List<Integer> powNUm = num.stream()
.map(n -> n*n)
.collect(toList());
System.out.println(powNUm);
output :
[1, 4, 9, 16, 25]
2.Given two lists of numers, how would you return all pairs of numbers? For example given a list [1,2,3] and a list[3,4] you should return [(1,3), (1,4), (2,3), (2,4), (3,3), (3,4)]. For simplicity, you can represent a pair as an array with elements.
List<Integer> num1 = Arrays.asList(1,2,3);
List<Integer> num2 = Arrays.asList(3,4);
List<int[]> pairs = num1.stream()
.flatMap(i -> num2.stream().map(j -> new int[]{i,j}))
.collect(toList());
pairs.forEach(pair -> System.out.println(pair[0]+","+pair[1]));
output :
1,3
1,4
2,3
2,4
3,3
3,4
3.How would you extend the previous example to return only pairs whose sum is divisible by 3? For example,(2,4) and (3,3) are valid.
List<Integer> num1 = Arrays.asList(1,2,3);
List<Integer> num2 = Arrays.asList(3,4);
List<int[]> pairs = num1.stream()
.flatMap(i -> num2.stream()
.filter(j -> (i+j)%3 == 0)
.map(j -> new int[]{i,j}))
.collect(toList());
pairs.forEach(pair -> System.out.println(pair[0]+","+pair[1]));
output :
2,4
3,3
5.3 Finding and matching
stream의 요소들이 주어진 속성에 일치여부를 찾을 수 있는 기능을 제공한다.(allMatch, anyMatch, noneMatch, findFirst, findAny)
5.3.1 Checking to see if a predicate matches at least one element
anyMatch 는 stream의 요소중에 주어진 속성과 일치하는 것이 하나라도 있으면 true를 리턴한다. terminal operation이다.
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("The menu is (somewhat) vegetarian friendly!!");
}
5.3.2. Checking to see if a predicate matches all elements
anyMatch 는 stream의 요소 모두가 주어진 속성과 일치하면 true를 리턴한다. terminal operation이다.
boolean isHealthy = menu.stream()
.allMatch(d -> d.getCalories() < 1000);
allMatch의 반대가 nonMatch 이다. 모든요소가 주어진 속성과 일치하지 않으면 true를 리턴한다.
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCalories() => 1000);
anyMatch, allMatch, noneMatch 이 3개의 기능을 short-circuiting 이라고 한다.
short-circuiting 이란
...
5.3.3. Finding an element
findAny 메소드는 임의의 요소를 반환한다. 이것은 다른 stream 기능과 결합하여 사용된다. filter 와 findAny를 통해서 채식요리를 찾을 수 있다.
Optional<Dish> dish =
menu.stream()
.filter(Dish::isVegetarian)
.findAny();
System.out.println(dish.toString());
간단히 Optional 알아보기(chapter 10. p315)
Optional\
null이 반환될때 생길 수 있는 문제를 피하기 위하여 java8에서는 Optional이 소개되었고 자세한 설명은 chapter10에 있다.
여기서는 제공되는 메소드에 대한 간략한 설명을 한다.
- isPresent() : Optional이 값을 포함하고 있다면 true, 아니면 false를 리턴한다.
- ifPresnet(Consumer\
- T get() : 값이 있으면 값을 리턴하고 없으면 NoSuchElement-Exception을 발생시킨다.
- T orElse(T other) : 값이 있으면 값을 리턴하고 없으면 기본값을 리턴한다.
5.3.4. Finding the first element
findFirst 는 findAny와 비슷하지만, steam의 요소의 순서를 보장한다는 차이점이 있다.
List에서 3으로 나눠지는 제곱값을 찾는 경우
List<Integer> someNumbers = Arrays.asList(1,2,3,4,5);
Optional<Integer> firstSquareDivisibleByThree =
someNumbers.stream()
.map(x -> x*x)
.filter(x -> x%3 == 0)
.findFirst();
System.out.println(firstSquareDivisibleByThree);
findAny 와 findFirst를 구분한 이유는 findAny는 병렬처리시 순서를 보장할 수 없기 때문이다.
5.4. Reducing
메뉴의 모든 요리의 칼로리를 더한 값은?
가장 칼로리가 높은 음식은?
reducton operations은 stream을 Integer같은 하나의 값으로 합칠 수 있는 메소드를 제공한다.
5.4.1 Summing the elements
// 모든 요소를 더한 값
int sum = numbers.stream().reduce(0, (a,b) -> a+b);
2개의 인수
- 초기값 . 예) 0
- 2개의 요소를 합치고 계산하는 구문 예) (a,b) -> a+b.
// 모든 요소를 곱한 값
int product = numbers.stream().reduce(1, (a,b) -> a*b);
그림으로 표현하면 위와 같다.
아래와 같이 좀 더 간소하게 표현할 수 있음.
List<Integer> numbers = Arrays.asList(1,2,3,4,5);
int sum1 = numbers.stream().reduce(0, Integer::sum);
System.out.println(sum1);
//Optional<Integer> sum2 = numbers.stream().reduce((a,b) -> (a+b));
Optional<Integer> sum2 = numbers.stream().reduce(Integer::sum);
System.out.println(sum2);
5.4.2. Maximum and minimum
stream에서 최대값과 최소값을 구할수 있다.
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min); // (x,y) -> x<y?x:y
Quiz 5-3 : reducing
How would you count the number of dishes in a stream using the map and reduce method?
int count = menu.stream()
.map(d-> 1)
.reduce(0, (a,b) -> a+b);
System.out.println(count);
or
long count = menu.stream().count();
Benefit of the reduce method and parallelism
reduce를 사용하면 내부반복이 추상화되면서 내부 구현에서 병렬로 reduce를 실행 할 수 있음.
반면에 반복적인 합계(iteractive summation)에서는 sum 변수를 공유해야 하므로 병렬로 구현하는게 힘들고, 강제적으로 동기화 하더라도 스레드간의 경쟁으로 이득이 감소하게 된다.
7장에서는 fork/join 프레임워크를 이용하는 방법을 보게 될거지만 중요한 사실은 가변 누적자 패턴(mutable accumulator pattern)은 병렬화의 막다른 골목이다는 것이다. 그러므로 reduce 라는 새로운 패턴이 필요하게 되었다.
7장에서 stream의 모든 요소를 병렬로 더하는 코드를 방법을 배우게 된다.
int sum = numbers.parallelStream().reduce(0, Integer::sum);
Stream operations : stateless vs. stateful
map, filter 등 : 입력 스트림에서 각 요소를 받아 0 또는 결과를 출력 스트림으로 보내기 때문에 내부 상태를 갖지 않는 연산(stateless operation)
reduce , sum, max 등 : 결과를 누적할 내부 상태가 필요하지만 스트림에서 처리하는 요소 수와 관계없이 내부 상태의 크기는 한정(bounded) 되어 있음.
반면 sorted, distinct 등 : filter 나 map 처럼 스트림을 입력으로 받아 다른 스트림을 출력하는 것처럼 보이지만, 다르다. 스트림의 요소를 정렬하거나 중복을 제거하려면 과거의 이력을 알고 있어야 한다. 예를 들어 어떤 요소를 출력 스트림으로 추가하려면 모든 요소가 버퍼에 추가되어 있어야 한다. 연산을 수행하는 데 필요한 저장소 크기는 정해져있지 않으므로 데이터 스트림의 크기가 무한이라면 문제가 생각 수 있다. 따라서 이러한 연산은 내부 상태를 갖는 연산(stateful operation)으로 간주.
5.5. 실전 연습
public class Trader {
private final String name;
private final String city;
public Trader(String n, String c){
this.name = n;
this.city = c;
}
public String getName() {
return name;
}
public String getCity() {
return city;
}
@Override
public String toString() {
return "practice.Trader:" + this.name + " in " + this.city;
}
}
public class Transaction {
private final Trader trader;
private final int year;
private final int value;
public Transaction(Trader trader, int year, int value) {
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader() {
return trader;
}
public int getYear() {
return year;
}
public int getValue() {
return value;
}
@Override
public String toString() {
return "{" + this.trader + ", " +
"year: " + year + ", " +
"value: " + value + "}";
}
}
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
1.2011년에 일어난 모든 트랜잭션을 찾아 값을 오름차순으로 정리하시오.
List<Transaction> ordredTran2011 =
transactions.stream()
.filter(d -> d.getYear() == 2011)
.sorted(Comparator.comparing(Transaction::getYear))
.collect(toList());
System.out.println(ordredTran2011);
2.거래자가 근무하는 모든 도시를 중복 없이 나열하시오.
3.케임브리지에서 근무하는 모든 거래자를 찾아서 이름순으로 정렬하시오.
4.모든 거래자의 이름을 알파벳순으로 정렬해서 반환하시오.
5.밀리노에 거래자가 있는가?
6.케임브리지에 거주하는 거래자의 모든 트랜잭션값을 출력하시오.
7.전체 트랜잭션 중 최댓값은 얼마인가?
8.전체 트랜잭션 중 최솟값은 얼마인가?
5.6. 숫자형 스트림
5.6.3. 숫자 스트림 활용: 피타고라스 수
Stream<double[]> pythagoreanTriples2 =
IntStream.rangeClosed(1,100).boxed()
.flatMap(a ->
IntStream.rangeClosed(a,100)
.mapToObj(
b -> new double[]{a, b, Math.sqrt(a*a+b*b)})
.filter(t -> t[2]%1 == 0));
pythagoreanTriples2.limit(5)
.forEach(t ->
System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
output :
3.0, 4.0, 5.0
5.0, 12.0, 13.0
6.0, 8.0, 10.0
7.0, 24.0, 25.0
8.0, 15.0, 17.0
5.7. 스트림 만들기
5.7.1. 값으로 스트림 만들기
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
output :
JAVA 8
LAMBDAS
IN
ACTION
5.7.2. 배열로 스트림 만들기
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
System.out.println(sum);
output :
41
5.7.3. 파일에서 스트림 만들기
long uniqueWords = 0;
try(Stream<String> lines =
Files.lines(Paths.get("/Users/red/pro/java8/out/production/ch5/data.txt"), Charset.defaultCharset())){
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
}
catch(IOException e){
e.printStackTrace();
}
System.out.println(uniqueWords);
Quiz. Fibonacci Tuples
Stream.iterate(new int[]{0,1}, t -> new int[]{t[1], t[0]+t[1]})
.limit(20)
.forEach(t -> System.out.println("("+ t[0] + ","+ t[1] + ")"));