반응형

[JAVA] 람다식(Lambda Expression)


안드로이드 자바로 스도쿠 어플을 개발하던 중 이상한 것을 발견했다. 그건 바로 -> 화살표였다.

응? 화살표? 내가 알고 있는 화살표는 C/C++에서 포인터 쓸 때 사용했는데...?

찾아보니 JAVA 8에서부터 지원하는 람다식(Lambda Expression)이라는 녀석이었다. 고로 이녀석을 조금 더 파헤쳐보자.



함수형 프로그래밍


람다식은 자바 8에 추가된 가장 큰 특징의 하나로 자바에서 함수형 프로그래밍 형태를 받아들인 결과이다.

그렇다면 함수형 프로그래밍은 무엇인가?

함수형 프로그래밍은 1990년대부터 가장 대중적인 프로그래밍의 형태로 인기를 끌고 있는 객체지향 프로그래밍과 함께 최근 주목받고 있는 프로그래밍 방식이다. 사실 함수형 프로그래밍은 1950년대부터 있었으므로 객체지향보다 역사가 오래되었다.


이번 포스팅은 함수형 프로그래밍을 중점으로 다루는게 아닌 람다식을 다루는 포스팅이니 간단하게 '기능 위주의 프로그래밍 기법'이라고만 정의를 하겠다.

객체지향 프로그래밍과의 차이점이라면 객체지향 프로그래밍은 클래스에 속성과 기능을 정의한다. 즉 기능이 객체에 포함된 개념이다. 하지만 함수형 프로그래밍에서는 기능, 즉 함수가 따로 존재한다.



객체지향 프로그래밍과 비교


그렇다면 본격적으로 객체지향과 함수형 프로그래밍을 비교해보자.

예를 들어 배열의 정렬이라는 주제로 살펴보자. Arrays 클래스가 제공하는 sort() 메소드를 이용하면 배열 요소를 정렬할 수 있다.


String strs[] = {"this", "is", "java", "world"};
System.out.println(Arrays.toString(strs));
Arrays.sort(strs);
System.out.println(Arrays.toString(strs));


이 코드를 실행하면 알파벳 오름차순으로 정렬된 결과를 볼 수 있다. Arrays의 sort(String[]) 메소드는 문자열을 비교할 때 Comparator의 compare() 메소드를 사용한다. compare() 내부에서는 String의 compareTo()를 이용하는데 두 문자열을 알파벳 순으로 비교해서 두번째 인자 값이 크면 양수, 작으면 음수, 같으면 0을 리턴한다. 그래서 compare()의 결과 양수가 반환되면 두 문자열은 자리를 바꾼다.


그런데 원하는 정렬의 기준을 알파벳 내림차순 또는 글자 수를 기준으로 오름차순 등 기존에 제공하지 않는 방식으로 처리하려면 어떻게 해야 할까?

sort()가 가지는 기본 정렬 방식을 사용하지 않으려면 새로운 정렬 방식을 적용해 주어야 한다.



이를 위해 필요한 새로운 기능을 만들고 Arrays.sort()에 전달하면 되는데 자바에서는 기능, 즉 함수만을 전달할 수는 없다. 자바에서 전달할 수 있는 것은 오로지 객체이다. 그래서 기능을 가지는 클래스를 만들고 그 클래스의 객체를 전달한다. 많은 경우 이런 정렬 방식은 해당 클래스에서만 유효한 경우가 많다.


이제 머리에 떠오르는 것은 내부 클래스일 것이다. 특히 일회용의 정렬 기능 작성을 위해서는 익명의 내부 클래스만한 것이 없다. 다음은 익명의 내부 클래스를 이용해 내림차순 정렬을 위한 Comparator를 작성하고 전달하는 코드이다.


Arrays.sort(strs, new Comparator <String>() {     @Override     public int compare(String o1, String o2) {         return o1.compareTo(o2) * -1;     } }); System.out.prinln(Arrays.toString(strs));


위 코드는 잘 동작한다. 여기서 우리가 주목할 내용은 정렬을 위해 필요했던 '기능'은 Comparator가 아니라 사실 compare()라는 점에 주목할 필요가 있다. compare()만 있으면 되지만, 자바 언어의 한계 또는 특성으로 인해 익명의 내부 클래스를 만들고 객체화해서 전달하고 있다.



람다식


자바 8부터 등장한 람다식은 이런 번거로움을 없애 준다. 위 코드를 람다식으로 표현하면 다음과 같다.


Arrays.sort(strs, (o1, o2) -> {return o1.compareTo(o2) * -1 });
System.out.println(Arrays.toString(strs));


사실 람다식을 처음 접하고선 감탄했다. 한눈에 보아도 분명 코드가 간결해진 것을 알 수 있다. 람다식은 객체가 필요한 자리에 단순히 코드 블록만을 전달해 준다. 이 람다식이 바로 함수라고 볼 수 있다. 람다식은 런타임에 익명의 내부 클래스로 변경돼서 처리되므로 동작은 동일하다.


람다식은 결과적으로 인터페이스 타입의 클래스를 손쉽게 구현하는 방법이다. 이런 인터페이스를 함수형 인터페이스 또는 타겟 타입이라고 한다.


중요한 것은 함수형 인터페이스에는 반드시 하나의 abstract 메소드만 존재해야 한다. 만약 abstract 메소드가 없거나 두 개 이상 존재한다면 람다식으로 대체할 수 없다.


함수형 인터페이스에는 일반적으로 @FunctionalInterface 어노테이션이 선언된다. 이 어노테이션은 필수 요소는 아니지만 컴파일러에게 '이 인터페이스는 람다식에서 사용할 함수형 인터페이스이므로 abstract 메소드가 하나만 있는지를 검사하라.'고 요청한다. 따라서 하나 이상의 abstract 메소드를 작성하면 오류가 발생한다. 즉 안정적인 프로그래밍을 위한 옵션이다.


다음 포스팅에서는 이 람다식에 대해 조금 더 자세히 살펴보겠다.






반응형
반응형