관리 메뉴

나만을 위한 블로그

[Kotlin] inline, noinline, crossline이란? 본문

개인 공부/Kotlin

[Kotlin] inline, noinline, crossline이란?

참깨빵위에참깨빵 2023. 3. 13. 22:05
728x90
반응형

in / out과 공변성 / 반공변성을 확인했으니 이제 inline, noinline, crossline 키워드를 확인한다. 먼저 inline의 사전적 정의는 아래와 같다.

 

그때마다 즉시 처리하는 / (내연기관이) 직렬의 / (부품 장치가) 일렬로 늘어선

 

그럼 코틀린의 inline은 특정 이벤트 or 처리가 발생하면 작동하는 어떠한 처리를 말하는 건가? 아니면 이런 느낌의 무언가인가? 같은 상상을 하면서 공식문서를 확인해 본다. 공식문서에선 inline 키워드를 inline function, inline class 2가지 페이지로 나눠서 설명하고 있다.

 

https://kotlinlang.org/docs/inline-functions.html#inline-properties

 

Inline functions | Kotlin

 

kotlinlang.org

inline 한정자는 지원 필드(backing fields)가 없는 프로퍼티의 접근자에서 사용할 수 있다. 개별 프로퍼티 접근자에 주석을 달 수 있다
val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ...
    inline set(v) { ... }
접근자를 모두 inline으로 표시하는 전체 프로퍼티에 주석을 달 수도 있다. 호출 사이트(call site)에서 inline 접근자는 일반 inline 함수로 인라인된다.
inline var bar: Bar
    get() = ...
    set(v) { ... }

 

 

https://kotlinlang.org/docs/inline-classes.html

 

Inline classes | Kotlin

 

kotlinlang.org

경우에 따라 비즈니스 로직이 일부 타입 주위에 래퍼를 만드는 게 필요하다. 그러나 추가적인 힙 할당으로 인해 런타임 오버헤드가 발생한다. 또한 래핑된 타입이 기본 타입인 경우, 기본 타입은 일반적으로 런타임에 의해 크게 최적화되는 반면, 해당 래퍼는 특별한 처리를 받지 않기 때문에 끔찍한 성능 저하가 발생한다. 이런 문제를 해결하기 위해 코틀린은 inline class라는 특별한 클래스를 도입했다. inline class는 값 기반 클래스(value-based classes)의 하위 집합이다. 이것들은 정체성(identity)이 없으며 값(value)만 보유할 수 있다. inline class를 선언하려면 클래스명 앞에 value 한정자를 붙인다
value class Password(private val s: String)
JVM 백엔드에 대한 inline class를 선언하려면 클래스 선언 전에 @JvmInline 어노테이션과 같이 value 한정자를 사용하라
< 주의 > inline class의 inline 한정자는 더 이상 사용되지 않는다
// For JVM backends
@JvmInline
value class Password(private val s: String)
이것이 inline이란 이름에 영감을 준 inline class의 주요 기능이다. 클래스의 데이터는 해당 용도에 인라인된다. 인라인 함수의 컨텐츠가 호출 사이트에 인라인되는 방식과 유사하다

 

설명을 봐도 이해가 안 된다. 다른 사람들은 어떻게 설명하는지 확인한다. 먼저 inline function부터 확인하고 그다음에 inline class를 확인한다.

 

https://www.geeksforgeeks.org/kotlin-inline-functions/

 

Kotlin Inline Functions - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

코틀린에서 고차 함수 or 람다식은 모두 객체로 저장되므로 함수 객체와 클래스 모두에 대한 메모리 할당, 가상 호출(virtual call)이 런타임 오버헤드를 유발할 수 있다. 때때로 우리는 람다식을 인라인해서 메모리 오버헤드를 제거할 수 있다. 이러한 고차 함수 or 람다식의 메모리 오버헤드를 줄이기 위해 궁극적으로 컴파일러에 메모리를 할당하지 않고 호출 위치에서 해당 함수의 인라인 코드를 복사하도록 요청하는 inline 키워드를 사용할 수 있다
fun higherfunc( str : String, mycall :(String)-> Unit) {
    // 문자열 str을 전달하여 print()를 호출(invoke)한다
    mycall(str)
}
 
