본문 바로가기
Language/Java

[Java 8] 람다(Lambda) - 1

by ocwokocw 2021. 2. 10.
- 출처: 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);
}
- 실행결과
1
[A, BB, CC]
- Consumer
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);
	}
}
1
2
forEach(Arrays.asList(1, 2, 3, 4, 5), 
	(Integer i) -> System.out.println(i));
- Function
 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

댓글