Edit Page

関数

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 が使われる

名前付き引数

関数を呼び出すとき、関数のパラメータに名前を付けることができます。 これは関数が沢山の引数を持ち、どの値がどの引数か関連付けが難しいようなケース、 特にbooleanやnullの引数にとても便利です。

関数呼び出しで名前付き引数を使うと、引数の順番を自由に並べ替える事が出来ます。 デフォルト値を使いたければ、それらの引数を単に省略すればOKです。

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!") // 訳:これは長いStringです!

全部の引数を省略するのでは無く、 特定の引数だけをデフォルト値としてスキップする事も出来ます。 しかし、最初にスキップした引数のあとは、全ての引数に名前をつける必要があります:

// 訳:これは短いStringです!
reformat("This is a short String!", upperCaseFirstLetter = false, wordSeparator = '_')

可変長引数(vararg)を名前付き引数として渡すには、 spread 演算子が使えます:

fun foo(vararg strings: String) { /*...*/ }

foo(strings = *arrayOf("a", "b", "c"))

JVMでJavaの関数を呼び出す時には、 Javaバイトコードは常に関数パラメータの名を保存する訳では無い為、 名前付き引数構文を使用できないことに注意してください。

Unit を返す関数

関数が有用な値を何も返さない場合、その戻り値の型は Unit になります。 Unit は、唯一の値 ( Unit ) だけを持つ型です。 この値は、明示的に return しなくてもかまいません:

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はブロックの本体から関数の戻り値の型を推論することはありません。 なぜならこのような関数は本体内に複雑な制御フローをもつことがあり、 戻り値の型が読み手に(時にはコンパイラにも)自明ではないからです。

可変長引数(vararg)

関数(通常は最後のひとつ)のパラメータは、 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 としてマーク出来るパラメータは一つだけです。 vararg パラメータが変数リストの最後のひとつでない場合には、 その後に続くパラメータは 名前付き引数の構文を使用するか、またはパラメータが関数の型をもっている場合は括弧の外でラムダを渡すことによって、渡すことができます。

vararg をもつ関数を呼び出すとき、例えば asList(1, 2, 3) のように、一つずつ引数を渡すことができます。 または、すでに配列を持っており、関数にその内容を渡したい場合は、( * を配列名の接頭辞にする) spread 演算子を使用します:

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 notations) を使用して関数を呼び出すことができます。 中置記法ではドットやカッコを省略します。 中置関数(infix funciton)は以下の要件を満たす必要があります:

  • メンバ関数や拡張関数である
  • パラメータをただ一つだけ持っている
  • パラメータは可変長引数では無い、またデフォルト引数もあってはならない
// Intにエクステンションを定義
infix fun Int.shl(x: Int): Int {
...
}

// 拡張関数を infix ノーテーションを使用して呼ぶ
1 shl 2

// これは次と同じ
1.shl(2)

中置の関数は算術演算子、型キャスト、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<*>) と同じ意味

一方、中置の関数は論理演算子&&||, isinなどのチェックなどの演算子よりは高い優先順位となります。 だから以下の式も同じ意味となります:

  • a && b xor ca && (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))

このコードは、数学定数であるコサインの不動点(fixpoint)を算出します。 それは Math.cos を 1.0 から始めて、それ以上変化しなくなるまで単に繰り返し呼び出します、 その結果、指定した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 ブロック内で使用することもできません。 現在、末尾再帰はJVMとKotlin/Nativeのバックエンドでのみサポートされています。

以下も見てね