Search

Lambda & Stream 활용

1. 활용 가이드

가. Stream<Integer> 보다 IntStream을 적극 활용

Stream<Integer>를 int 형 등의 기본형으로 unboxing하는 것은 비효율적인 작업
IntSream 등 기본 자료형의 Stream으로 처리하는데 유용한 메소드들이 많이 있음

나. stream만으로 배열의 값 생성하기

첫번째 생성한 stream으로 배열 구성
→ flatMap 사용하여 Integer → Stream<int[]>로 변환
IntStream.rangeClosed(START_NUM, END_NUM) .boxed() // flatMap은 IntStream을 인자로 받지 않음, Stream<T[]> -> Stream<T>로 변환 // integer -> Stream.of(1, 2, 3, 4, 5, 6) : 1 ~ 6까지를 6번 생성 // .map(integer1 -> new int[]{integer, integer1}) : {1, 1}, {1, 2} ~ {6, 6} 생성 .flatMap(integer -> Stream.of(1, 2, 3, 4, 5, 6).map(integer1 -> new int[]{integer, integer1})) .forEach(arr -> System.out.println(arr[0] + " " + arr[1]));
Java
복사
만약 flatMap 대신 map을 사용했다면 Integer → Stream<Stream<int[]>>으로 변환됨
→ 내부에 map이 쓰이기 때문에 flatMap으로 끊어줘야함

다. Map::values() → List

다음과 같이 ArrayList 생성자의 인자로 tasks.values()를 전달하여 List 타입으로 반환 받을 수 있음
Map<Integer, Task> tasks = new HashMap<>(); @Override public List<Task> findAllByCompletedFalse() { return new ArrayList<>(tasks.values()); }
Java
복사
단, Map의 value 각각에 조건을 걸기 위해서는 stream 활용
Map<Integer, Task> tasks = new HashMap<>(); @Override public List<Task> findAllByCompletedFalse() { return tasks.values().stream() .filter(task -> !task.isCompleted()) .collect(Collectors.toCollection(ArrayList::new)); }
Java
복사

람다를 통한 중복 제거