fun main(args: Array<String>) {
    print("GeeksforGeeks: ")
    higherfunc("A Computer Science portal for Geeks",::print)
}
자바처럼 코틀린도 플랫폼 독립적인 언어라서 먼저 바이트코드로 변환된다. Tools -> Kotlin -> Show Kotlin Bytecode로 바이트코드를 가져올 수 있다. 그다음 디컴파일해서 아래 바이트 코드를 얻는다

 

인텔리제이 기준으로 위 순서대로 상단 탭을 클릭하면 이 사이트에서 말하는 메뉴를 발견할 수 있다.

 

저렇게 클릭하면 오른쪽에 창이 하나 나오는데, DECOMPILE 버튼을 누르면 바이트코드로 디컴파일된다. 아래 사진 밑의 코드는 저 버튼을 누르면 새 코드창이 열리면서 나오는 코드를 복붙한 것이다.

 

 

import kotlin.Metadata;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 8, 0},
   k = 2,
   d1 = {"\u0000\u001e\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0011\n\u0002\b\u0002\u001a\"\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u00032\u0012\u0010\u0004\u001a\u000e\u0012\u0004\u0012\u00020\u0003\u0012\u0004\u0012\u00020\u00010\u0005\u001a\u0019\u0010\u0006\u001a\u00020\u00012\f\u0010\u0007\u001a\b\u0012\u0004\u0012\u00020\u00030\b¢\u0006\u0002\u0010\t¨\u0006\n"},
   d2 = {"higherfunc", "", "str", "", "mycall", "Lkotlin/Function1;", "main", "args", "", "([Ljava/lang/String;)V", "KotlinPractice"}
)
public final class TestKt {
   public static final void higherfunc(@NotNull String str, @NotNull Function1 mycall) {
      Intrinsics.checkNotNullParameter(str, "str");
      Intrinsics.checkNotNullParameter(mycall, "mycall");
      mycall.invoke(str);
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkNotNullParameter(args, "args");
      String var1 = "GeeksforGeeks: ";
      System.out.print(var1);
      higherfunc("A Computer Science portal for Geeks", (Function1)null.INSTANCE);
   }
}

 

위 코드에서 집중해서 봐야 하는 부분은 아래 코드다.

mycall.invoke(str);

mycall은 문자열을 매개변수로 전달해서 print()를 호출한다. print()를 호출하는 동안 추가 호출을 생성하고 메모리 오버헤드를 증가시킨다. 이것은 아래처럼 작동한다

 

mycall(new Function() {
    @Override
    public void invoke() {
     //println statement is called here.
    }
});

 

많은 양의 함수를 매개변수로 호출하면 각 함수가 메서드 수에 추가돼서 메모리, 성능에 막대한 영향을 준다. 위 코드에서 어떤 인라인 키워드가 수행되는가?

 

inline fun higherfunc( str : String, mycall :(String)-> Unit) {
    // 문자열 str을 전달하여 print()를 호출(invoke)한다
    mycall(str)
}
// main function
fun main(args: Array<String>) {
    print("GeeksforGeeks: ")
    higherfunc("A Computer Science portal for Geeks",::print)
}

 

위 코드를 아까 방식대로 똑같이 바이트코드로 바꾸면 아래와 같다.

 

import kotlin.Metadata;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.internal.Intrinsics;
import kotlin.jvm.internal.SourceDebugExtension;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 8, 0},
   k = 2,
   d1 = {"\u0000\u001e\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0011\n\u0002\b\u0002\u001a(\u0010\u0000\u001a\u00020\u00012\u0006\u0010\u0002\u001a\u00020\u00032\u0012\u0010\u0004\u001a\u000e\u0012\u0004\u0012\u00020\u0003\u0012\u0004\u0012\u00020\u00010\u0005H\u0086\bø\u0001\u0000\u001a\u0019\u0010\u0006\u001a\u00020\u00012\f\u0010\u0007\u001a\b\u0012\u0004\u0012\u00020\u00030\b¢\u0006\u0002\u0010\t\u0082\u0002\u0007\n\u0005\b\u009920\u0001¨\u0006\n"},
   d2 = {"higherfunc", "", "str", "", "mycall", "Lkotlin/Function1;", "main", "args", "", "([Ljava/lang/String;)V", "KotlinPractice"}
)
@SourceDebugExtension({"SMAP\nTest.kt\nKotlin\n*S Kotlin\n*F\n+ 1 Test.kt\nmain/TestKt\n*L\n1#1,12:1\n5#1,2:13\n*S KotlinDebug\n*F\n+ 1 Test.kt\nmain/TestKt\n*L\n10#1:13,2\n*E\n"})
public final class TestKt {
   public static final void higherfunc(@NotNull String str, @NotNull Function1 mycall) {
      int $i$f$higherfunc = 0;
      Intrinsics.checkNotNullParameter(str, "str");
      Intrinsics.checkNotNullParameter(mycall, "mycall");
      mycall.invoke(str);
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkNotNullParameter(args, "args");
      String str$iv = "GeeksforGeeks: ";
      System.out.print(str$iv);
      str$iv = "A Computer Science portal for Geeks";
      int $i$f$higherfunc = false;
      int var4 = false;
      System.out.print(str$iv);
   }
}

 

