자바 함수형 인터페이스 활용

2018, Jun 26    

함수형 인터페이스


  • java8에서 동작(함수)를 파라미터화 하기 위해서 클래스의 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스라는 기법을 사용하였다.
  • 익명 클래스를 전달 할 시 코드의 장황함을 해결하기 위해서 람다 표현식이 활용 된다.
//자바의 Onclick인터페이스
public interface OnClickListener {
    void onClick(View v);
}

//Onclick타입의 변수를 인자로 받는 메소드
public class Button {
    public void setOnClickListener(OnClickListener l) {
            //......
    }
}

//익명클래스를 이용해 파라미터 전달
Button button = new Button();
button.setOnClickListener(new OnClickListener() {
        @override
        public void onClick(View v) {
            System.out.println("button view");
        }

    }) {
        //.....
}


//익명 클래스 대신 람다식을 인자로 넘김
button.setOnClickListener(v->{ System.out.println("button view"); })
  • 위와 같이 람다식을 인자로 넘기는 코드가 가능한 이유는 OnclickListener에 추상 메소드가 단 하나만 있기 때문이다.
  • 이렇게 하나의 추상 메소드를 가진 인터페이스를 함수형 인터페이스(functional interface) 또는 SAM인터페이스-single abstract method라고 한다.


코틀린은 함수형 인터페이스를 인자로 취하는 자바 메소드를 호출할 때 람다를 넘길 수 있게 해준다.

//자바
public class FunctionalTest {

        public String setFunctional(int num, IntFunction<Integer> f) {
            return "function Result : " + f.apply(num);
        }

        public void setFunctionalCustom(Runnable r) {
        }
}

> > > val ft = FunctionalTest()
>>> println(ft.setFunctional(5,{i->i*i}))
function Result : 25

>>> println(ft.setFunctional(5){i->i*i})
function Result : 25


익명클래스 전달코드를 간소화 하기 위해 람다식을 이용하지만, 내부적으로 람다와 익명클래스 사이에는 차이가 있다.

  • 익명클래스를 이용할 시에는 매번 새로운 익명 클래스의 인스턴스가 생성 되지만,
    람다는 단 하나의 인스턴스만 만들고 그 무명 객체를 메소드를 호출할 때마다 반복 사용한다.
//람다를 이용할시 IntFunction타입의 인스턴스 한번만 생성됨.

//example1
ft.setFunctional(5){i->i*i}



//example2
val intFunc = IntFunction { i->i*i }
println(ft.setFunctional(5, intFunc))
  • 매번 같은 인스턴스를 사용한다면, 란다가 주변 영역의 변수는 어떻게 포획(capture)하는걸까? 이런 경우 컴파일러는 매번 주변 영역의 변수를 포획한 새로운 인스턴스를 생성해 준다.
//자바 (In FunctionalTest class)
public void setFunctionalCustom(Runnable r) {
}

//코틀린
fun callCustom(id: String) {

    // "id"변수 포획
     setFunctionalCustom{ println(id) } // <-- 새로운 "id" 참조를 위해 매번 새로운 Runnable 인스턴스를 만들어 낸다.
}

SAM생성자를 통한 람다 반환


컴파일러가 자동으로 람다를 함수형 인터페이스의 익명 클래스로 바꾸지 못하는 경우 SAM생성자를 사용해야 하는데,
바로 함수형 인터페이스의 인스턴스를 반환하는 메소드가 있을 경우 그렇다.

//오류 발생!
fun createAllDoneFunnable() : Runnable {
    return { println("All Done!") }
}

//Runnable생성자를 통해 리턴
fun createAllDoneFunnable() : Runnable {
    return Runnable { println("All Done!") }
}

//혹은 리턴타입을 람다식으로 바꾸면 가능
fun createAllDoneFunnabl1e() : () -> Unit {
    return { println("All Done!") }
}

fun createFuntional(num : Int) : IntFunction<Int> {
    return IntFunction { num*num }
}
  • 자바는 생성자 없이 되던데..
public Runnable returnRunnable() {

    return ()->{
        System.out.println("Runnable");
    };
}

public IntFunction<Integer> returnIntFunc() {
    return i->i*i;
}

람다안의 this
람다 안에서 this는 그 람다를 둘러싼 클래스의 인스턴스를 가리킨다.
만약 이벤트 리스너가 이벤트를 처리하다가 자기 자신의 리스너 등록을 해제해야 한다면, (람다 안에서 익명 클래스의 인스턴스 자체를 호출해야 한다면) 람다를 사용 할 수 없다.
이런 경우 람다 대신 익명 객체를 사용해서 리스너를 구현해야 한다. 익명 객체 안에서는 this가 그 익명 객체 인스턴스 자신을 가리킨다.