Kotlinの公式チュートリアルの日本語解説がパッと探した感じでなかったので、
自身で勉強しながら解説を付けてまとめてみました。
そもそもチュートリアルというよりも機能の紹介といったほうが近いかもしれないです。
また今回のシリーズは自分のようなJava→Kotlinを導入する人に向けての解説となります。
Javaの知識で網羅できるような範囲には触れませんのでご了承ください。
さて、今回はFunctionsにあたる部分の解説を行います。
また、今回の解説付きのコードはGitHubにリポを切ってプッシュしています。
リポ内では公式ページの入力ボックスごとにソースを切っており、
それぞれ上から順にlesson1-4で番号を振っています。
Kotlinでの関数宣言と呼び出しの方法
(1)シンプルな関数使用サンプル
```kotlin:lesson1.kt // 1) fun printMessage(messeage: String): Unit{ println(messeage) } ``` Kotlinでは、関数は"fun"を用いて宣言します。 上記の 1) は標準的な関数の宣言と呼び出しのサンプルです。 ここで出てくる":"の後ろについているものは戻り値の型で、"Unit"は戻り値なしとなります。 (Javaにおけるvoidと同義)(2)引数の初期値の指定
```kotlin:lesson1.kt // 2) fun printMessageWithPrefix(messeage: String, prefix: String = "Info"){ println("[$prefix] $messeage") } ```Kotlinでは、関数の引数を定義する際にデフォルトの初期値を指定しておくことができます。
初期値を指定しておくと、該当のパラメータに値が指定されなかった場合に初期値の値をパラメータとして実行します。
またここでは 1) の時のような戻り値の型が指定されていませんが、
指定を省略した場合は暗黙的にKotlinが型の判定と変換を行ってくれます。
(3)戻り値を返す
```kotlin:lesson1.kt // 3) fun sum(x: Int, y: Int): Int{ return x + y } ``` Kotlinでは、戻り値を返す時に"return"を使用します。 このあたりはさすがJavaとの互換性ともあって、Javaを扱っていれば自然と実装できる内容です。(4)処理内容を直接戻り値にする
```kotlin:lesson1.kt // 4) fun multiply(x: Int, y: Int) = x * y ``` Kotlinでは、単純な処理内容であればわざわざブロック内に処理を描く必要はありません。 戻り値の型も自動で判定してくれるので、とてもシンプルな記述で関数を実装することができます。(1)~(4)の処理内容を呼び出す
では、ここまで宣言してきた関数をそれぞれ呼び出してみましょう。 今までのはあくまでも呼び出す関数を宣言してきただけなので、 Javaと同様に呼び出し元のmain関数を用意するのをお忘れなく。fun main(){
// 5) call 1)
printMessage("Hello")
// Hello
// 6) call 2) with 2 parameters
printMessageWithPrefix("Hello", "Log")
// [Log]Hello
// 7) call 2) with 1 parameter
printMessageWithPrefix("Hello")
// [Info]Hello
// 8) call 2)
printMessageWithPrefix(prefix = "Log", messeage = "Hello")
// [Log]Hello
// 9) call 3)
println(sum(1, 2))
// 3
}
ソースコメントにも記載していますが、1つずつ解説していきます。
-
では、単純にパラメータを渡して 1) の関数を実行しています。
-
では、2個全てのパラメータを渡して 2) の関数を実行しています。
初期値が指定されている引数にもパラメータが渡されているため、
渡されたパラメータを使用して処理を実行します。 -
では、初期値が指定されている引数にパラメータを渡さずに 2) の関数を実行しています。
初期値が指定されている引数にパラメータが渡されていないため、
宣言時に指定された初期値をパラメータとして処理を実行します。 -
では、値を渡す引数名を指定してパラメータを渡しています。
これにより、関数宣言時と引数の指定順番が変わっていてもパラメータを正しく渡すことができます。 -
では、printlnの引数として 3)の戻り値を渡しています。(関数のネスト)
infixを用いた関数の宣言方法
infix関数とは
infixは直訳すると"中置"で、infixで定義された関数は 呼び出し時に関数を挟むオブジェクトや値を引数にします。 これにより、自然な英語に近い表現で関数を呼び出すことができるようになります。(1)infix関数を宣言する
```kotlin:lesson2.kt // 1) infix fun Int.times(str: String) = str.repeat(this) // 1') infix fun String.repeats(n: Int) = this.repeat(n) ``` 上記はinfixの宣言方法を紹介するサンプルです。 infix関数は次のフォーマットを用いて宣言します。 ` infix fun [クラス].[関数名]([引数]):[戻り値の型] ` infix関数では関数自体を引数として暗黙的に定義するため、 クラスメンバとして定義しない場合は引数のクラスを指定する必要があります。1') については、公式のサンプルにはないですが解説と整理用に追記しました。
- で定義した関数では呼び出し方が倒置になっているため、
より自然に呼び出せる形式に変換したのが 1') です。
(2)infix関数を呼び出す
```kotlin:lesson2.kt // These print "ByeBye" // 2) println(2 times "Bye") // 2') println("Bye" repeats 2) // 2'') println(2.times("Bye")) ``` 先ほど書いている通り、infixで定義した関数は 引数となる値やオブジェクトで関数を挟む形で呼び出しを行います。上記はそれぞれ 1) と 1') で定義した関数を呼び出す際の処理です。
- は 1) の関数を呼び出すため処理です。
"2"が"Int.times"に該当するため、repeatの引数である"this"に渡されて処理を行います。
2') は、1') を呼び出していますが、 これは 1) の引数を逆にしただけです。
こちらでは呼び出す時の書き順を『文字列を』『繰り返す』『回数』とすることで
英語に近い表現で呼び出していることがわかると思います。
(ただし、この例は既存のrepeat関数と全く同じ処理になります)
2'') は、2) と同じく 1)の関数を呼び出す処理ですが、
こうしてみるとなぜ定義時に"Int."を付けているのかがよくわかると思います。
(3)(4)to関数
```kotlin:lesson2.kt // 3) var pair = "Ferrari" to "Katarina" println(pair) // (Ferrari, Katarina)// 4)
infix fun String.onto(other: String) = Pair(this, other)
val myPair = "McLaren" onto "Lucas"
println(myPair)
// (McLaren, Lucas)
Kotlinでは標準ライブラリとして"to"関数が用意されています。
to関数はPairクラスのオブジェクトを返却するinfixの関数です。
Pairクラスでは名前の通りキーと値のペアが定義されており、このサンプルではその中身を出力しています。
また、4) ではto関数の処理を独自関数を定義して再現しています。
<h4>(5)(6)独自クラスでinfix関数を使用する</h4>
```kotlin:lesson2.kt
fun main(){
// 5)
val sophia = Person("Sophia")
val claudia = Person("Claudia")
sophia likes claudia
}
// 6)
class Person(val name: String){
val likedPeople = mutableListOf<Person>()
infix fun likes(other: Person) {likedPeople.add(other)}
}
ここまでの例ではIntやStringといった標準クラスのオブジェクトを1つ目の引数として使用してきましたが、
もちろん自分で用意したクラスで定義することもできます。
ここでは 6) で定義したクラスにinfix関数を定義し、それをmainの 5) で呼び出しています。
この時、infix関数likesはPersonクラスのメンバであるため、定義時にクラスの指定は不要です。
(関数自体がPerson.likesとなります。)
- で呼び出した関数の処理としては、
Person型のオブジェクトsophiaのメンバ"likedPeople"に
これまたPerson型オブジェクトのclaudiaが追加されるという処理になります。
(単純に出力できないので説明が難しい)
operator関数と演算子のオーバーロード
operator関数とは
operator関数は名前の通り演算子の処理を使う関数で、演算子記号を用いて関数を呼び出すことができます。 なぜそんなことができるのかというと、Kotlinでは演算子記号を関数として実装しており、 そのメソッドをオーバーロードすることで処理の中身を上書いています。では実際にサンプルを見てみましょう。
(1)operator関数を定義する
// 1)
operator fun Int.times(str: String) = str.repeat(this)
operator関数を定義するには、関数に"operator"修飾子を付けます。簡単ですね。
ただし、operator関数では演算子記号に対する関数をオーバーロードしているため
operatorをつければなんでもかんでも使えるというわけではなく、
それぞれの演算子に紐付く関数名を使用する必要があります。
ここでは、"*"演算子に対応するメソッドである"times"をオーバーライドしています。
その他の対応する演算子と対応するメソッドの名前は、Kotlinの公式ドキュメントに書いてあります。
https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
(2)operator関数を呼び出す
// 2)
println(2 * "Bye ")
// Bye Bye
operator関数は演算子に対するメソッドをオーバーロードしているだけなので、
呼び出す時は演算をするように書いてあげるだけです。
operator関数はオーバーロード
ここまで何回か書いてきているように、operatorで定義する関数は 紐付く演算子の処理をオーバーロードしているだけです。例えば、先程の 2) では{[Int] * [String]}に対してオーバーロードを行っていますが、
パラメータを変えて{[Int] * [Int]}で呼び出すと、本来のメソッドの処理(乗算)を呼び出します。
// 2')
println(2 * 3)
// 6
また、引数のクラスを間違えると当然のように処理結果も変わります。
// 2'')
println(2 * "3")
// 33
パラメータミスによるバグを起こさないためにも、
明示的にキャストを挟んでからパラメータを渡してあげるといいかもしれません。
// 2''')
println(2 * "3".toInt())
// 6
これはオーバーロード全般にも言えることではありますが、オペレータ関数は既存の汎用メソッドである都合上オーバーライドで定義されているものがたくさんあります。
処理の過程でどんなものが渡されるかわからないぐらいであれば、最初から意図した動作になるようキャストするのが無難でしょう。
さて、このソースの中には 3) と 4) もありますが、これもオーバーロードのサンプルです。
// 3)
operator fun String.get(range: IntRange) = substring(range)
// 4)
val str = "Always forgive your enemies; nothing annoys them so much."
println(str[0..14])
インデックスにアクセスする"String.get"メソッドに対してIntRangeクラスの値を渡すことで、
対象文字列の部分文字列を取得する処理を実装しています。
可変長の引数を扱う
(1)可変長で引数を定義する
Kotlinで可変長の変数を引数に使う場合、編集の修飾子を"vararg"にして定義します。 // 1)
fun printAll(vararg messages: String){
// void printAll(String... messages){
for (m in messages) println(m)
}
- はvarargを用いて関数を定義した場合のサンプルです。
補足コメントにも書いていますが、これはJavaで"String..."を用いた時と同じになります。
(2)可変長の引数にパラメータを渡す
(1)の関数を呼び出してみます。 // 2)
printAll("Hello", "こんにちは", "Salut", "Hola", "你好")
// Hello こんにちは Salut Hola 你好 が1行ずつで表示
可変長なので、パラメータはいくつでも構いません。
カンマ区切りで文字列を渡せば、渡した数だけ出力します。
また、処理の中身はprintlnなので、各実行ごとに改行が入ります。
(3)(4)可変長の引数と同時に別のパラメータを渡す
可変長の引数を持つ場合、呼び出し側のパラメータはカンマで区切って複数渡します。 では他に引数がある場合はどうなるでしょうか。通常、引数が複数ある場合は呼び出しのパラメータとして(第1引数,第2引数,...)という形で渡しますが
引数が可変長の場合はやはりカンマ区切りで渡すため、方法が重複します。
そこで、このような場合にはパラメータを渡す引数名を呼び出し時に指定する必要があります。
この記事の最初の方に出てきた呼び出し方ですね。
// 3)
fun printAllWithPrefix(vararg messages: String, prefix: String){
for (m in messages) println(prefix + m)
}
// 4)
printAllWithPrefix(
"Hello", "こんにちは", "Salut", "Hola", "你好",
prefix = "Greeting: "
)
// Greeting: Hello ...順番に出力
可変長ではない引数のprefixにパラメータを渡す場合は、それを明示してあげれば値を渡すことができます。
(5)可変長の配列を渡す
Kotlinでは、可変長の配列をそのまま渡すことはできません。 ただし、スプレッド演算子と呼ばれる"*"を配列の前につけてあげると渡すことができます。 // 5)
fun log(vararg entries: String){
printAll(*entries)
}
log("Hello", "こんにちは", "Salut", "Hola", "你好")
書いてみて思ったこと
今回は自分で調べながらになりますが、自分がわからなかったことを上手く補完できるように書きました。 もし自分と同じようにこのチュートリアルを読んでよくわからなかったという人がこれを見て分かったら嬉しいです。また、そもそもこれ違うとか意見がありましたら教えてください。
自分も間違って覚えていることになるので指摘はとても助かります。
今回はQiita自体も初投稿なので、書き方の改善などご意見がありましたらそちらもアドバイスをお待ちしております。
そもそもやっぱりこれチュートリアルではないような...。
(自分がチュートリアルと勘違いしてるだけかもですが)