関数
Kotlinの関数はfun
キーワードを使って宣言します:
fun double(x: Int): Int {
return 2 * x
}
関数の使用法
関数は標準的な方法で呼び出されます:
val result = double(2)
メンバー関数を呼び出すにはドット記法を使用します:
Stream().read() // Streamクラスのインスタンスを作成し、read()を呼び出す
パラメータ
関数パラメータはパスカル記法(名前: 型)を使用して定義されます。パラメータはコンマで区切られ、各パラメータは明示的に型指定する必要があります:
fun powerOf(number: Int, exponent: Int): Int { /*...*/ }
関数パラメータを宣言する際に、末尾のコンマを使用できます:
fun powerOf(
number: Int,
exponent: Int, // 末尾のコンマ
) { /*...*/ }
デフォルト値を持つパラメータ
関数パラメータにはデフォルト値を設定できます。これは対応する引数を省略した場合に使用されます。これによりオーバーロードの数を減らすことができます:
fun read(
b: ByteArray,
off: Int = 0,
len: Int = b.size,
) { /*...*/ }
このようなパラメータは_オプションパラメータ_とも呼ばれます。
デフォルト値は型に=
を付加することで設定されます。
オーバーライドするメソッドは常にベースメソッドのデフォルトパラメータ値を使用します。デフォルトパラメータ値を持つメソッドをオーバーライドする場合、シグネチャからデフォルトパラメータ値を省略する必要があります:
open class A {
open fun foo(i: Int = 10) { /*...*/ }
}
class B : A() {
override fun foo(i: Int) { /*...*/ } // デフォルト値は許可されていません。
}
デフォルト値を持つパラメータがデフォルト値を持たないパラメータの前に来る場合、デフォルト値は名前付き引数を使用して関数を呼び出すことによってのみ使用できます:
fun foo(
bar: Int = 0,
baz: Int,
) { /*...*/ }
foo(baz = 1) // デフォルト値 bar = 0 が使用されます
すべてのデフォルト値を持つパラメータの後に続く最後のパラメータが関数型である場合、対応するラムダ引数を名前付き引数として、または括弧の外に渡すことができます:
fun foo(
bar: Int = 0,
baz: Int = 1,
qux: () -> Unit,
) { /*...*/ }
foo(1) { println("hello") } // デフォルト値 baz = 1 が使用されます
foo(qux = { println("hello") }) // 両方のデフォルト値 bar = 0 および baz = 1 が使用されます
foo { println("hello") } // 両方のデフォルト値 bar = 0 および baz = 1 が使用されます
名前付き引数
関数を呼び出す際に、その引数の1つまたは複数を名前で指定できます。これは、関数に多くの引数があり、特にそれがboolean値またはnull
値である場合に、値を引数と関連付けるのが難しい場合に役立ちます。
関数呼び出しで名前付き引数を使用する場合、それらがリストされる順序を自由に変更できます。デフォルト値を使用したい場合は、これらの引数を完全に省略できます。
4つの引数にデフォルト値が設定されているreformat()
関数を考えてみましょう。
fun reformat(
str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ',
) { /*...*/ }
この関数を呼び出す際、すべての引数を名前で指定する必要はありません:
reformat(
"String!",
false,
upperCaseFirstLetter = false,
divideByCamelHumps = true,
'_'
)
デフォルト値を持つすべての引数をスキップできます:
reformat("This is a long String!")
すべてのデフォルト値を持つ引数を省略するのではなく、特定の引数をスキップすることもできます。ただし、最初のスキップされた引数の後では、それ以降のすべての引数を名前で指定する必要があります:
reformat("This is a short String!", upperCaseFirstLetter = false, wordSeparator = '_')
可変長引数 (vararg
)を名前付きで渡すには、スプレッド演算子(配列の前に*
を付加)を使用します:
fun foo(vararg strings: String) { /*...*/ }
foo(strings = *arrayOf("a", "b", "c"))
JVM上でJava関数を呼び出す場合、Javaバイトコードが関数パラメータの名前を常に保持するとは限らないため、名前付き引数構文を使用することはできません。
Unitを返す関数
関数が有用な値を返さない場合、その戻り値の型はUnit
です。Unit
は唯一の値Unit
を持つ型です。この値は明示的に返す必要はありません:
fun printHello(name: String?): Unit {
if (name != null)
println("Hello $name")
else
println("Hi there!")
// `return Unit` または `return` は任意です
}
Unit
の戻り値の型宣言も任意です。上記のコードは以下と等価です:
fun printHello(name: String?) { ... }
単一式関数
関数本体が単一の式で構成されている場合、中括弧を省略し、=
記号の後に本体を指定できます:
fun double(x: Int): Int = x * 2
戻り値の型がコンパイラによって推論できる場合、明示的な宣言はオプションです:
fun double(x: Int) = x * 2
明示的な戻り値の型
ブロック本体を持つ関数は、Unit
を返すことが意図されている場合を除き、常に明示的に戻り値の型を指定する必要があります。その場合、戻り値の型の指定はオプションです。
Kotlinはブロック本体を持つ関数の戻り値の型を推論しません。これは、そのような関数が本体に複雑な制御フローを持つ可能性があり、戻り値の型が読者にとって(そして時にはコンパイラにとっても)不明確になるためです。
可変長引数 (varargs)
関数のパラメータ(通常は最後のパラメータ)をvararg
修飾子でマークできます:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // tsは配列です
result.add(t)
return result
}
この場合、可変数の引数を関数に渡すことができます:
val list = asList(1, 2, 3)
関数内では、型T
のvararg
パラメータは、上記の例のようにT
の配列として可視化され、ts
変数の型はArray<out T>
になります。
vararg
としてマークできるパラメータは1つだけです。vararg
パラメータがリストの最後でない場合、それに続くパラメータの値は名前付き引数構文を使用するか、パラメータが関数型の場合は括弧の外にラムダを渡すことで渡す必要があります。
vararg
関数を呼び出す際、例えばasList(1, 2, 3)
のように引数を個別に渡すことができます。すでに配列があり、その内容を関数に渡したい場合は、スプレッド演算子(配列の前に*
を付加)を使用します:
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
プリミティブ型配列をvararg
に渡したい場合は、toTypedArray()
関数を使用して通常の(型付けされた)配列に変換する必要があります:
val a = intArrayOf(1, 2, 3) // IntArrayはプリミティブ型配列です
val list = asList(-1, 0, *a.toTypedArray(), 4)
中置記法
infix
キーワードでマークされた関数は、中置記法(ドットと呼び出しの括弧を省略)を使用して呼び出すこともできます。中置関数は次の要件を満たす必要があります:
infix fun Int.shl(x: Int): Int { ... }
// 中置記法を使用して関数を呼び出す
1 shl 2
// と同じです
1.shl(2)
中置関数の呼び出しは、算術演算子、型キャスト、および
rangeTo
演算子よりも低い優先順位を持ちます。次の式は同等です:
1 shl 2 + 3
は1 shl (2 + 3)
と等価です0 until n * 2
は0 until (n * 2)
と等価ですxs union ys as Set<*>
はxs union (ys as Set<*>)
と等価です一方、中置関数の呼び出しの優先順位は、論理演算子
&&
と||
、is
-およびin
-チェック、およびその他のいくつかの演算子よりも高くなっています。これらの式も同様に同等です:
a && b xor c
はa && (b xor c)
と等価ですa xor b in c
は(a xor b) in c
と等価です
中置関数は常にレシーバーとパラメータの両方を指定する必要があることに注意してください。中置記法を使用して現在のレシーバーのメソッドを呼び出す場合、明示的にthis
を使用します。これは曖昧なパースを防ぐために必要です。
class MyStringCollection {
infix fun add(s: String) { /*...*/ }
fun build() {
this add "abc" // 正しい
add("abc") // 正しい
//add "abc" // 不正: レシーバーを指定する必要があります
}
}
関数スコープ
Kotlinの関数はファイル内でトップレベルで宣言できます。これは、Java、C#、Scala(Scala 3以降でトップレベル定義が利用可能)のような言語で関数を保持するためにクラスを作成する必要があるのとは異なり、その必要がないことを意味します。トップレベル関数の他に、Kotlin関数はメンバー関数や拡張関数としてローカルに宣言することもできます。
ローカル関数
Kotlinはローカル関数をサポートしています。これは他の関数内にある関数です:
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
ローカル関数は、外側の関数のローカル変数(クロージャ)にアクセスできます。上記の場合、visited
はローカル変数にできます:
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
メンバー関数
メンバー関数は、クラスまたはオブジェクト内に定義された関数です:
class Sample {
fun foo() { print("Foo") }
}
メンバー関数はドット記法で呼び出されます:
Sample().foo() // Sampleクラスのインスタンスを作成し、fooを呼び出す
クラスとメンバーのオーバーライドに関する詳細は、クラスと継承を参照してください。
ジェネリック関数
関数はジェネリックパラメータを持つことができ、これらは関数名の前に山括弧を使用して指定されます:
fun <T> singletonList(item: T): List<T> { /*...*/ }
ジェネリック関数の詳細については、ジェネリクスを参照してください。
末尾再帰関数
Kotlinは末尾再帰として知られる関数型プログラミングのスタイルをサポートしています。通常ループを使用する一部のアルゴリズムでは、スタックオーバーフローのリスクなしに再帰関数を使用できます。tailrec
修飾子でマークされ、必要な形式的条件を満たしている場合、コンパイラは再帰を最適化し、高速で効率的なループベースのバージョンに置き換えます:
val eps = 1E-10 // "十分良い"、10^-15でも可
tailrec fun findFixPoint(x: Double = 1.0): Double =
if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
このコードは、数学定数であるコサインの不動点
を計算します。これは、1.0
から開始して結果が変化しなくなるまでMath.cos
を繰り返し呼び出し、指定されたeps
精度で0.7390851332151611
という結果を生成します。結果として得られるコードは、より伝統的な以下のスタイルと同等です:
val eps = 1E-10 // "十分良い"、10^-15でも可
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (Math.abs(x - y) < eps) return x
x = Math.cos(x)
}
}
tailrec
修飾子の対象となるには、関数は自身を最後の操作として呼び出す必要があります。再帰呼び出しの後にさらにコードがある場合、try
/catch
/finally
ブロック内、またはオープン関数では末尾再帰を使用できません。現在、KotlinではJVMとKotlin/Nativeで末尾再帰がサポートされています。
こちらも参照: