Kotlin関数(Functions)
概要
まず、簡単な関数を定義する例を見てみよう。
fun hello(name: String) {
println("Hello, ${name}!")
}
上の例ではhello関数を定義している。Kotlinで関数を定義するには、funというキーワードを使用する。「funキーワード、関数名、引数リスト」の順に記述する。引数には型を明示する必要がある。
関数は通常クラスに属するが、必ずしもクラスに属している必要はなく、トップレベル(Top-level)にも定義できる。
値を返す関数
引数を使って計算を行い、結果を返す関数を定義してみよう。次の例では、簡単な加算を行うsum関数を定義している。
fun sum(a: Int, b: Int): Int {
return a + b
}
先ほどのhello関数と異なる点は、戻り値の型を指定し、returnキーワードで値を返している点である。戻り値の型は、関数の引数リストの直後にコロン(:)を挟んで記述する。sum関数の戻り値は2つのInt型の値を足したものなので、戻り値の型もIntになる。returnキーワードは関数の値を返すためのキーワードである。
戻り値がある関数は、末尾に型を明示し、returnキーワードを使って返す必要がある。
Single expression functions(一行で関数を定義する方法)
また、次のように一行で書ける関数は、=を使って戻り値として直接定義することもできる。
fun sum(a: Int, b: Int) = a + b
デフォルト引数
関数の引数にはデフォルト値を設定できる。
fun hello(name: String, exclamation: Boolean = false) {
val suffix = if (exclamation) "!" else ""
println("Hello, ${name}${suffix}")
}
hello関数はBoolean型のexclamationという引数を持ち、デフォルト値が設定されている。デフォルト値がある引数(デフォルト引数)は、呼び出し時に値の指定を省略できる。省略するとデフォルト値が使われる。
例: デフォルト引数を持つ関数の使用例
// 2番目の引数を省略
hello("Kotlin") // Hello, Kotlin
// 2番目の引数を指定
hello("Kotlin", true) // Hello, Kotlin!
また、関数呼び出し時に引数へ渡す値を名前付きで指定することもできる。
例: 名前付き引数
hello(name = "Foo")
// 引数リストの順序に従う必要はない。
hello(exclamation = false, name = "Baz")
デフォルト引数は、呼び出し時に引数名を記述することで、指定したい引数だけに値を渡せる。
fun drawCircle(x: Int, y: Int, r: Int,
lineColor: String = "#000",
fillColor: String = "transparent",
borderWidth: String = "1px") {
... 省略 ...
}
fun main() {
drawCircle(100, 100, 50, borderWidth="2px")
}
Unit関数
値を返さない関数はUnit関数として扱われる。
fun printMessage(msg: String): Unit {
println(msg)
}
Unitは次のように通常省略される。
fun printMessage(msg: String) {
println(msg)
}
Nothing型関数
常に例外を返し、戻り値を返せない関数はNothing型の関数として定義する。
fun raiseError(msg: String): Nothing {
throw MyException(msg)
}
可変長引数
varargは可変長引数を宣言する。
fun foo(vararg args: String) {
args.forEach {
println(it)
}
}
fun main() {
foo("A", "B", "C")
}
可変長引数の関数に配列を変数として渡すときは、スプレッド演算子(*)を使って配列を展開して渡す必要がある。
fun main() {
val arr = arrayOf("A", "B", "C")
foo(*arr)
}
関数参照(::)
関数名の前に::を付けると、関数を参照するオブジェクトを取得できる。次の例では、add関数を関数オブジェクトmethodに代入して呼び出している。
fun add(x: Int, y: Int): Int = x + y
fun main() {
val method = ::add
println(method(3, 5))
}
再帰呼び出し
関数が自分自身を呼び出すことを再帰呼び出しという。再帰呼び出しを使うと、ループを宣言的に書ける。たとえば、引数リストの合計値を返す関数を例にしてみよう。まず通常版である。
例: forによるループ
fun sum(list: List<Int>): Int {
var sum = 0
for (e in list) {
sum += e
}
return sum
}
forによってループを回している。変数sumはvarで宣言され、繰り返し新しい値が代入される。
次は再帰呼び出し版である。
例: 再帰呼び出しによるループ
fun sum(list: List<Int>): Int =
if (list.isEmpty()) 0 else list.first() + sum(list.drop(1))
forによる再代入がなくなった。代わりに、sum関数の定義部分で自分自身を呼び出している。
参考までに、isEmpty()、first()、drop(1)は整数のListであるlistのメソッドである。それぞれ、リストが空かどうか、リストの最初の要素、最初の要素を1つ除いた新しいリストを返す。
最大公約数を求める関数を作ってみよう。
// 大きい数を返す。
fun max(a: Int, b: Int): Int = if (a < b) b else a
// 小さい数を返す。
fun min(a: Int, b: Int): Int = if (a <= b) a else b
// 最大公約数を返す。
fun gcd(a: Int, b: Int): Int {
var x = max(a, b)
var y = min(a, b)
while(y != 0) {
val w = y
y = x % y
x = w
}
return x
}
最大公約数を求めるgcd関数を、再帰呼び出しを使って再実装してみよう。
例: gcdを再帰関数にする
fun gcd(a: Int, b: Int): Int {
val x = max(a, b)
val y = min(a, b)
return if (y == 0) x
else gcd(y, x % y)
}
varもwhileループも、そして一時変数wも削除できる。このように再帰呼び出しを使うと、コードがすっきりして読みやすくなる場合が多い。
再帰呼び出しの欠点は、関数を何度も呼び出してスタックを消費することである。何度も、環境によっては非常に多くの回数、関数を呼び出し続けると、スタックオーバーフローが発生してプログラムがクラッシュする。これを避けるため、Kotlinには末尾呼び出し最適化(tail call optimization)という仕組みがある。
末尾呼び出しとは、再帰呼び出しが最後にある呼び出しのことである。たとえば、すぐ上の例のgcd関数は末尾呼び出しを行っている。このような再帰関数にtailrecキーワードを付けると、末尾呼び出し最適化が行われ、スタックオーバーフローが発生しないコードに変換される。
例: 末尾呼び出し最適化が有効なgcd関数
tailrec fun gcd(a: Int, b: Int): Int {
val x = max(a, b)
val y = min(a, b)
return if (y == 0) x
else gcd(y, x % y)
}
最適化が有効になるのはあくまで末尾呼び出しだけなので、関数によっては再帰の方法を学んでおく必要がある。