inline 키워드의 도움으로 println() 람다식은 System.out.print 형식으로 main 함수에 복사되며 추가 호출이 불필요하다

 

두 바이트코드를 잘 보면 inline 한정자를 붙이지 않은 경우 main() 안에서 print() 이후 higherfunc()가 호출되지만, inline 한정자가 붙은 경우 print()가 2번 호출되고 있다. 즉 print()가 복사되어 총 2번 호출되고, higherfunc()를 또 호출하지 않는 걸 볼 수 있다. inline을 쓸 경우 내부적으로 이런 처리가 일어나기 때문에 메모리와 성능에 영향을 주지 않는 것이라고 이해했다.

 

https://www.tutorialspoint.com/when-to-use-an-inline-function-in-kotlin

 

When to use an inline function in Kotlin

When to use an inline function in Kotlin - Kotlin is a statistically typed language. It has different options to handle higher-order functions. Kotlin came up with a wonderful solution for higher-order functions by introducing inline functions.An Inline fu

www.tutorialspoint.com

코틀린엔 고차 함수를 처리하는 다양한 옵션이 있다. 코틀린은 inline 함수를 도입해서 고차 함수를 위한 솔루션을 제시했다. inline 함수는 함수 선언 직전에 inline 키워드로 선언되는 일종의 함수다. 함수가 inline으로 선언되면, 컴파일러는 이 함수에 대한 메모리를 할당하지 않고 런타임에 호출 위치에서 가상으로 코드 조각을 복사한다. 다음과 같은 경우에 inline 함수를 선택해야 한다

- 고차 함수에 접근해야 하는 경우
- 보다 효율적으로 메모리를 할당해야 하는 경우
- 함수 타입 매개변수를 전달해야 하는 경우
- 앱 성능을 저하시키므로, 거대한 함수를 inline으로 전환해선 안 된다
- inline 함수는 함수가 다른 함수 or 람다를 매개변수로 허용할 때 유용하다
- 객채 생성을 방지하고 제어 흐름을 개선해야 하는 경우 inline 함수를 쓸 수 있다

 

https://www.baeldung.com/kotlin/inline-functions

코틀린에서 함수는 일급 시민이므로 다른 일반 타입과 마찬가지로 함수를 전달하거나 반환할 수 있다. 그러나 런타임에 이런 함수를 표현하면 때때로 몇 가지 제한 사항이나 성능 문제가 발생할 수 있다...(중략)...코틀린에서 일급 시민이 되는 함수의 특혜는 동작의 일부를 다른 함수에 전달할 수 있다는 것이다. 함수를 람다로 전달하면 더 간결하고 우아하게 의도를 표현할 수 있지만 이는 이야기의 일부일 뿐이다. 람다의 어두운 면을 탐구하기 위해 컬렉션을 필터링하는 확장 함수를 선언해서 바퀴를 재발명해 보겠다
fun <T> Collection<T>.filter(predicate: (T) -> Boolean): Collection<T> = // Omitted
이제 위의 함수가 자바로 컴파일되는 방법을 살펴본다. 매개변수로 전달되는 술어 함수(predicate function)에 초점을 맞춘다
public static final <T> Collection<T> filter(Collection<T>, kotlin.jvm.functions.Function1<T, Boolean>);
Function1 인터페이스를 써서 술어가 어떻게 처리되는지 확인하라. 이제 이것을 코틀린에서 호출하면 아래와 같다
sampleCollection.filter { it == 1 }
람다 코드를 래핑하기 위해 아래와 유사한 코드가 생성된다
filter(sampleCollection, new Function1<Integer, Boolean>() {
  @Override
  public Boolean invoke(Integer param) {
    return param == 1;
  }
});
고차 함수를 선언할 때마다 이런 특수 함수 타입의 인스턴스가 하나 이상 만들어진다...(중략)...코틀린 람다로 캡슐화된 작업을 실제로 수행하려면 상위 함수(이 경우는 filter)가 새 인스턴스에서 invoke라는 특수 메서드를 호출해야 한다. 그 결과 추가 호출로 인해 더 많은 오버헤드가 발생한다. 요약하면 함수에 람다를 전달할 때 내부적으로 아래와 같은 일들이 발생한다

