이 포스팅은 Kotlin in Action, 드미트리 제메로프 & 스베트라나 이사코바, 에이콘출판사(2017)을 읽고 개인 학습용으로 정리한 글입니다.
함수 타입 정의
(파라미터 타입 목록) -> 반환 타입
그냥 함수를 정의하는 경우 Unit 반환 타입 지정 생략 가능
함수 타입을 정의하는 경우 반환 타입 반드시 명시해야
널이 될 수 있는 함수 타입 가능
var canReturnNull : (Int, Int) -> Int? = {x,y -> null}
var funOrNull: ((Int, Int) -> Int))? = null
⭐컴파일된 코드안에서 함수 타입은 일반 인터페이스로 바뀐다
코틀린 표준 라이브러리는 함수 인자의 개수에 따라 인터페이스 제공
-> ex. 인자가 없는 함수 Function0R, 인자가 하나인 함수 Function1R
-> 각 인터페이스에는 invoke 메서드 정의 -> invoke를 호출하면 함수 실행
함수 타입의 변수 = FunctionN 인터페이스를 구현하는 객체 저장
-> 그 클래스의 invoke 메서드 본문 = 람다의 본문
//코틀린 함수 선언
fun processTheAnswer(f: (Int) -> Int){
println(f(42))
}
//자바 8
processTheAnswer({number -> number + 1});
//자바 8 이전
processTheAnswer(
new Function1<Integer, Integer>(){
@Override
public Integer invoke(Integer number){
return number+1;
}
}
);
CollectionsKt.forEach(strings, s->{ //strings는 확장 함수의 수신 객체
System.out.println(s);
return Unit.INSTANCE; //Unit 타입의 값 명시적으로 반환해야
});
널이 될 수 있는 함수 타입으로 함수를 받으면
-> 그 함수 직접 호출할 수 X (NPE 발생 가능성이 있어 컴파일되지 X)
-> null 여부 명시적으로 검사해야
함수 타입이 invoke 메서드를 구현한 인터페이스라는 사실 이용하여 안전 호출
-> 널이 될 수 있는 함수?.invoke()
inline fun <T> synchronized(lock: Lock, action: () -> T) : T{
lock.lock()
try{
return action()
}
finally{
lock.unlock()
}
}
fun foo(l: Lock){
println("Before sync")
synchronized(l){
println("Action")
}
println("After sync")
}
fun main(){
val l = Lock()
foo(l)
}
fun foo(l: Lock){
println("Before sync")
l.lock()
try{
return println("Action")
}
finally{
l.unlock()
}
println("After sync")
}
synchronized의 본문 뿐만 아니라 synchronized에 전달된 람다의 본문도 함께 인라인
-> 코틀린 컴파일러는 그 람다를 무명 객체로 감싸지 X
인라인 함수를 호출하면서 람다를 넘기는 대신 함수 타입의 변수를 넘기는 경우
-> 람다 본문 인라이닝 되지 않고 synchronized 함수의 본문만 인라이닝
⭐파라미터로 받은 람다를 다른 변수에 저장하고 나중에 그 변수를 사용하는 경우
-> 람다를 표현하는 객체 존재해야
-> 람다 인라이닝할 수 X
둘 이상의 람다를 받는 인라인 함수에서 일부 람다만 인라이닝 하고 싶은 경우
-> noinline 변경자 파라미터 이름 앞에 붙이기
inline fun foo(inlined: ()->Unit, noinline notInlined: () -> Unit){ ... }
filter와 map은 인라인 함수
-> 두 함수의 본문은 인라이닝 됨 -> 추가 객체/클래스 생성 X
-> 하지만 결과를 저장하는 중간 리스트 생성
asSequence를 통해 리스트 대신 시퀸스 사용
-> 중간 시퀸스는 람다를 필드에 저장하는 객체로 표현됨
-> 최종 연산은 중간 시퀸스에 있는 여러 람다를 연쇄 호출
-> 시퀸스는 람다를 저장해야하므로 람다 인라인 X
시퀸스 연산에서는 람다가 인라이닝되지 않기 때문에
⭐크기가 작은 컬렉션은 오히려 일반 컬렉션 연산이 성능이 좋을 수도
일반 함수의 경우 JVM은 코드 실행을 분석해 가장 이익이 되는 방향으로 호출 인라이닝
-> 이 과정은 바이트코드를 기계어 코드로 번역하는 과정(JIT)에서 일어남
코틀린 인라인 함수는 바이트 코드에서 각 함수 호출 지점을 함수 본문으로 대치
-> 코드 중복 발생
-> 함수를 직접 호출하면 스택 트레이스 더 깔끔함
함수 호출 비용 줄일 수 O, 람다를 표현하는 클래스와 람다 인스턴스에 해당하는 객체 만들 필요 X
인라이닝을 사용하면 일반 람다에서는 사용할 수 없는 몇 가지 기능 사용 가능
-> ex. 넌로컬(nonlocal) 반환
자원(resource): 파일, Lock, 데이터베이스 트랜잭션 등
자원 관리 패턴
-> 보통 사용하는 방법: try 블록 시작 직전에 자원 획득, finally 블록에서 자원 해제
코틀린 withLock 함수: Lock 인터페이스의 확장 함수
fun<T> Lock.withLock(action: () -> T):T{
lock()
try{
return action()
}finally{
unlock()
}
}
람다 안에서 return을 사용하면 람다로부터만 반환되는 것 X, 그 람다를 호출하는 함수가 실행 종료되고 반환
자신을 둘러싸고 있는 블록보다 더 바깥에 있는 다른 블록을 반환하게 만드는 return
-> 넌로컬 return
⭐인라이닝되지 않는 함수에 전달되는 람다 안에서 return 사용 X
람다 식에서 로컬 리턴 사용 가능
-> for루프의 break와 비슷한 역할
로컬 return과 넌로컬 return 구분을 위해 레이블 사용
this의 레이블에도 같은 규칙 적용됨
무명 함수
-> 함수 이름이나 파라미터 타입 생략 가능
-> 반환 타입 지정 규칙 일반 함수와 동일
(블록이 본문인 경우 반환 타입 명시, 식이 본문인 경우 생략 가능)
무명 함수 안에서 레이블이 붙지 않은 return 식: 무명 함수 자체 리턴
⭐return에 적용되는 규칙: fun 키워드를 사용해 정의된 가장 안쪽 함수 반환
함수의 인터페이스 구현으로 객체를 넘겨주는 경우
-> 메서드를 호출할 때마다 새로운 객체 생성됨
함수의 인터페이스 구현으로 람다를 넘겨주는 경우
-> 람다에 대응하는 무명 객체 메서드를 호출할 때마다 반복 사용
-> but 람다가 주변 영역의 변수를 포획하는 경우 반복 사용 X, 새로운 인스턴스 생성됨