✅ 코틀린 - 함수형 인터페이스 : 추상 메서드가 단 하나만 존재하는 인터페이스를 의미하며, SAM(Single Abstract Method)라고도 한다. 이렇게 정의된 인터페이스는 람다 표현식이나 메소드 참조를 통해 인스턴스가 가능하므로, 코드를 더욱 간결하게 유지할 수 있다.
Q1. 익명 클래스로 만드는 것이 왜 좋을까? : 클래스의 선언과 객체의 생성을 동시에 할 수 있으며, 메서드 내에서 기존에 존재하는 클래스를 일회용으로 구성하여 필요한 메서드를 재정의하여 사용할 수 있다.
✅ 일회용 : 해당 클래스의 인스턴스가 한 번만 생성되고, 이후에는 재사용되지 않는다. 해당 객체가 더 이상 참조되지 않으면 가비지 컬렉터에 의해 처리된다.
// OnClickListener 인터페이스는 onClick이라는 메서드만 선언된 인터페이스다.
public interface OnClickListener {
void onClick(View v);
}
// Button 클래스는 setOnClickListener 메소드를 사용해 버튼의 리스너를 설정한다.
// 이때 인자의 타입은 OnClickListener다.
public class Button {
public void setOnClickListener(OnClickListener l) { ... }
}
button.setOnClickListener(new OnclickListener() {
@Override
public void onClick(View v) {
...
}
})
button.setOnClickListener { view -> ... }
람다는 보통 무명 클래스로 컴파일되고, 외부 변수를 캡처하지 않는 경우(외부 스코프에 의존하지 않는 경우), 람다 인스턴스는 한 번만 생성되고 재사용할 수 있다. 하지만 무명 객체는 매번 새로운 인스턴스를 생성한다.
하지만, 람다가 외부 변수를 캡처한다면 람다가 생성되는 시점마다 새로운 무명 객체가 생성된다. 이런 경우는 실행 시점에 무명 클래스의 생성에 부가 비용이 들어서 일반 함수를 사용한 구현보다 비효율적이다.
✅ 클로저(Closure) : 람다식으로 표현된 내부 함수에서 외부 범위에 선언된 변수에 접근할 수 있는 구조로, 실행 시점에서 람다식의 모든 참조가 포함된 닫힌(closed) 객체를 람다 코드와 함께 저장한다.
✅ 캡처(Capture) : 클로저가 외부 스코프의 변수를 잡아내는 과정을 의미한다. 캡처된 변수는 람다가 활성화되는 동안 생존하며, 람다는 외부 스코프와 상호작용할 수 있다.
// 함수형 인터페이스(Runnable)를 인자로 원하는 자바 메소드에 코틀린 람다를 전달할 수 있다.
void postponeComputation(int delay, Runnable computation);
postponeComputation(1000) { println(42) }
postponeComputation(1000, object: Runnable {
override fun run() {
println(42)
}
})
val runnable = Runnable { println(42) }
fun handleComputation() {
// 모든 handleComputation 호출에 같은 객체를 사용한다.
postponeComputation(1000, runnable)
}
fun handleComputation(id: String) {
// handleComputation을 호출할 때마다 새로 Runnable 인스턴스를 만든다.
postponeComputation(1000, runnable)
}
호출 지점에 함수의 코드가 삽입되어 실행되는 함수로, 함수 호출의 오버헤드를 제거하고, 별도의 스택 프레임이 생성되지 않아 스택 메모리 할당과 제거의 오버헤드도 감소한다.
inline 키워드 어떤 함수에 붙이면 컴파일러는 그 함수를 호출하는 코드를 함수 본문에 해당하는 바이트코드로 변환해준다.
fun nonInlined(block: () -> Unit) {
block()
}
fun doSomething() {
nonInlined { println("do something") }
}
public void nonInlined(Function0 block) {
block.invoke();
}
public void doSomething() {
noInlined(System.out.println("do something");
}
public static final void doSomething() {
nonInlined(new Function() {
public final void invoke() {
System.out.println("do something");
}
});
}
inline fun inlined(block: () -> Unit) {
block()
}
fun doSomething() {
inlined { println("do something") }
}
public static final void doSomething() {
System.out.println("do something");
}