적용 절차
1) 공통부분과 변경부분을 구분
2) 변경부분에 대해 인터페이스의 메소드로 선언
3) 인터페이스의 메소드를 본문의 메소드에 인자로 전달함
→ 하나의 인터페이스에는 하나의 메소드만 선언됨
→ 단, 인터페이스의 메소드를 overriding해서 사용할 수 있음
4) 인터페이스의 메소드의 구현부는 본문의 메소드를 사용하는 곳(테스트 코드 등)에 익명 클래스로 구현
5) 익명 클래스를 람다로 변경
6) 스트림을 활용해서 본문의 유사 메소드 여러 개를 하나의 메소드로 통합
실습(위의 적용 절차에 따라)
1) Lambda.java의 변경부분은 내부 조건(노란색 배경 부분), 공통부분은 그 외 전부
// Lambda public static int sumAll(List<Integer> numbers) { int total = 0; for (int number : numbers) { total += number; } return total; } public static int sumAllEven(List<Integer> numbers) { int total = 0; for (int number : numbers) { if (number % 2 == 0) { total += number; } } return total; } public static int sumAllOverThree(List<Integer> numbers) { int total = 0; for (int number : numbers) { if (number > 3) { total += number; } } return total; }
Java
복사
2) 변경부분에 대해 인터페이스의 메소드로 선언
public interface Conditional { boolean condition(Integer number); }
Java
복사
3) 인터페이스의 메소드를 본문의 관련 메소드에 인자로 전달
public static int sumAll(List<Integer> numbers, Conditional conditional) { int total = 0; for (int number : numbers) { if (conditional.condition(number)) { total += number; } } return total; } public static int sumAllEven(List<Integer> numbers, Conditional conditional) { int total = 0; for (int number : numbers) { if (conditional.condition(number)) { total += number; } } return total; } public static int sumAllOverThree(List<Integer> numbers, Conditional conditional) { int total = 0; for (int number : numbers) { if (conditional.condition(number)) { total += number; } } return total; }
Java
복사
4) 본문의 메소드를 사용하는 곳에서 인터페이스의 메소드의 구현부를 익명클래스로 작성
// LambdaTest @Test public void sumAll() throws Exception { int sum = Lambda.sumAll(numbers, new Lambda.Conditional() { @Override public boolean condition(Integer number) { return true; } }); assertThat(sum).isEqualTo(21); } @Test public void sumAllEven() throws Exception { int sum = Lambda.sumAllEven(numbers, new Lambda.Conditional() { @Override public boolean condition(Integer number) { return number % 2 == 0; } }); assertThat(sum).isEqualTo(12); } @Test public void sumAllOverThree() throws Exception { int sum = Lambda.sumAllOverThree(numbers, new Lambda.Conditional() { @Override public boolean condition(Integer number) { return number > 3; } }); assertThat(sum).isEqualTo(15); }
Java
복사
5) 익명 클래스를 람다로 대체
// LambdaTest @Test public void sumAll() throws Exception { int sum = Lambda.sumAll(numbers, number -> true); assertThat(sum).isEqualTo(21); } @Test public void sumAllEven() throws Exception { int sum = Lambda.sumAllEven(numbers, number -> number % 2 == 0); assertThat(sum).isEqualTo(12); } @Test public void sumAllOverThree() throws Exception { int sum = Lambda.sumAllOverThree(numbers, number -> number > 3); assertThat(sum).isEqualTo(15); }
Java
복사
6) 스트림을 활용해서 본문의 유사 메소드 다수를 하나의 메소드로 통합
// Lambda.java public static int sumAllByCondition(List<Integer> numbers, Conditional conditional) { return numbers.stream() .filter(number -> conditional.condition(number)) .reduce(0, (frontNum, nextNum) -> Integer.sum(frontNum, nextNum)); } // to(메소드 참조 활용하여 간략하게 정리) public static int sumAllByCondition(List<Integer> numbers, Conditional conditional) { return numbers.stream() .filter(conditional::condition) .reduce(0, Integer::sum); }
Java
복사

2. 스트림 생성, 중개연산, 최종연산

가. 배열과 스트림

1번
//문자열 배열 strArr의 모든 문자열의 길이를 더한 결과를 출력 //String[] strArr = { "aaa", "bb", "c", "dddd" }; //[실행결과] //sum = 10 // 배열 내 모든 요소를 Stream의 요소로 변환 Stream<String> strStream = Stream.of(strArr); // strStream 내 모든 요소를 길이값으로 변환 후(중개연산), 총합을 구해서(최종연산) 반환 int sum = strStream.mapToInt(string -> string.length()).sum(); // or int sum = strStream.mapToInt(String::length).sum(); System.out.println(sum);
Java
복사
// 다른 풀이 Stream<String> strStream = Stream.of(strArr); // Integer 타입의 Stream에 길이값 저장 Stream<Integer> integerStream = strStream.map(string -> string.length()); // 반환형을 Integer로 하기 위해 reduce의 첫번째 인자로 0 삽입 Integer sum = integerStream.reduce((0, (a, b) -> Integer.sum(a, b)); System.out.println(sum);
Java
복사
2번
//문자열 배열 strArr의 문자열 중에서 가장 긴 것의 길이를 출력 //String[] strArr = { "aaa", "bb", "c", "dddd" }; // //[실행결과] //4 String[] strArr = { "aaa", "bb", "c", "dddd" }; // String Stream으로 모든 String[] 요소의 변환 Stream<String> stringStream = Stream.of(strArr); // String Stream 요소의 길이값으로 변환 // 길이값 비교해서 가장 큰 값 반환 OptionalInt maxElement = stringStream.mapToInt(string -> string.length()).max(); // 결과값 출력 System.out.println(maxElement.getAsInt())
Java
복사
// 다른 풀이 Stream<String> strStream = Stream.of(strArr); strStream.map(String::length) .sorted(Comparator.reverseOrder()) //문자열 길이로 역순정렬 .limit(1).forEach(System.out::println); //제일 긴 문자열의 길이 출력
Java
복사
3번
//문자열 배열 strArr의 문자열 중에서 가장 긴 문자열을 출력 //String[] strArr = { "aaa", "bb", "c", "dddd" }; // //[실행결과] //"dddd" String[] strArr = {"aaa", "bb", "c", "dddd"}; // String Stream 생성 Stream<String> stringStream = Stream.of(strArr); // 역순으로 정렬하되, key는 String Stream의 각 요소의 길이로 설정함 stringStream.sorted(Comparator.comparingInt(String::length).reversed()) .limit(1).forEach(System.out::println);
Java
복사

