자바는 Java 8 버전부터 함수형 프로그래밍을 지원하기 위해
람다(lambda)와 스트림(stream)을 도입했다.
람다와 스트림을 사용하면 함수형 프로그래밍 스타일로 자바 코드를 작성할 수 있다.
물론 람다와 스트림을 사용하여 작성한 코드를 일반 스타일 자바 코드로 바꾸어 작성하는 것이 불가능하지 않다.
그런데도 람다와 스트림을 사용하는 이유는 작성하는 코드의 양이 줄어들고
읽기 쉬운 코드를 만들 수 있기 때문이다.
이 책에서는 람다와 스트림이 무엇인지 어떻게 사용하는지만 알아본다.
- 람다(lambda)
익명 함수(anonymous function)을 의미한다.
일반적인 코드와 람다를 적용한 코드를 비교하여 알아보자
일반적인 코드
두 개의 정수를 입력받아 정수의 결괏값을 리턴하는 sum 함수를 정의한 인터페이스이다.
interface Calulator {
int sum(int a, int b);
}
앞서 정의한 인터페이스를 사용하려면 Calculator인터페이스를 구현해야한다.
class MyCalculator implements Calculator {
public int sum(int a, int b) {
return a+b;
}
}
아래는 완성한 프로그램이다.
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 는 두 번째 메서드를 허용하지 않는다.
}
@FunctionalInterface 는 인터페이스가 함수형 인터페이스임을 표시하며,
단 하나의 메서드만 가질 수 있게 한다. 주로 람다표현식과 같이 사용한다.
- 람다 축약하기
람다를 적용한 자바코드는 다음처럼 좀 더 축약이 가능하다.
인터페이스에 이미 입출력에 대한 타입이 정의되어 있으므로 입력값의 자료형인 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);
}
}
- 더 축약하기
두 수를 더하여 그 결과를 리턴하는 함수 (a,b) → a+b는
Integer.sum(int a , int b)와 동일하기 때문에 더 축약이 가능해진다
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);
}
}
- 스트림
스트림(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[] 배열을 리턴한다.
스트림 방식은 일반적인 코드보다 확실히 간결하고 가독성이 좋다는 것을 확인할 수 있을 것이다.
참고자료 :
위키독스 점프 투 자바 https://wikidocs.net/book/31
'Java' 카테고리의 다른 글
자바 가상 머신 JVM(Java Virtual Machine) (0) | 2024.02.27 |
---|---|
String, StringBuffer, StringBuilder (1) | 2024.02.26 |
[점프 투 자바] 7장 자바 날개달기1 (1) | 2024.01.01 |
[점프 투 자바] 6장 자바의 입출력 (1) | 2024.01.01 |
[점프 투 자바] 5장 객체지향 프로그래밍2 (1) | 2024.01.01 |