1. 특수 타입의 인스턴스가 하나 이상 생성돼 힙에 저장된다
2. 추가 메서드 호출이 항상 발생한다

...(중략)...람다를 사용할 때 추가 메모리 할당 및 추가 가상 메서드 호출로 인해 런타임 오버헤드가 발생한다. 따라서 람다 대신에 같은 코드를 직접 실행한다면 구현이 더 효율적일 것이다. 추상화와 효율성 중 하나를 선택해야 하는가? 알려진 바와 같이 코틀린의 inline 함수를 쓰면 둘 다 가질 수 있다. 우리는 훌륭한 람다를 작성할 수 있으며 컴파일러는 우리를 위해 인라인되고 직접적인 코드를 생성한다. 우리가 할 일은 inline을 추가하는 것이다
inline fun <T> Collection<T>.each(block: (T) -> Unit) {
    for (e in this) block(e)
}
inline 함수를 사용할 때 컴파일러는 함수 본문을 인라인한다. 즉 본문을 함수가 호출되는 위치로 직접 대체한다. 기본적으로 컴파일러는 함수 자체와 함수에 전달된 람다 모두에 대한 코드를 인라인한다. 예를 들어 컴파일러는 아래 코드를 그 밑의 코드로 번역한다
val numbers = listOf(1, 2, 3, 4, 5)
numbers.each { println(it) }
val numbers = listOf(1, 2, 3, 4, 5)
for (number in numbers)
    println(number)
inline 함수를 사용할 때 추가 객체 할당, 추가 가상 메서드 호출이 없다. 그러나 inline으로 인해 만들어진 코드가 상당히 커질 수 있으므로 특히 긴 함수의 경우에는 inline 함수를 과하게 사용해선 안 된다...(중략)

 

이제야 인라인의 의미를 알겠다. 함수 안의 코드들을 그 함수가 호출된 위치에 끼워 넣어서 대체하는 것이다. 물론 이것을 수행하는 주체는 컴파일러다.

 

여기까지 inline에 대해 확인한 내용을 정리하면 아래와 같다.

 

  • 코틀린에서 함수에 람다를 전달하면 특수 타입의 인스턴스가 하나 이상 생성돼서 힙에 저장되고, 추가 메서드 호출이 항상 발생한다. 이로 인해 런타임 시 메모리 오버헤드가 발생하거나 성능에 악영향을 준다
  • 이 문제를 해결하기 위해 만들어진 게 inline 키워드다. inline 키워드가 붙은 함수는 호출될 때, 함수 본문 코드가 해당 함수가 호출된 위치에 대체된다
  • 이로 인해 추가로 객체를 할당(=메모리를 할당)하거나 가상 메서드를 추가로 호출하는 일이 없다
  • 그러나 inline 키워드는 긴 함수에 사용하면 되려 성능을 저하시킬 수 있으니 주의해서 써야 한다

 

이제 inline class를 확인해 본다.

 

https://www.geeksforgeeks.org/kotlin-inline-classes/

 

Kotlin Inline classes - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

inline class는 일부 타입에 대한 기존 래퍼의 단점을 극복하기 위해 코틀린 1.3부터 도입됐다. inline class는 기본 자료형의 값 범위와 함께 typealias의 장점을 추가한다. 일부 아이템을 판매하고 비용이 float로 정의된 data class가 있다고 가정한다
data class Items(val itemno: Int, val cost: float, val qty: Int)
달러, 루피 2가지 통화를 지원하는 경우 다른 클래스에서 cose를 리팩토링해야 한다
data class Items(val itemno: Int, val cost: Cost, val qty: Int)

data class Cost(val value: Float, val currency: Currency)

enum class Currency {
    RUPEE,
    DOLLAR
}
위 방법은 2가지 문제가 있다

1. 메모리 오버헤드
2. 복잡성

이 2가지 문제는 inline class로 해결된다
data class Item(val id: Int, val price: RupeePrice, val qty: Int)

