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
•
람다를 통한 중복 제거, 왜 함수형 프로그래밍을 배워야 하는가, 자바지기 박재성