36
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Kotlinのinline修飾子について

Posted at

こんにちはsekitakaです。
AWSのバージニアリージョンがトラブってて開発しにくいのでQiita記事書いています。

Anko-Layoutsを使ってみようと思ってコードを見てみたところ、inlineという見慣れない修飾子を見つけたので調べてみました。

inline修飾子とは

inline修飾子は主に関数を引数に持つ関数に使われる修飾子です。
この修飾子が付与された関数は、呼び出し元から関数が呼ばれるのでなく、関数の中身がコードとして展開されます。
C言語を知っている方はマクロをイメージすると分かりやすいと思います。

Javaにデコンパイルされたコードを見ると分かりやすいので確認してみましょう。

inline関数

引数で渡されたラムダ式の関数を2回実行するinline修飾子が付与された関数repeatを,mainから呼び出します。

Foo.kt
inline fun repeat(fn: () -> Unit) {
    fn()
    fn()
}

fun main(args: Array<String>) {
    repeat {
        print("foo")
    }
}

これをAndroid StudioのKotlin Bytecodeの機能でデコンパイルすると次のようになります。

Foo.decompiled.java
@Metadata(
   mv = {1, 1, 6},
   bv = {1, 0, 1},
   k = 2,
   d1 = {"\u0000\u001a\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005\u001a\u0017\u0010\u0006\u001a\u00020\u00012\f\u0010\u0007\u001a\b\u0012\u0004\u0012\u00020\u00010\bH\u0086\b¨\u0006\t"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "repeat", "fn", "Lkotlin/Function0;", "production sources for module app"}
)
public final class FooKt {
   public static final void repeat(@NotNull Function0 fn) {
      Intrinsics.checkParameterIsNotNull(fn, "fn");
      fn.invoke();
      fn.invoke();
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      String var1 = "foo"; // このあたり
      System.out.print(var1);
      var1 = "foo";
      System.out.print(var1);
   }
}

mainからrepeatが呼ばれるのでなく、"foo"を表示するコードが2回呼ばれている事がわかると思います。
このように引数のラムダ式を呼び出し元の関数内に展開して使用するのがinline修飾子の役割です。

非inline関数

inlineでないrepeat関数がどのようにデコンパイルされるのかも見てみましょう。
inline修飾子を付けない以下のコードをデコンパイルします。

Foo.kt
fun repeat(fn: () -> Unit) {
    fn()
    fn()
}

fun main(args: Array<String>) {
    repeat {
        print("foo")
    }
}

結果は次の通りです。
ごらんのようにmainからはrepeat関数を呼んでいます。repeatに渡すラムダ式はクラス内に見当たりませんが@Metadataにそれらしき記述があるので、ごにょごにょうまいことやっているのでしょう。

Foo.decompiled.java
@Metadata(
   mv = {1, 1, 6},
   bv = {1, 0, 1},
   k = 2,
   d1 = {"\u0000\u001a\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0003\n\u0002\u0018\u0002\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005\u001a\u0014\u0010\u0006\u001a\u00020\u00012\f\u0010\u0007\u001a\b\u0012\u0004\u0012\u00020\u00010\b¨\u0006\t"},
   d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "repeat", "fn", "Lkotlin/Function0;", "production sources for module app"}
)
public final class FooKt {
   public static final void repeat(@NotNull Function0 fn) {
      Intrinsics.checkParameterIsNotNull(fn, "fn");
      fn.invoke();
      fn.invoke();
   }

   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      repeat((Function0)null.INSTANCE); // ここ
   }
}

どうしてinline修飾子が必要なの?

関数を引数にとったり戻り値として返すことは様々なオーバーヘッドがあるようです。詳しくは公式ドキュメントの初めの方に書いてあります。
inline修飾子を使うことでプログラムのサイズは大きくなるが、パフォーマンスの向上になるんだそうです。
特に大きなループの中で毎回ラムダ式を実行するような場合に効果を発揮しそうですね。

注意点

呼び出し元の関数内に展開されるのはreturnなども同じなので、inline関数に渡すラムダ式やinline関数内で書いたreturnは呼び出し元関数のreturnになってしまいます。

先程のrepeat関数へ渡す引数のラムダ式内でreturnするとfooは一度しか表示されずmain関数が終了してします。

Foo.kt
inline fun repeat(fn: () -> Unit) {
    fn()
    fn()
}

fun main(args: Array<String>) {
    repeat {
        print("foo")
        return // mainのreturn になる
    }
}

ListforEachなんかも実はinline関数なので、次のようなコードを書くとループの1つめの要素でmainが終了してしまいます。

fun main(args: Array<String>) {
    val foo = listOf<String>("foo", "bar")
    foo.forEach { item ->
        print(item)
        return
    }
}

JavaScriptの匿名関数とかになれてるとうっかりハマることもあるかもしれません。

まとめ

いかがでしたでしょうか。inline関数の特性を使いこなしてハイパフォーマンスなアプリ開発をしたいものですね。

36
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?