- 출처: Java 8 in action
- 람다란 무엇인가?
람다 표현식이란 메서드로 전달할 수 있는 익명함수를 단순화 한것이다. 람다 표현식에는 이름이 없으며, 파라미터 리스트, 바디, 반환형식, 발생할 수 있는 예외 리스트를 가지고 있다.
- 메소드와 달리 이름이 없으므로 익명이라 표현한다.
- 어떤 클래스에 종속되는 메소드가 아니라서 함수이다.
Java 8 이전에는 메소드를 전달하고 싶으면 익명클래스를 하나 선언하여 전달하였다. 이말이 무슨말인지 모를수도 있지만 아래와 같은 코드를 한번쯤은 사용해본적이 있을거라고 생각한다.
1 2 3 4 5 6 7 peoples.sort(new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getAge() - o2.getAge(); } });
위의코드에서 compare 메소드 안의 동작처럼 정렬을 하라는 의도라서 메소드 부분만 필요하지만 Java 8 이전에는 메소드만을 넘길 수가 없었으므로, Comparator 를 이용하여 이름을 짓지 않고 sort 함수에 넘겼다.
위의 코드는 좀 과도하다. 나이를 오름차순으로 정렬하라는 의도의 메소드 하나만 넘기면 되는데 클래스 자체를 선언해서 넘겼고 코드가 복잡해졌다. 이럴때 람다를 이용하면 코드가 훨씬 깔끔해진다.
1 peoples.sort((o1, o2) -> o1.getAge() - o2.getAge());
람다는 3부분으로 나뉘어 있다. 왼쪽에는 파라미터 리스트, 그리고 바디와 구분하는 화살표(->), 그리고 오른쪽에 함수 바디가 존재한다.
Java 8 에서는 아래와 같은 형식을 지원한다.
- 함수형 인터페이스
1 2 3 4 5 6 7 8 9 peoples.sort((o1, o2) -> o1.getAge() - o2.getAge()); peoples.sort((Person o1, Person o2) -> o1.getAge() - o2.getAge()); peoples.sort((o1, o2) -> { System.out.println(o1); System.out.println(o2); return o1.getAge() - o2.getAge(); });
여기서 의문점이 생긴다. sort 에 내가 인자 2개를 넘겨야 하는지 int 형이 반환이 되어야 하는지는 어떻게 판단하는것이고 무조건 람다를 아무데서나 사용할 수 있는것인가? 먼저 함수형 인터페이스 개념에 대해 알아보자.
함수형 인터페이스란 하나의 추상 메서드를 지정하는 인터페이스이다. 그냥 메서드가 아니고 추상 메서드라고 지칭한 이유가 있다. 앞에서 이용한 Comparator 소스 일부분을 살펴보자.
1 2 3 4 5 6 7 8 9 int compare(T o1, T o2); .............................................. default Comparator<T> reversed() { return Collections.reverseOrder(this); } ............................................. public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() { return Collections.reverseOrder(); }
우리가 사용한 compare 메소드와 수많은 default 메소드 그리고 static method 들이 있다. default 는 해당 메소드를 구현하지 않으면 default 메소드 안의 정의대로 동작이 된다.(Java 8 부터 지원이 되며, default 메소드는 나중에 알아본다.)
그런데 compare 메소드에 대한 동작 부분은 전혀 보이지 않는다. 비록 많은 default 나 static 메소드가 있지만 추상 메서드가 compare 하나 뿐이기 때문에, 이 인터페이스는 함수형 인터페이스이다.
그리고 Comparator 제일 위에 보면 친절하게 어노테이션까지 달아놓았다. 문자그대로 @FunctionalInterface(함수형 인터페이스)라고 말이다.
1 2 @FunctionalInterface public interface Comparator<T> {
아래 예제를 살펴보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public interface Calculator { int calculate(int a, int b); } public interface DoubleCalculator extends Calculator { double calculate(double a, double b); } public class ACompanyCalculator{ public int calcTwoInteger(int a, int b, Calculator calc) { return calc.calculate(a, b); } public double calcTwoDouble(double a, double b, DoubleCalculator doubleCalc) { return doubleCalc.calculate(a, b); } }
여기서 DoubleCalculator 도 추상메소드가 하나라서 함수형 인터페이스일까? 그렇지 않다. Calculator 를 확장하고 새로운 double 형에 대하여 추상메소드를 하나 더 선언하였기 때문에 해당 함수를 이용해보려고 하면 에러가 난다.
1 2 3 4 5 6 7 ACompanyCalculator aCalc = new ACompanyCalculator(); int left = 1; int right = 3; int sum = aCalc.calcTwoInteger(left, right, (o1, o2) -> o1 + o2); double multiply = aCalc.calcTwoDouble((double) left, (double) right, (o1, o2) -> o1 * o2);
7번째 행에서 The target type of this expression must be a functional interface 라는 문구가 보일것이다.- 함수형 인터페이스의 사용
함수형 메서드의 추상 메서드 시그니처를 함수 디스크립터라고 한다. Java 8 라이브러리 설계자들은 java.util.function 패키지에 여러가지 새로운 함수형 인터페이스를 정의해놓았다.
- Predicate
1 2 3 4 5 6 7 8 9 10 11 @FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t);Predicate는 test 라는 추상 메서드를 정의하며 T 형식을 받아 boolean 을 반환한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 public static List<String> nonEmptyStrings(List<String> orgList, Predicate<String> checkFunc){ List<String> results = new ArrayList<>(); for(String orgStr: orgList) { if(checkFunc.test(orgStr)) { results.add(orgStr); } } return results; }
1 2 3 4 5 6 7 8 9 public static void main(String[] args) { Predicate<String> isEmptyString = (String s) -> !s.isEmpty(); List<String> nonEmptyStrings = nonEmptyStrings(Arrays.asList("A", "", "BB", "CC", ""), isEmptyString); System.out.println(nonEmptyStrings); }- 실행결과- Consumer
1 [A, BB, CC]
1 2 3 4 5 6 7 8 9 @FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t);제네릭 형식 T를 받아서 void를 반환하는 accept 라는 추상메서드를 정의한다.
1 2 3 4 5 public static <T> void forEach(List<T> list, Consumer<T> c){ for(T i: list) { c.accept(i); } }- Function
1 2 forEach(Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println(i));
1 2 3 4 5 6 7 8 9 10 @FunctionalInterface public interface Function<T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t);Function은 T 를 인수로 받아 R을 반환하는 apply 라는 메서드를 정의한다.
1 2 3 4 5 6 7 8 9 10 11 public static <T, R> List<R> map(List<T> list, Function<T, R> func){ List<R> result = new ArrayList<>(); for(T t: list) { R r = func.apply(t); result.add(r); } return result; }
1 2 List<Integer> r = map(Arrays.asList("1","2","3","4","5"), (String i) -> Integer.parseInt(i) * Integer.parseInt(i));
'Language > Java' 카테고리의 다른 글
[Java 8] Stream - 1 (Stream 개요) (0) | 2021.02.11 |
---|---|
[Java 8] 람다(Lambda) - 3 (0) | 2021.02.11 |
[Java 8] 람다(Lambda) - 2 (0) | 2021.02.10 |
[Java 8] 객체로서의 함수 (0) | 2021.02.10 |
[Java 8] 함수형 프로그래밍 개요 (0) | 2021.02.10 |
댓글