나. 무작위 수와 스트림

로또 번호
//임의의 로또번호(1~45)를 정렬해서 출력 //[실행결과] //1 //20 //25 //33 //35 //42 import java.util.Comparator; import java.util.Random; import java.util.stream.Stream; public class LAMDBA4 { public static void main(String[] args) { // Random().ints(): 범위의 난수를 무제한으로 반환하는 무제한 스트림 new Random().ints(1, 46) .distinct() // 중복값 제거 .limit(6) // 반환 개수를 6개로 한정 .sorted() // 정렬됨 .forEach(System.out::println); } }
Java
복사
주사위 값 다루기
final int START_NUM = 1; final int END_NUM = 6; // 1, 2번 주사위 눈의 값 생성(1 ~ 6), IntStream. IntStream.rangeClosed(START_NUM, END_NUM).boxed() .flatMap(integer -> Stream.of(1, 2, 3, 4, 5, 6).map(integer1 -> new int[]{ integer, integer1})) .filter(arr -> arr[0] + arr[1] == 6) .forEach(arr -> System.out.println("[" + arr[0] + "," + arr[1] + "]"));
Java
복사

3. 메소드 → 람다

일반적인 경우
int max(int a, int b) { return a > b ? a : b; } // to (int a, int b) -> (a > b) ? a : b
Java
복사
서로 다른 타입의 매개변수가 있는 경우
int printVar(String name, int i) { System.out.println(name+"="+i); } // to (String name, int i) -> System.out.println(name+"="+i)
Java
복사
3번
int square(int x) { return x*x; } // to (int x) -> x*x
Java
복사
4번
int roll() { return (int)(Math.random() * 6); } // to () -> (int)(Math.random() * 6)
Java
복사
5번
int sumArr(int[] arr) { int sum = 0; for(int i : arr) sum += i; return sum; } // to (int[] arr) -> { int sum = 0; for(int i : arr) sum += i; return sum; }
Java
복사
6번
int[] emptyArr() { return new int[] {}; } // to () -> new int[]{}
Java
복사

4. 람다 → 메소드 참조

1번
(String s) -> s.length() // to String::length
Java
복사
2번
() -> new int[]{} // to int[]::new
Java
복사
3번
arr -> Arrays.stream(arr) // to Arrays::stream
Java
복사
4번
(String str1, String str2) -> strl.equals(str2) // to // equals의 메소드의 정의를 보면 전달받는 매개변수의 위치가 이미 지정됨 String::equals
Java
복사
5번
(a, b) -> Integer.compare(a, b) // to Integer::compare
Java
복사
6번
(String kind, int num) -> new Card(kind, num) // to Card::new
Java
복사
7번
(x) -> System.out.println(x) // to System.out::println
Java
복사
8번
() -> Math.random() // to Math::random
Java
복사
9번
(str) -> str.toUpperCase() // to String::toUpperCase
Java
복사
10번
() -> new NullPointerException() // to NullPointerException::new
Java
복사
11번
(Optional opt) -> opt.get() // to Optional::get
Java
복사
12번
(StringBuffer sb, String s) -> sb.append(s) // to StringBuffer::append
Java
복사
13번
(String s) -> System.out.println(s) // to System.out::println
Java
복사

Reference

자바의 정석, 남궁성
자바의 정석 연습문제 풀이 참고, https://developer-ek.tistory.com/20
람다를 통한 중복 제거, 왜 함수형 프로그래밍을 배워야 하는가, 자바지기 박재성