メインコンテンツまでスキップ

関数

Kotlinの関数は、funキーワードを使って宣言します。

fun double(x: Int): Int {
return 2 * x
}

関数の使用法

関数は、標準的な方法で呼び出されます。

val result = double(2)

メンバ関数の呼び出しは、ドット記法を使用します。

Stream().read() // Streamクラスのインスタンスを作成し、read()を呼び出す

引数

関数の引数は、Pascal記法 - name: type を使用して定義します。引数はカンマで区切られ、それぞれの引数は明示的に型指定されなければなりません。

fun powerOf(number: Int, exponent: Int): Int { /*...*/ }

関数の引数を宣言するときに、末尾のカンマを使用できます。

fun powerOf(
number: Int,
exponent: Int, // trailing comma
) { /*...*/ }

デフォルト引数

関数の引数は、デフォルト値を持つことができます。これは、対応する引数を省略した場合に使用されます。これにより、オーバーロードの数を減らすことができます。

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つ以上の引数に名前を付けることができます。これは、関数に多くの引数があり、特にブール値または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は、1つの値 - 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はArrayです
result.add(t)
return result
}

この場合、可変個の引数を関数に渡すことができます。

val list = asList(1, 2, 3)

関数内では、型Tvarargパラメータは、上記の例のように、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キーワードでマークされた関数は、infix記法(呼び出しのドットと括弧を省略)を使用して呼び出すこともできます。Infix関数は、次の要件を満たす必要があります。

infix fun Int.shl(x: Int): Int { ... }

// infix記法を使用した関数の呼び出し
1 shl 2

// は以下と同じです
1.shl(2)
注記

Infix関数呼び出しは、算術演算子、型キャスト、およびrangeTo演算子よりも優先順位が低くなります。 次の式は同等です。

  • 1 shl 2 + 31 shl (2 + 3) と同等です
  • 0 until n * 20 until (n * 2) と同等です
  • xs union ys as Set<*>xs union (ys as Set<*>) と同等です

一方、infix関数呼び出しの優先順位は、ブール演算子&&および||is- およびin-チェック、およびその他のいくつかの演算子よりも高くなります。これらの式も同等です。

  • a && b xor ca && (b xor c) と同等です
  • a xor b in c(a xor b) in c と同等です

Infix関数は、常にレシーバとパラメータの両方を指定する必要があることに注意してください。Infix記法を使用して現在のレシーバでメソッドを呼び出す場合は、明示的に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 // "good enough", could be 10^-15

tailrec fun findFixPoint(x: Double = 1.0): Double =
if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))

このコードは、コサインのfixpoint(不動点)を計算します。これは数学的な定数です。1.0から始めて、結果が変化しなくなるまでMath.cosを繰り返し呼び出し、指定されたeps精度で0.7390851332151611の結果が得られます。結果のコードは、次のより伝統的なスタイルと同等です。

val eps = 1E-10 // "good enough", could be 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ブロック内、またはopen関数で、さらにコードがある場合は、末尾再帰を使用できません。 現在、末尾再帰は、Kotlin for JVMおよびKotlin/Nativeでサポートされています。

参照: