[毎日Kotlin] Day10. Extension functions(拡張関数)

2018.01.25

はじめに

毎日Kotlinシリーズです。

このシリーズを初めての方はこちらです。「毎日Kotlin」はじめました | Developers.IO

問題

Extension functions | Try Kotlin

拡張関数を書いてみよう。

Read about extension functions. Then implement extension functions Int.r() and Pair.r() and make them convert Int and Pair to RationalNumber.

fun Int.r(): RationalNumber = TODO()
fun Pair<Int, Int>.r(): RationalNumber = TODO()

data class RationalNumber(val numerator: Int, val denominator: Int)

テストコード| Try Kotlin

class TestExtensionFunctions() {
    @Test fun testIntExtension() {
        Assert.assertEquals("Rational number creation error: ", RationalNumber(4, 1), 4.r())
    }

    @Test fun testPairExtension() {
        Assert.assertEquals("Rational number creation error: ", RationalNumber(2, 3), Pair(2, 3).r())
    }
}

狙い

ここで考えて欲しい問題の意図はなんだろうか?

Kotlinで使用頻度が高い機能だと思います。便利です。慣れている、理解している人には読みやすく、初見の人には意味不明です。Kotlinのソースを読むキモの部分なので覚えよう。

この問題の他に、以下のことも考えてみよう。

  1. Javaの等価コードを作ってみよう。
  2. レシーバーが付いているっと表現されるがどういうことだろう。
  3. thisはいったいなんだろう。
  4. 拡張関数と通常のメソッド名とかぶったらどうなるだろう。

解答例

fun Int.r(): RationalNumber = RationalNumber(this, 1)
fun Pair<Int, Int>.r(): RationalNumber = RationalNumber(first, second)

Javaの等価コードを作ってみよう。

public class JavaCode {

    public static RationalNumber r(int numerator) {
        return new RationalNumber(numerator, 1);
    }

    public static RationalNumber r(Pair<Integer, Integer> pair) {
        return new RationalNumber(pair.first, pair.second);
    }

    public void main() {
        JavaCode.r(4, 1);
        JavaCode.r(new Pair<Integer, Integer>(2, 3));
    }
   
}

class RationalNumber {
    public int numerator = 0;
    public int denominator = 0;

    public RationalNumber(int numerator, int denominator) {
        this.numerator = numerator;
        this.denominator = denominator;
    }
}

え?なんとも残念なコードですか?ウルトラCのコードを期待していましたか?Kotlinさんは僕達が作るのも面倒なことをよしなにやってくれている縁の下の力持ちなのです。

「レシーバーが付いている」っと表現されるがどういうことだろう

Javaの等価コードの第一引数の名前を指しています。KotlinのコードをJavaに変換した時のように書きます。

JavaCode.ktからJavaに変換したときのイメージです。

public class JavaCodeKt {

    public static RationalNumber r(int receiver) {
        return new RationalNumber(receiver, 1);
    }

    public static RationalNumber r(Pair<Integer, Integer> receiver) {
        return new RationalNumber(receiver.first, receiver.second);
    }

    public void main() {
        JavaCode.r(4, 1);
        JavaCode.r(new Pair<Integer, Integer>(2, 3));
    }
   
}

numeratorと書いていた部分がreceiverに変わっただけですね。拡張関数の第一引数の変数名がreceiverという名前が自動的につくられています。型はなにかというとInt.r()なのでInt型です。

まとめるとreceiver: Intが第一引数になる。レシーバー付 = receiverの型が指定されているということです。

レシーバーが付いていないとどうなるだろうか?考えてみよう。

「this」とはなんだろう。

thisは第一引数のことを指します。

fun Pair<Int, Int>.r(): RationalNumber = RationalNumber(first, second)

Javaの等価コードは以下の通りです。

public static RationalNumber r(Pair<Integer, Integer> receiver) {
        return new RationalNumber(receiver.first, receiver.second);
    }

receiver: Pair<Int, Int>が第一引数になっています。つまり、この場合のthisreceiverのことです。なので、RationalNumber(first, second)RationalNumber(this.first, this.second)と同じで、イメージはRationalNumber(receiver.first, receiver.second)と同じです。

拡張関数と通常のメソッド名とかぶったらどうなるだろう

拡張関数は動的に解決されず、コンパイル時に静的に解決されます。そして、拡張関数とかぶった場合は通常のメソッドが優先されます。このへんの詳しい説明はこちらがとても参考になります。

あとがき

Day11.でまたお会いしましょう。