예전의 자바에서는 함수타입을 표현할 때 추상메서드를 하나만 담은 인터페이스(함수 객체)를 사용했다.

함수객체는 특정 함수나 동작을 나타내는데 쓰였다.

 

JDK1.1 이 등장하면서 함수 객체를 만드는 주요 수단은 익명 클래스가 사용되었다.

 

아래는 문자열을 길이순으로 정렬하기 위한 비교함수로 익명 클래스를 사용하는 예이다.

Collections.sort(words, new Comprator<String>(){
	public int compare(String s1, String s2){
    		return Integer.compare(s1.length(), s2.length());
    	}
}

위 코드에서는 Comparator 인터페이스가 정렬을 담당하는 추상 전략을 뜻하며, 문자열을 정렬하는 구체적인 전략을 익명 클래스로 구현했다.

 

하지만 위의 방식은 코드가 너무 길어지기에 자바에는 함수형 프로그래밍이 적합하지 않았다.

 

자바8과 함께 람다식(Lambda expression)이 등장하면서 추상메서드 하나짜리 인터페이스는 특별한 의미를 인정받아 대우를 받게 되었다.

 

람다는 함수나 익명클래스와 개념은 비슷하지만 코드는 훨씬 간결하다.

 

아래는 익명 클래스를 사용했던 소스와 똑같은 기능을 람다로 표현한 것이다.

Collections.sort(words,(s1, s2)->Integer.compare(s1.length(),s2.length());

위 코드를 보면 람다, 매개변수, 반환값의 타입에 대한 언급이 없다.

컴파일러가 문맥을 살펴 타입을 추론해주기 때문이다.

이펙티브 자바에서는 람다식을 작성할 때 타입을 명시해야 코드가 더 명확할 때만 제외하고는 람다의 모든 매개변수 타입은 생략하는것을 권장하고있다.

 

그런다음 타입을 알 수 없다는 오류가 발생할 때만 해당 타입을 명시하면 된다.

 

위 람다 소스에서 비교자 생성 메서드를 사용하면 코드를 더 간결하게 만들 수 있다.

Collections.sort(words, comparingInt(String::length));

 

여기서 더 나아가 자바8 의  List 인터페이스에 추가된 sort 메서드를 이용하면 더더 간결해진다.

words.sort(comparingInt(String::length));

 

람다가 언어차원에서 지원이 되면서 부터 함수 객체를 실용적으로 사용할 수 있게 되었다.

 

[Item.32]의 Operation 열거 타입을 예로 들어보겠다.

필요한 공부 순서로 진행하므로 아직 Item.32의 내용이 없을 수 있다.

 

아래의 코드는 apply 메서드의 동작이 상수마다 달라야 해서 상수별 클래스를 몸체에 사용해 각 상수에서 apply메서드를 재정의 한 것이다.

public enum Operation{
	PLUS("+"){
    	public double apply(double a, double b) { return a + b); }
    }
    MINUS("-"){
    	public double apply(double a, double b) { return a - b); }
    }
    TIMES("*"){
    	public double apply(double a, double b) { return a * b); }
    }
    DIVIDE("/"){
    	public double apply(double a, double b) { return a / b); }
    }
    
    private final String symbol;
    
    Operation(String symbol) { this.symbol = symbol; }
    
    @Override public String toString() { return symbol; }
    public abstract double apply(double a, double b);
}

 

람다를 이용하면 열거 타입의 인스턴스 필드를 이용하는 방식으로 상수별로 다르게 동작하는 코드를 쉽게 구현할 수 있다.

public enum Operation {
	PLUS ("+", (a,b) -> a + b);
    MINUS("-", (a,b) -> a - b);
    TIMES("*", (a,b) -> a * b);
    DIVIDE("/", (a,b) -> a / b);
    
    private final String symbol;
    private final DoubleBinaryOperator op;
    
    Operation(String symbol, DoubleBinaryOperator op) {
    	this.symbol = symbol;
        this.op = op;
    }
    
    @Override public String toString() { return symbol; }
    
    public double apply(double a, double b){
    	return op.applyAsDouble(a, b);
    }
}

 

이 코드에서는 람다를 DoubleBinaryOperator 인터페이스 변수에 할당한 부분이 있는데,
이는 java.util.function 패키지가 제공하는 인터페이스 중 하나이다.
기능은 double 타입 인수 2개를 받아 double타입 결과를 리턴한다.

 

* 주의점 : 

람다는 이름이 없고 문서화도 할 수 없다. 따라서 코드 자체의 가독성이 떨어지거나 코드의 줄 수가 길어지면 람다를 쓰지 말아야 한다.

 

 

이처럼 람다의 시대가 열리면서 익명 클래스는 설 자리가 크게 좁아지게 되었다. 하지만 람다로 대체할 수 없는 곳이 있다.

람다는 함수형 인터페이스에서만 쓰인다.  추상 클래스의  인스턴스를 만들 때 람다를 쓸 수 없으니 익명 클래스를 써야한다. 추상메서드가 여러개인 인터페이스의 인스턴스를 생성할 때도 익명 클래스를 쓸 수 있다.

 

추가로 람다는 자신을 참조할 수 없다. 람다에서의 this 키워드는 바깥 인스턴스를 가리킨다.

그래서 함수 객체가 자신을 참조해야 한다면 반드시 익명 클래스를 써야 한다.

 

자바8 버전이 등장하면서 작은 함수 객체를 구현하는데 적압한 람다가 도입되었다.
익명 클래스는 (함수형 인터페이스가 아닌) 타입의 인스턴스를 만들 떄만 사용하라.
람다는 작은 함수 객체를 아주 쉽게 표현할 수 있어 함수형 프로그래밍의 지평을 열었다.

+ Recent posts