함수형 프로그래밍(람다,스트림)

19890 단어 JavaJava

자바는 Java 8버전부터 함수형 프로그래밍을 지원하기 위해 람다(Lambda)와 스트림(Stream)이 도입되었습니다.
람다와 스트림을 사용하면 요새 유행하는 함수형 프로그래밍 스타일로 자바 코드를 작성할 수 있습니다.

  • 물론 람다와 스트림을 사용하여 작성한 코드를 일반 스타일의 자바코드로 바꾸어 작성하는 것이 불가능 하지는 않습니다.
    다르게 말하면 람다와 스트림 없이도 자바 코드를 작성하는데 어려움은 없지만, 그럼에도 불구하고 람다와 스트림을 사용하는 이유는 작성하는 코드의 양이 줄어들어 읽고 쉬운 코드를 만드는데 유리하기 때문입니다.

람다(Lambda)

람다는 익명 함수(Anonymous functions)를 의미합니다.
자바에는 메서드만 있지만 람다에 의해서 함수라는 개념이 도입된 것입니다.

먼저 다음과 같은 인터페이스를 한번 보면

interface Calculator{
	int sum(int a,int b);
    }

두개의 정수를 입력으로 받아 정수의 결과값을 리턴하는 sum함수를 정의한 인터페이스입니다.Calculator인터페이스를 사용하려면 다음처럼 Calculator 인터페이스를 구현해야 합니다.

public class MyCalculator implements Calculator{
    @Override
    public int sum(int a, int b) {
        return a+b;
    }
}

Calculator 인터페이스를 구현한 MyCalculator 클래스를 생성하였다. 그러면 이제 MyCalculator 클래스는 다음처럼 사용할 수 있다.

public class CalculatorTest {
    public static void main(String[] args) {
        MyCalculator mc = new MyCalculator();
        int result = mc.sum(3,4);
        System.out.println(result); //7 출력
    }
}

이제 위에서 작성한 코드를 람다를 적용한 코드로 바꾸어 보자.

public class CalculatorLambda {
    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 어노테이션을 사용하는 것이 좋다.
이것을 사용하면 다음처럼 2개 이상의 메서드를 가진 인터페이스를 작성하는 것이 불가능해진다.

@FunctionalInterface
interface Calculator {
    int sum(int a, int b);
    int mul(int a, int b);  // @FunctionalInterface 는 두 번째 메서드를 허용하지 않는다.
}

람다 축약

다시 원래의 코드로 돌아가보면,위에서 작성한 람다 함수는 다음처럼 조금더 축약이 가능하다.

(a,b) ->a +b;

인터페이스에 이미 입출력에 대한 자료형이 정의되어 있으므로 입력값의 타입인 int는 생략가능하다.

integer.sum

그리고 두 수를 더하여 그 결과를 리턴하는 함수(a,b)->a+b는 Integer.sum(int a, int b)와 동일하기 때문에 다음과 같이 더 축약이 가능해진다.

Interger :: sum
  • 어떤 클래스의 메서드를 사용할 때는 위와 같이 ::기호를 사용하여 클래스와 메서드를 구분하여 표기한다.
    이제 Interger :: sum을 사용해보면,
@FunctionalInterface
interface Calculator1 {
    int sum(int a, int b);
}
public class LambdaPlus  {
    public static void main(String[] args) {
        Calculator mc = Integer :: sum;
        int result = mc.sum(3,4);
        System.out.println(result);	//7출력
    }
}

스트림(Stream)

스트림은 글자 그대로 해석하면 "흐름"이라는 뜻이다. 데이터가 물결처럼 흘러가면서 필터링 과정을 통해 여러번 변경되어 반환하기 때문에 이러한 이름을 갖게 되었다.

int [] data = {5,6,3,2,3,1,1,2,2,4,8}
//이 배열에서 짝수만 찾아 중복을 제거한 후에 역순으로 정렬하는 프로그램을 작성하시오., 프로그램의 수행 결과는,
int[] result ={8,6,4,2};
//가 되어야 한다.

먼저 정수 배열에서 짝수만을 찾아 ArrayList에 넣고 Set을 사용하여 중복을 제거한 후에 다시 Set을 리스트로 변환했다. 그리고 리스트의 sort를 사용하여 역순으로 정렬하고 정렬된 값으로 다시 정수 배열을 생성했다.
보통 이와 같은 비슷한 과정을 통해 문제를 해결해야 한다.
이 문제는 스트림을 사용하면 다음과 같이 해결할수 있다.

package LackJava.Programing;
import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.Comparator;

public class StreamTest {
    public static void main(String[] args) {
        int [] data ={5, 6, 4, 2, 3, 1, 1, 2, 2, 4, 8};
        IntStream result =Arrays.stream(data);
                 .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[] 배열을 리턴한다.
    스트림 방식은 일반적인 코드보다 확실히 간결하고 가독성이 좋다는 것을 확인할 수 있을 것이다.

좋은 웹페이지 즐겨찾기