inline class RupeePrice(val price: Float) {
    inline fun toDollars(): Float = price * 71.62f
}
inline class에는 기본 생성자에서 초기화된 단일 속성이 있어야 한다. 런타임에 inline class의 인스턴스는 이 단일 속성을 써서 표시된다. 클래스의 데이터는 해당 용도에 인라인된다

< 멤버 >

inline class는 프로퍼티, 함수를 선언할 수 있다는 점에서 일반 클래스와 비슷하다. 그러나 특정 제한 사항도 있다. inline class는 init 블록을 가질 수 없고 lateinit, delegated 속성 같은 복잡하고 계산 가능한 속성을 가질 수 없다
inline class Name(val s: String) {
    val length: Int
        get() = s.length
    fun greet() {
        println("Hello, $s")
    }
}   
fun main() {
    val name = Name("Kotlin")
    name.greet() // 정적 메서드로 호출된다
    println(name.length) // 정적 메서드로 호출된다
}
< 상속 >

이런 클래스는 인터페이스에서 상속할 수 있지만 다른 클래스를 상속할 수 없으며 final이어야 한다
interface Printable {
    fun prettyPrint(): String
}

inline class Name(val s: String) : Printable {
    override fun prettyPrint(): String = "Let's $s!"
}

fun main() {
    val name = Name("Kotlin")
    println(name.prettyPrint()) // 여전히 정적 메서드로 호출된다
}

 

inline class의 경우 공식문서에서 말한 대로 inline class가 아닌 value class 형태로 사용하는 것에 주의하자. 이걸 무시하고 inline class라고 쓸 경우 아래와 같이 표시된다.

 

 

inline 한정자는 더 이상 사용되지 않으니 value로 바꿔 쓰라고 IDE가 권장하는 것이다. IDE의 말대로 고치면 아래 형태로 자동 변경된다.

 

@JvmInline
value class Test(val a: Int)

 

아무튼 inline class도 inline function과 비슷하게 컴파일러가 객체를 만들지 않고 값을 매핑할 수 있게 도와주는 역할을 하는 클래스다. 난 아직 래퍼라는 걸 써 본 적이 없어서 inline class를 쓰면 뭐가 좋은지는 아직 그렇게 와닿지 않는다.

 


 

이제 inline과 비슷한 noinline과 crossline을 확인해 본다.

 

https://www.baeldung.com/kotlin/crossinline-vs-noinline

inline은 많은 컴파일러가 코드 성능 최적화를 위해 사용하는 오래된 트릭 중 하나다. 따라서 코틀린은 당연히 더 나은 성능, 더 작은 공간을 차지하기 위해 이 트릭을 활용한다
기본적으로 inline 키워드는 호출 사이트에서 메서드 호출 및 전달된 모든 람다 함수를 인라인하도록 컴파일러에 지시한다. 때때로 어떤 이유로든 인라인에서 전달된 람다 함수 중 일부를 제외하고 싶을 수 있다. 이 경우 noinline 한정자를 써서 표시된 람다 함수를 인라인에서 제외할 수 있다
inline fun executeAll(action1: () -> Unit, noinline action2: () -> Unit) {
    action1()
    action2()
}
위 예시에서 코틀린은 여전히 executeAll() 호출과 action1 람다를 인라인한다. 그러나 noinline 한정자로 인해 action2 람다 함수에 대해 동일한 작업을 수행하지 않는다. 기본적으로 코틀린이 아래 코드를 그다음 코드로 컴파일할 것으로 예상할 수 있다
fun main() {
    executeAll({ print("Hello") }, { print(" World") })
}
fun main() {
    print("Hello")
    val action2 = { print(" World") }
    action2()
}
executeAll() 호출의 흔적이 없다. 첫 번째 람다 함수는 분명 인라인되어 있다. 그러나 두 번째 람다 함수는 인라인되지 않고 있는 그대로 존재한다

코틀린에선 명명된 함수, 익명 함수 또는 인라인 함수를 종료하기 위해 정규화되지 않은 일반 반환만 사용할 수 있다. 람다를 종료하려면 레이블(return@label 같이)을 사용해야 한다. 둘러싸는 함수에서 종료되기 때문에 람다에서 정상적인 반환을 사용할 수 없다
fun foo() {
    val f = {
        println("Hello")
        return // won't compile
    }
}
코틀린 컴파일러는 람다 내부의 반환을 사용해서 둘러싸는 함수를 종료하는 걸 허용하지 않는다. 이런 반환을 비지역 반환이라고 한다. 람다가 호출 사이트에서 인라인되기 때문에, 인라인 함수에서 로컬이 아닌 제어 흐름을 사용할 수 있다
inline fun foo(f: () -> Unit) {
    f()
}

