펌글 : 출처 https://wikidocs.net/157858
람다(Lambda)
람다는 익명 함수(Anonymous functions)를 의미한다. 예제를 통해 람다에 대해서 자세히 알아보자.
일반적인 코드
먼저 다음과 같은 인터페이스를 보자.
interface Calculator {
int sum(int a, int b);
}
두 개의 정수를 입력으로 받아 정수의 결과값을 리턴하는 sum 함수를 정의한 인터페이스이다. Calculator 인터페이스를 사용하려면 다음처럼 Calculator 인터페이스를 구현해야 한다.
class MyCalculator implements Calculator {
public int sum(int a, int b) {
return a+b;
}
}
Calculator 인터페이스를 구현한 MyCalculator 클래스를 생성하였다. 그러면 이제 MyCalculator 클래스는 다음처럼 사용할 수 있다.
interface Calculator {
int sum(int a, int b);
}
class MyCalculator implements Calculator {
public int sum(int a, int b) {
return a+b;
}
}
public class Sample {
public static void main(String[] args) {
MyCalculator mc = new MyCalculator();
int result = mc.sum(3, 4);
System.out.println(result); // 7 출력
}
}
람다를 적용한 코드
이제 위에서 작성한 코드를 다음처럼 람다를 적용한 코드로 바꾸어 보자.
interface Calculator {
int sum(int a, int b);
}
public class Sample {
public static void main(String[] args) {
Calculator mc = (int a, int b) -> a +b;
int result = mc.sum(3, 4);
System.out.println(result);
}
}
위 코드에서 사용한 람다 함수는 다음과 같다.
(int a, int b) -> a +b
괄호 사이의 int a, int b는 Calculator 인터페이스의 sum 함수의 입력항목에 해당하고 -> 뒤의 a+b 가 리턴값에 해당한다. 이렇게 람다함수를 사용하면 MyCalculator와 같은 실제 클래스 없이도 Calculator 객체를 생성할 수 있다.
인터페이스 사용시 주의사항
여기서 주의해야 할 점은 Calculator 인터페이스의 메서드가 1개 이상이면 람다함수를 사용할 수 없다는 점이다.
interface Calculator {
int sum(int a, int b);
int mul(int a, int b); // mul 메서드를 추가하면 컴파일 에러가 발생한다.
}
그래서 람다 함수로 사용할 인터페이스는 다음처럼 @FunctionalInterface 어노테이션을 사용하는 것이 좋다. @FunctionalInterface 어노테이션을 사용하면 다음처럼 2개 이상의 메서드를 가진 인터페이스를 작성하는 것이 불가능해 진다.
@FunctionalInterface
interface Calculator {
int sum(int a, int b);
int mul(int a, int b); // @FunctionalInterface 는 두 번째 메서드를 허용하지 않는다.
}
람다 축약
다시 원래의 코드로 돌아가 보자. 위에서 작성한 람다 함수는 다음처럼 조금 더 축약이 가능하다.
(a, b) -> a + b
인터페이스에 이미 입출력에 대한 타입이 정의되어 있으므로 입력값의 타입인 int는 생략이 가능하다.
interface Calculator {
int sum(int a, int b);
}
public class Sample {
public static void main(String[] args) {
Calculator mc = (a, b) -> a +b;
int result = mc.sum(3, 4);
System.out.println(result);
}
}
Integer.sum
그리고 두 수를 더하여 그 결과를 리턴하는 함수 (a, b) -> a+b 는 Integer.sum(int a, int b)와 동일하기 때문에 다음과 같이 더 축약이 가능해진다.
Integer::sum
어떤 클래스의 메서드를 사용할 때는 위와 같이 :: 기호를 사용하여 클래스와 메서드를 구분하여 표기한다.
Integer::sum을 사용한 코드는 다음과 같다.
@FunctionalInterface interface Calculator { int sum(int a, int b); } public class Sample { public static void main(String[] args) { Calculator mc = Integer::sum; int result = mc.sum(3, 4); System.out.println(result); } }
람다 함수 인터페이스
이번에는 인터페이스를 생성하는 대신 함수형 프로그래밍을 위해 제공되는 인터페이스들을 사용하여 위 코드를 좀 더 축약해 보자.
import java.util.function.BiFunction;
public class Sample {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> mc = (a, b) -> a + b;
int result = mc.apply(3, 4); // sum이 아닌 apply 메서드를 사용해야 한다.
System.out.println(result); // 7 출력
}
}
BiFunction의 <Integer, Integer, Integer> 제네릭스는 순서대로 입력항목 2개, 출력항목 1개를 의미한다. 그리고 BiFunction 인터페이스의 apply 메서드를 호출하면 람다함수 (a, b) -> a + b가 실행된다.
BiFunction은 입출력 항목의 타입을 다양하게 사용할 수 있다.
그런데 우리가 작성한 코드를 자세히 보면 입출력 항목의 타입이 모두 Integer로 동일하다는 것을 알 수 있다. 이렇게 입출력 항목이 모두 동일할 경우에는 다음의 BinaryOperator를 사용하여 더 간단하게 표현할 수 있다.
import java.util.function.BinaryOperator;
public class Sample {
public static void main(String[] args) {
BinaryOperator<Integer> mc = (a, b) -> a + b;
int result = mc.apply(3, 4);
System.out.println(result); // 7 출력
}
}
스트림(Stream)
스트림은 글자 그대로 해석하면 "흐름" 이라는 뜻이다. 데이터가 물결처럼 흘러가면서 필터링 과정을 통해 여러번 변경되어 반환되기 때문에 이러한 이름을 갖게 되었다.
다음의 문제를 풀어보자.
스트림은 글자 그대로 해석하면 "흐름" 이라는 뜻이다.
데이터가 물결처럼 흘러가면서 필터링 과정을 통해 여러번 변경되어 반환되기 때문에
이러한 이름을 갖게 되었다.다음의 문제를 풀어보자.
다음과 같은 정수 배열이 있다.
int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};
이 배열에서 짝수만 뽑아 중복을 제거한 후에 역순으로 정렬하는 프로그램을 작성하시오.
즉, 프로그램의 수행 결과는 다음과 같아야 한다.
int[] result = {8, 6, 4, 2};
import java.util.*;
public class Sample {
public static void main(String[] args) {
int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};
// 짝수만 포함하는 ArrayList 생성
ArrayList<Integer> dataList = new ArrayList<>();
for(int i=0; i<data.length; i++) {
if(data[i] % 2 == 0) {
dataList.add(data[i]);
}
}
// Set을 사용하여 중복을 제거
HashSet<Integer> dataSet = new HashSet<>(dataList);
// Set을 다시 List로 변경
ArrayList<Integer> distinctList = new ArrayList<>(dataSet);
// 역순으로 정렬
distinctList.sort(Comparator.reverseOrder());
// Integer 리스트를 정수 배열로 변환
int[] result = new int[distinctList.size()];
for(int i=0; i< distinctList.size(); i++) {
result[i] = distinctList.get(i);
}
}
}
먼저 정수 배열에서 짝수만을 찾아 ArrayList에 넣고 Set을 사용하여 중복을 제거한 후에 다시 Set을 리스트로 변환했다. 그리고 리스트의 sort를 사용하여 역순으로 정렬하고 정렬된 값으로 다시 정수 배열을 생성했다. 복잡하지만 보통 이와 비슷한 과정을 통해 문제를 해결해야 한다.
이 문제는 스트림을 사용하면 다음과 같이 간단하게 해결할수 있다.
import java.util.Arrays;
import java.util.Comparator;
public class Sample {
public static void main(String[] args) {
int[] data = {5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};
int[] result = Arrays.stream(data) // IntStream을 생성한다.
.boxed() // IntStream을 Stream<Integer>로 변경한다.
.filter((a) -> a % 2 == 0) // 짝수만 뽑아낸다.
.distinct() // 중복을 제거한다.
.sorted(Comparator.reverseOrder()) // 역순으로 정렬한다.
.mapToInt(Integer::intValue) // Stream<Integer>를 IntStream으로 변경한다.
.toArray() // int[] 배열로 반환한다.
;
}
}
위 코드는 다음과 같은 순서로 동작한다.
- Arrays.stream(data)로 정수 배열을 IntStream으로 생성한다.
- .boxed() 로 IntStream을 Integer의 Stream으로 변경한다. 이렇게 하는 이유는 뒤에서 사용할 Comparator.reverseOrder 메서드는 원시타입인 int 대신 Integer를 사용해야 하기 때문이다.
- .filter((a) -> a % 2 == 0)로 짝수만 필터링한다. 이 때 사용한 (a) -> a % 2 == 0 구문은 위에서 공부한 람다 함수이다. 입력 a가 짝수인지를 조사하는 람다함수로 짝수에 해당되는 데이터만 필터링한다.
- .distinct()로 스트림에서 중복을 제거한다.
- .sorted(Comparator.reverseOrder())로 역순 정렬한다.
- .mapToInt(Integer::intValue)로 Integer의 Stream을 IntStream으로 변경한다. 왜냐하면 최종적으로 int[] 타입의 배열을 리턴해야 하기 때문이다.
- .toArray()를 호출하여 IntStream의 배열인 int[] 배열을 리턴한다.
스트림 방식은 일반적인 코드보다 확실히 간결하고 가독성이 좋다는 것을 확인할 수 있을 것이다.
'STUDY > JAVA' 카테고리의 다른 글
[JAVA] 자바 로그 - Logger (0) | 2023.05.24 |
---|---|
[JAVA] 자바 스레드(Thread) 총정리 (0) | 2023.05.16 |
[JAVA] HttpURLConnection 이용한 POST 호출 방법 (0) | 2023.05.10 |
[JAVA] 동기화 sychronized (0) | 2023.05.10 |
[JAVA] ReentrantLock 설명 (0) | 2023.05.08 |