Edit Page

関数

関数の宣言

Kotlinの関数は fun キーワードを使用して宣言されています。

fun double(x: Int): Int {
}

関数の使い方

関数の呼び出しは、伝統的なアプローチを採用しています。

val result = double(2)

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

Sample().foo() // Sampleクラスのインスタンスを作ってfooを呼ぶ

中置記法

次のようなとき、中置表記法 (infix notations) を使用して関数を呼び出すことができます:

  • メンバ関数や拡張関数であるとき
  • 単一のパラメータを持っているとき
  • infix キーワードでマークされているとき
// Intにエクステンションを定義
infix fun Int.shl(x: Int): Int {
...
}

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

1 shl 2

// これは次と同じ

1.shl(2)

パラメーター

関数のパラメータはパスカル記法、すなわち 名前: タイプ を使用して定義されています。パラメータはカンマを使用して分離されます。各パラメータは、明示的に入力する必要があります。

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

デフォルトの引数

関数パラメータは、対応する引数が省略されているときに使用されるデフォルト値をもつことができます。これにより、他言語に比べてオーバーロード数を減らすことができます。

fun read(b: Array<Byte>, 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 reformat(str: String,
             normalizeCase: Boolean = true,
             upperCaseFirstLetter: Boolean = true,
             divideByCamelHumps: Boolean = false,
             wordSeparator: Char = ' ') {
...
}

私たちは、デフォルト引数を使用して、これを呼び出すことができます:

reformat(str)

しかし、デフォルト値が無い場合はそれの呼び出しは次のようになります:

reformat(str, true, true, false, '_')

名前付き引数で、コードをはるかに読みやすくすることができます:

reformat(str,
    normalizeCase = true,
    upperCaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
  )

すべての引数を必要としない場合:

reformat(str, wordSeparator = '_')

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

可変長引数(可変引数, 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 としてマークされることがあります。 vararg パラメータが変数リストの最後のひとつでない場合には、名前付き引数の構文を使用して、またはパラメータが関数型をもっている場合、括弧の外でラムダを渡すことによって、リストにおける次のパラメータの値を渡すことができます。

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

val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

関数のスコープ

Kotlinでは、関数はファイルのトップレベルで宣言することができます。これは、関数を保持するためのクラスを作成する必要がないことを意味します。JavaやC#, Scalaなどの言語と同じように。トップレベルの関数に加えて、Kotlinの関数はメンバ関数や拡張機能として、ローカルに宣言することもできます。

ローカル関数

Kotlinはローカル関数、すなわち、ある関数内の別の関数をサポートしています。

fun dfs(graph: Graph) {
  fun dfs(current: Vertex, visited: Set<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 修飾子でマークされ、必要な形式を満たしている場合、コンパイラは高速かつ効率的なループベースのバージョンを残して、再帰を最適化します。

tailrec fun findFixPoint(x: Double = 1.0): Double
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

このコードは、数学定数であるコサインの不動点(固定点, fixpoint)を算出します。それは Math.cos を 1.0 から始めて結果に0.7390851332151607の結果を得、それ以上変化しなくなるまで単に繰り返し呼び出します。その結果生成されたコードは、このより伝統的なスタイルに相当します。

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (x == y) return y
        x = y
    }
}

tailrec 修飾子の対象となるためには、関数は実行する最後の操作として自身を呼び出す必要があります。再帰呼び出しの後に多くのコードがあるときは、末尾再帰を使用することはできません。 try / catch / finally ブロック内で使用することもできません。現在、末尾再帰はJVMのバックエンドでのみサポートされています。