스트림 활용
필터링과 슬라이싱
프레디케이트로 필터링
스트림 인터페이스는 filter 메서드를 지원함
filter 메서드는 프레디케이트(불린반환 함수)
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
고유 요소 필터링
스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드 지원
고유 여부는 스트림에서 만든 객체의 hashCode,equals로 결정됨
| List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4); numbers.stream() .filter(i -> i % 2 == 0) .distinct() .forEach(System.out::println); | cs |
스트림 축소
스트림은 주어진 사이즈 이하의 크기를 갖는 새로운 스트림을 반환하는 limit(n) 메서드를 지원함
| List<Dish> dishes = menu.stream() .filter(d -> d.getCalories() > 300) .limit(3) .collect(toList()); | cs |
요소 건너뛰기
처음 n개 요소를 제외한 스트림을 반환하는 skip(n) 메서드를 지원
| //처음 두 요리를 건너뛴 다음에 300칼로리가 넘는 나머지 요리 반환 예제 List<Dish> dishes = menu.stream() .filter(d -> d.getCalories() > 300) .skip(2) .collect(toList()); | cs |
매핑
스트림은 함수를 인수로 받는 map 메서드 지원함
| //단어의 길이를 List<Integer>로 매핑( 새로운 버전을 만듬 ) List<String> words = Arrays.asList("Java8","Lambdas","In","Action"); List<Integer> wordLengths = words.stream() .map(String::length) .collect(toList()); //각요리명을 추출후에 각요리명의 길이? List<Integer> dishNameLengths = menu.stream() .map(Dish::getName) .map(String::length) .collect(toList()); | cs |
map과 Arrays.stream 활용
배열 스트림 대신 문자열 스트림 -> 문자열을 받아 스트림을 만드는 Arrays.stream() 메서드
| String[] arrayOfWords = {"Goodbye","World"}; Stream<String> streamOfwords = Arrays.stream(arrayOfWords); | cs |
flatMap 각 단어를 개별 문자열로 이루어진 배열로 만든다음에 각 배열을 별도의 스트림으로
flatMap은 하나의 평면화된 스트림을 반환
| // flatMap words.stream() .flatMap((String line) -> Arrays.stream(line.split(""))) .distinct() .forEach(System.out::println); | cs |
| List<String> uniqueCharacters = words.stream() .map(w -> w.split("")) //각 단어를 개별문자로 포함하는 배열로 변환 .flatMap(Arrays::stream) // 생성된 스트림을 하나의 스트림으로 평면화 .distinct() .collect(Collectors.toList()); | cs |
검색과 매칭
특정 속성이 데이터 집합에 있는지 여부를 검색하는 데이터 처리에 자주 사용
allMatch,anyMatch,noneMatch,findFirst,findAny
프레디게이트가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인할 때 anyMatch 메서드를 이용
| if(menu.stream().anyMatch(Dish::isVegetarian)) { System.out.println("The menu is (somewhat) vegetarian friendly!!"); } | cs |
anyMatch 불린을 반환 최종연산
allMatch 프레디게이트가 모든 요소와 일치하는지 검사
스트림의 모든 요소가 주어진 프레디케이트와 일치하는지 검사
| boolean isHealthy = menu.stream() .allMatch(d -> d.getCalories() < 1000); | cs |
noneMatch allMatch와 반대 연산 즉, noneMatch는 주어진 프레디케이트와 일치하는 요소가 없는지 확인
모든 요소들이 파라미터로 주어진 Predicate의 조건을 만족하지 않는지 조사
| boolean isHealthy = menu.stream() .noneMatch(d -> d.getCalories() >= 1000); | cs |
anyMatch,allMatch,noneMatch 세가지 메서드는 스트림 쇼트서킷 기법 = 자바의 &&,|| 같은 연산 활용
쇼트서킷 = 표현식에서 하나라도 거짓이라는 결과가 나오면 나머지 표현식의 결과와 상관없이 전체 결과도 거짓
findAny 요소 검색 = 현재 스트림에서 임의의 요소를 반환
| Optional<Dish> dish = menu.stream() .filter(Dish::isVegetarian) .findAny(); | cs |
Optional이란?
Optional<T> 클래스(java.util.Optional)는 값의 존재나 부재 여부를 표현하는 컨테이너 클래스
| Optional<Dish> dish = findVegetarianDish(); dish.ifPresent(d -> System.out.println(d.getName())); | cs |
첫 번째 요소 찾기
| 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(); //9 | cs |
리듀싱 연산 = 모든 스트림 요소를 처리해서 값으로 도출
프로그래밍 언어 용어 = 폴드(fold)
요소의 합 : 15
| List<Integer> numbers = Arrays.asList(3,4,5,1,2); int sum = numbers.stream().reduce(0, (a, b) -> a + b); System.out.println(sum); | cs |
메서드 레퍼런스를 이용해서 구현 Integer::sum
| int sum2 = numbers.stream().reduce(0, Integer::sum); System.out.println(sum2); //15 //Integer::sum public static int sum(int a, int b) { return a + b; } | cs |
초깃값 없음
스트림에 아무 요소도 없는 상황이면 이런식으로 구현
| //reduce는 Optional 객체를 반환 Optional<Integer> sum = numbers.stream().reduce((a,b) -> (a+b)); | cs |
최대값과 최소값
| List<Integer> numbers = Arrays.asList(3,4,5,1,2); int max = numbers.stream().reduce(0, (a, b) -> Integer.max(a, b)); //5 | cs |
최소값
| Optional<Integer> min = numbers.stream().reduce(Integer::min); //1 | cs |
map과 reduce를 연결하는 기법 맵 리듀스(map-reduce) 패턴 쉽게 병렬화하는 특징
| int count = menu.stream() .map(d -> 1) .reduce(0, (a,b) -> a+b); //9 | cs |
문제들
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | package lambdasinaction.chap5; import lambdasinaction.chap5.*; import java.util.*; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toList; public class PuttingIntoPractice{ public static void main(String ...args){ 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) ); //2011년에 일어난 모든 트랜잭션을 찾아서 값을 오름차순으로 정렬 List<Transaction> tr2011 = transactions.stream() .filter(transaction -> transaction.getYear() == 2011) .sorted(comparing(Transaction::getValue)) .collect(toList()); System.out.println(tr2011); //[{Trader:Brian in Cambridge, year: 2011, value:300}, {Trader:Raoul in Cambridge, year: 2011, value:400}] //거래자가 근무하는 모든 도시를 중복 없이 나열 List<String> cities = transactions.stream() .map(transaction -> transaction.getTrader().getCity()) .distinct() .collect(toList()); System.out.println(cities); //[Cambridge, Milan] //케임브리지에서 근무하는 모든 거래자를 찾아서 이름순으로 정렬 List<Trader> traders = transactions.stream() .map(Transaction::getTrader) .filter(trader -> trader.getCity().equals("Cambridge")) .distinct() .sorted(comparing(Trader::getName)) .collect(toList()); System.out.println(traders); //[Trader:Alan in Cambridge, Trader:Brian in Cambridge, Trader:Raoul in Cambridge] //모든 거래자의 이름을 알파벳순으로 정렬해서 반환 String traderStr = transactions.stream() .map(transaction -> transaction.getTrader().getName()) .distinct() .sorted() .reduce("", (n1, n2) -> n1 + n2); System.out.println(traderStr); //AlanBrianMarioRaoul //밀라노에 거래자가 있는가? boolean milanBased = transactions.stream() //anyMatch에 프레디케이트를 전달해서 밀라노에 거래자가 있는지 확인 .anyMatch(transaction -> transaction.getTrader() .getCity() .equals("Milan") ); System.out.println(milanBased); //true //케임브리지에 거주하는 거래자의 모든 트랜잭션값을 출력 transactions.stream() .map(Transaction::getTrader) .filter(trader -> trader.getCity().equals("Milan")) .forEach(trader -> trader.setCity("Cambridge")); System.out.println(transactions); //[{Trader:Brian in Cambridge, year: 2011, value:300}, {Trader:Raoul in Cambridge, year: 2012, value:1000}, {Trader:Raoul in Cambridge, year: 2011, value:400}, {Trader:Mario in Cambridge, year: 2012, value:710}, {Trader:Mario in Cambridge, year: 2012, value:700}, {Trader:Alan in Cambridge, year: 2012, value:950}] //전체 트랜잭션 중 최대값은 얼마? int highestValue = transactions.stream() .map(Transaction::getValue) .reduce(0, Integer::max); System.out.println(highestValue); //1000 //전체 트랜잭션 중 최솟값? Optional<Transaction> smallestTransaction = transactions.stream() .reduce((t1,t2) -> t1.getValue() < t2.getValue() ? t1 : t2); System.out.println(smallestTransaction.get().getValue()); //300 Optional<Transaction> smallestTransaction2 = transactions.stream().min(comparing(Transaction::getValue)); System.out.println(smallestTransaction2.get().getValue()); //300 } } | cs |
숫자형 스트림
숫자 스트림으로 매핑
스트림을 특화 스트림으로 변환할 때 mapToInt,mapToDouble,mapToLong = map과 정확히 같은 기능을 수행하지만
Stream<T> 대신 특화된 스트림 반환 (InteStream)
| int calories = menu.stream() .mapToInt(Dish::getCalories) .sum(); System.out.println("Number of calories:" + calories); //4300 | cs |
객체 스트림으로 복원하기
| IntStream intStream = menu.stream().mapToInt(Dish::getCalories); Stream<Integer> stream = intStream.boxed(); //숫자 스트림을 스트림으로 변환 | cs |
기본값: OptionalInt
| OptionalInt maxCalories = menu.stream() .mapToInt(Dish::getCalories) .max(); //800 int max; max = maxCalories.orElse(1); //값이 없을 때 기본 최대값을 명시적으로 설정 | cs |
숫자 범위
자바8의 IntStream과 LongStream에서 range와 rangeClosed 두가지 정적 메서드 제공
두 메서드 첫 번째 인수로 시작값, 두번째 인수로 종료값
//1부터 100까지의 짝수 스트림 -> 50개
| IntStream evenNumbers = IntStream.rangeClosed(1, 100) .filter(n -> n % 2 == 0); System.out.println(evenNumbers.count()); | cs |
| Stream<int[]> pythagoreanTriples = IntStream.rangeClosed(1, 100).boxed() //flatmap 생성된 각각의 스트림을 하나의 평준화된 스트림으로 만들어준다 .flatMap(a -> IntStream.rangeClosed(a, 100) .filter(b -> Math.sqrt(a*a + b*b) % 1 == 0).boxed() .map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)})); pythagoreanTriples.limit(5).forEach(t -> System.out.println(t[0] + ", " + t[1] + ", " + t[2])); // 3, 4, 5 5, 12, 13 6, 8, 10 7, 24, 25 8, 15, 17 | cs |
값으로 스트림 만들기
임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용
//예제 Stream.of로 문자열 스트림 만드는 예제
| // Stream.of Stream<String> stream = Stream.of("Java 8", "Lambdas", "In", "Action"); stream.map(String::toUpperCase).forEach(System.out::println); // JAVA 8 LAMBDAS IN ACTION | cs |
empty 메서드를 이용 스트림 비울수 있음
| // Stream.empty Stream<String> emptyStream = Stream.empty(); | cs |
배열로 스트림 만들기
배열을 인수로 받는 정적 메서드 Arrays.stream을 이용해서 스트림을 만들 수 있음
| // Arrays.stream int[] numbers = {2, 3, 5, 7, 11, 13}; System.out.println(Arrays.stream(numbers).sum()); //41 | cs |
파일로 스트림 만들기
| long uniqueWords = 0; try { Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) { //스트림은 자원을 자동으로 해제할수 있는 AutoClosable uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))) //단어 스트림 생성 .distinct() //중복 제거 .count(); //고유 단어 수 계산 } } catch(IOException e) { //파일을 열다가 예외가 발생하면 처리 } | cs
|
함수로 무한 스트림 만들기
스트림 API는 함수에서 스트림을 만들수 있는 두 개의 정적 메서드
Stream.iterate,Stream.generate 제공 , 두연산 이용 무한스트림
고정된 컬렉션에서 고정된 크기의 스트림 아닌 크기가 고정되지 않은 스트림을 만들 수 있음
무한스트림 -> 언바운드 스트림
| //iterate Stream.iterate(0, n -> n + 2) .limit(10) .forEach(System.out::println); // 0 2 4 6 8 10 12 14 16 18 | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // fibonnaci with iterate //피보나치 수열 0,1,1,2,3,5,8,13,21,34,55 .. 수열은 0,1로 시작 이후의 숫자는 이전 두 숫자를 더한 값 Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1],t[0] + t[1]}) .limit(10) .forEach(t -> System.out.println("(" + t[0] + ", " + t[1] + ")")); (0, 1) (1, 1) (1, 2) (2, 3) (3, 5) (5, 8) (8, 13) (13, 21) (21, 34) (34, 55) Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1],t[0] + t[1]}) .limit(10) . map(t -> t[0]) .forEach(System.out::println); 0 1 1 2 3 5 8 13 21 34 | cs |
generate
iterate와 비슷하게 generate도 요구할 때 값을 계산하는 무한 스트림 만들수 있음
iterate와 달리 generate는 생산된 각 값을 연속적으로 계산하지 않음
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | // random stream of doubles with Stream.generate Stream.generate(Math::random) .limit(10) .forEach(System.out::println); //결과 0.45439035506715 0.6853459484535649 0.8658401960545284 0.5503893827405103 0.05879337081744085 0.17644180893979233 0.7766261445389754 0.3645132613836499 0.16948893721073588 0.9746557126985628 // stream of 1s with Stream.generate IntStream.generate(() -> 1) .limit(5) .forEach(System.out::println); //결과 1 1 1 1 1 IntStream.generate(new IntSupplier(){ public int getAsInt(){ return 2; } }).limit(5) .forEach(System.out::println); //결과 2 2 2 2 2 IntSupplier fib = new IntSupplier(){ private int previous = 0; private int current = 1; public int getAsInt(){ int nextValue = this.previous + this.current; this.previous = this.current; this.current = nextValue; return this.previous; } }; IntStream.generate(fib).limit(10).forEach(System.out::println); //결과 1 1 2 3 5 8 13 21 34 55 | cs |
요약
1.filter,distinct,skip,limit 메서드로 스트림을 필터링하거나 자를 수 있다
2.map,flatMap 메서드로 스트림의 요소를 추출하거나 변환할수 있다
3.findFirst,findAny 메서드로 스트림의 요소를 검색할 수 있다
allMatch,noneMatch,anyMatch 메서드를 이용해서 주어진 프레디케이트와 일치하는 요소를 스트림에서 검색할 수 있음
이들 메서드는 쇼트서킷 -> 결과를 찾는 즉시 반환, 전체 스트림을 처리하지 않음
4.reduce 메서드로 스트림의 모든 요소를 반복 조합하며 값을 도출할 수 있다
예) 스트림의 최대값이나 모든 요소의 합계 구할수 있음
5.filter,map 등은 상태를 저장하지 않는 상태 없는 연산
reduce 같은 연산은 값을 계산하는데 필요한 상태를 저장
6.IntStream,DoubleStream,LongStream 기본형 특화 스트림 -> 각각의 기본형에 맞게 특화
7.컬렉션뿐 아니라 값,배열,파일, iterate와 generate 같은 메서드로도 스트림을 만들수 있음
8.크기가 정해지지 않은 스트림을 무한 스트림이라고 함
댓글