fun main() {
    foo { 
        println("Hello World")
        return
    }
}
람다에서 나가더라도 람다 자체는 기본 함수에 인라인된다. 따라서 이 반환문은 람다가 아닌 기본 함수에서 직접 발생한다. 인라인 함수 안에서 정상적인 반환을 사용할 수 있는 이유다. 그럼 인라인 함수에서 인라인이 아닌 함수로 람다 함수를 전달하면 어떤 일이 생기는가?
inline fun foo(f: () -> Unit) {
    bar { f() }
}

fun bar(f: () -> Unit) {
    f()
}
inline function에서 인라인이 아닌 함수로 람다 f를 전달하고 있다. 이렇게 인라인 함수의 람다 매개변수가 인라인이 아닌 다른 함수 컨텍스트로 전달되면 비지역 반환을 사용할 수 없다. 따라서 위 코드는 코틀린에서 컴파일조차 되지 않는다...(중략)

 

진짜 안 되나 궁금해서 한번 해 봤다. 진짜로 안 된다.

 

 

대신 f 왼쪽에 crossinline 한정자를 붙이면 컴파일 에러가 더 이상 발생하지 않는다.

 

 

crossinline을 더 잘 이해하려면 비지역 반환이 무엇인지 알아야 할 것 같다. 비지역 반환이 무엇인지 확인해 본다.

 

https://kotlinlang.org/docs/inline-functions.html#non-local-returns

 

Inline functions | Kotlin

 

kotlinlang.org

위 공식문서 링크는 crossinline을 확인하면서 봤던 링크다. 중간에 비지역 반환(Non-local returns)에 대한 내용이 같이 있었으니 한번 더 읽어보자.

 

https://blog.naver.com/PostView.nhn?blogId=yuyyulee&logNo=221390355858 

 

[Kotlin 강좌] 23. 람다에서의 리턴(Return)

람다에서 return을 사용하면 어떻게 될까? 람다는 하나의 함수이므로, 람다 식을 종료할까? 람다가 실행되...

blog.naver.com

인라인 함수에 사용된 람다식의 return은 람다를 인자로 받는 인라인 함수도 함께 종료시킨다. 이것을 비지역 반환이라고 한다

 

https://stackoverflow.com/questions/40160489/kotlin-whats-does-return-mean

 

Kotlin: Whats does "return@" mean?

I'm using RxJava in one of my projects, I converted one of my classes to Kotlin using the Android Studio plugin and in one of map flatMap lambda (Func1 in java), intermediates returns looks like the

stackoverflow.com

코틀린에서 return@label 구문은 이 명령문이 반환하는 여러 중첩 함수 중 어떤 함수를 지정하는 데 사용된다. 함수 리터럴(람다) 및 로컬 함수와 함께 작동한다. 레이블이 지정되지 않은 반환문은 가장 가까운(=가장 안쪽) 둘러싸는 fun(람다 무시)에서 반환된다. 아래 함수를 보자
fun foo(ints: List<Int>) {
    ints.forEach {
        if (it == 0) return
        print(it)
    }
}
여기서 return은 람다뿐 아니라 foo()의 실행을 끝낸다. 그러나 다른 함수(람다 or 외부 함수)에서 반환하려면 return문에서 레이블로 지정해야 한다
fun foo(ints: List<Int>) {
    ints.forEach {
        if (it == 0) return@forEach // forEach에 전달된 람다의 암시적 label
        print(it)
    }
}
fun foo(ints: List<Int>): List<String> {
    val result = ints.map f@{
        if (it == 0) return@f "zero" // return at named label
        if (it == -1) return emptyList() // return at foo
        "number $it" // expression returned from lambda
    }
    return result
}

foo(listOf(1, -1, 1)) // []
foo(listOf(1, 0, 1)) // ["number 1", "zero", "number 1"]
람다에서 비지역 반환(즉 외부 함수에서 반환)은 로컬, 인라인 함수에 대해서만 지원된다. 둘러싸는 함수(변수에 저장하고 나중에 호출할 수 있는 등)와 비지역 반환은 이 경우 무의미하다
반응형
Comments