Kotlinでのラムダ式
1. 概要
この記事では、Kotlin言語でラムダを探索します。 ラムダはKotlinに固有のものではなく、他の多くの言語で長年使用されてきたことを覚えておいてください。
Lambdas式は基本的に無名関数であり、値として扱うことができます。たとえば、メソッドへの引数として渡したり、返したり、通常のオブジェクトで実行できるその他のことを実行したりできます。
2. ラムダの定義
後で説明するように、KotlinLambdasはJavaLambdasと非常によく似ています。 Java Lambdaの操作方法といくつかのベストプラクティスについて詳しくは、こちらをご覧ください。
ラムダを定義するには、次の構文に固執する必要があります。
val lambdaName : Type = { argumentList -> codeBody }
オプションではないラムダの唯一の部分は codeBody。
引数リストは、最大で1つの引数を定義するときにスキップでき、タイプはKotlinコンパイラによって推測されることがよくあります。 必ずしも変数も必要ではありません。ラムダはメソッド引数として直接渡すことができます。
ラムダブロック内の最後のコマンドのタイプは、返されるタイプです。
2.1. 型推論
Kotlinの型推論により、ラムダの型をコンパイラーで評価できます。
数値の2乗を生成するラムダを書くと、次のようになります。
val square = { number: Int -> number * number }
val nine = square(3)
Kotlinは、上記の例を1つの Int を取り、 Int: (Int)->Intを返す関数であると評価します。
単一の引数番号に100を掛けるラムダを作成したい場合は、その値を String:として返します。
val magnitude100String = { input : Int ->
val magnitude = input * 100
magnitude.toString()
}
Kotlinは、このラムダが(Int)->String型であることを理解します。
2.2. タイプ宣言
Kotlinが型を推測できない場合があり、ラムダの型を明示的に宣言する必要があります。 他のタイプと同じように。
パターンはinput->output ですが、コードが値を返さない場合は、タイプUnitを使用します。
val that : Int -> Int = { three -> three }
val more : (String, Int) -> String = { str, int -> str + int }
val noReturn : Int -> Unit = { num -> println(num) }
クラス拡張としてラムダを使用できます。
val another : String.(Int) -> String = { this + it }
ここで使用するパターンは、定義した他のラムダとは少し異なります。 角かっこにはまだ引数が含まれていますが、角かっこの前に、このラムダをアタッチする型があります。
文字列からこのパターンを使用するには、 Type.lambdaName(arguments)を呼び出して、「別の」例を呼び出します。
fun extendString(arg: String, num: Int) : String {
val another : String.(Int) -> String = { this + it }
return arg.another(num)
}
2.3. ラムダからの帰り
最後の式は、ラムダが実行された後に返される値です。
val calculateGrade = { grade : Int ->
when(grade) {
in 0..40 -> "Fail"
in 41..70 -> "Pass"
in 71..100 -> "Distinction"
else -> false
}
}
最後の方法は、無名関数の定義を活用することです。引数とreturn型を明示的に定義する必要があり、他のメソッドと同じようにreturnステートメントを使用できます。
val calculateGrade = fun(grade: Int): String {
if (grade < 0 || grade > 100) {
return "Error"
} else if (grade < 40) {
return "Fail"
} else if (grade < 70) {
return "Pass"
}
return "Distinction"
}
3. それ
単一引数ラムダの省略形は、キーワード’it’を使用することです。 この値は、ラムダ関数に渡す引数が1つだけであることを表します。
次のIntsの配列に対して、同じforEachメソッドを実行します。
val array = arrayOf(1, 2, 3, 4, 5, 6)
最初にラムダ関数の短縮形を見てから、同じコードの短縮形を見ていきます。ここで、「it」は次の配列の各要素を表します。
ロングハンド:
array.forEach { item -> println(item * 4) }
速記:
array.forEach { println(it * 4) }
4. ラムダの実装
スコープ内にあるラムダを呼び出す方法と、ラムダを引数として渡す方法について簡単に説明します。
ラムダオブジェクトがスコープ内に入ると、その名前の後に角かっこと引数を使用して、他のスコープ内メソッドと同じように呼び出します。
fun invokeLambda(lambda: (Double) -> Boolean) : Boolean {
return lambda(4.329)
}
ラムダを引数として高次のメソッドに渡す必要がある場合、5つのオプションがあります。
4.1. ラムダオブジェクト変数
セクション2で宣言されている既存のラムダオブジェクトを使用して、他の引数と同じようにオブジェクトをメソッドに渡します。
@Test
fun whenPassingALambdaObject_thenCallTriggerLambda() {
val lambda = { arg: Double ->
arg == 4.329
}
val result = invokeLambda(lambda)
assertTrue(result)
}
4.2. ラムダリテラル
ラムダを変数に割り当てる代わりに、リテラルをメソッド呼び出しに直接渡すことができます。
Test
fun whenPassingALambdaLiteral_thenCallTriggerLambda() {
val result = invokeLambda({
true
})
assertTrue(result)
}
4.3. ブラケットの外側のラムダ文字
JetBrainsによって推奨されるラムダリテラルのもう1つのパターンは、ラムダをメソッドの最後の引数として渡し、ラムダをメソッド呼び出しの外に配置することです。
@Test
fun whenPassingALambdaLiteralOutsideBrackets_thenCallTriggerLambda() {
val result = invokeLambda { arg -> arg.isNaN() }
assertFalse(result)
}
4.4. メソッドリファレンス
最後に、メソッド参照を使用するオプションがあります。 これらは既存のメソッドへの参照です。
以下の例では、 Double ::isFiniteを使用します。 その関数はラムダと同じ構造を取りますが、タイプは KFunction1
@Test
fun whenPassingAFunctionReference_thenCallTriggerLambda() {
val reference = Double::isFinite
val result = invokeLambda(reference)
assertTrue(result)
}
5. JavaのKotlinLambda
Kotlinは、生成された関数インターフェースを使用してJavaと相互運用します。 それらはKotlinソースコードここに存在します。
これらの生成されたクラスで渡すことができる引数の数には制限があります。 現在の制限は22です。 インターフェイスFunction22で表されます。
関数インターフェースのジェネリックスの構造は、ラムダへの引数の数と数を表し、そのクラスの数が順番に引数タイプになります。
最後の一般的な引数は、戻り値の型です。
import kotlin.jvm.functions.*
public interface Function1<in P1, out R> : Function<R> {
public operator fun invoke(p1: P1): R
}
以下は、KotlinとJavaの一部であるプロジェクトからKotlinLambdaを呼び出す例です。
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
...
new Function1<Customer, Unit>() {
@Override
public Unit invoke(Customer c) {
AnalyticsManager.trackFacebookLogin(c.getCreated());
return null;
}
}
Java8を使用する場合、関数無名クラスの代わりにJavaラムダを使用します。
@Test
void givenJava8_whenUsingLambda_thenReturnLambdaResult() {
assertTrue(LambdaKt.takeLambda(c -> c >= 0));
}
6. 匿名の内部クラス
Kotlinには、匿名の内部クラスを操作する2つの興味深い方法があります。
6.1. オブジェクト式
Kotlin内部匿名クラスまたは複数のメソッドで構成されるJava匿名クラスを呼び出す場合は、オブジェクト式を実装する必要があります。
これを示すために、単純なインターフェイスと、そのインターフェイスの実装を取り、メソッドを呼び出すクラスをブール値引数に依存させます。
class Processor {
interface ActionCallback {
fun success() : String
fun failure() : String
}
fun performEvent(decision: Boolean, callback : ActionCallback) : String {
return if(decision) {
callback.success()
} else {
callback.failure()
}
}
}
匿名の内部クラスを提供するには、「オブジェクト」構文を使用する必要があります。
@Test
fun givenMultipleMethods_whenCallingAnonymousFunction_thenTriggerSuccess() {
val result = Processor().performEvent(true, object : Processor.ActionCallback {
override fun success() = "Success"
override fun failure() = "Failure"
})
assertEquals("Success", result)
}
6.2. ラムダ式
一方、代わりにラムダを使用するオプションもあります。 匿名内部クラスの代わりにラムダを使用することには特定の条件があります:
- このクラスは、Javaインターフェースの実装です(Kotlinのものではありません)。
- インターフェイスには最大
これらの条件の両方が満たされている場合は、代わりにラムダ式を使用できます。
ラムダ自体は、インターフェイスの単一メソッドと同じ数の引数を取ります。
一般的な例は、標準のJava Consumer:の代わりにラムダを使用することです。
val list = ArrayList<Int>(2)
list.stream()
.forEach({ i -> println(i) })
7. 結論
構文的には似ていますが、KotlinラムダとJavaラムダは完全に異なる機能です。 Java 6をターゲットにする場合、KotlinはラムダをJVM1.6内で利用できる構造に変換する必要があります。
それにもかかわらず、Java8ラムダのベストプラクティスは引き続き適用されます。
ラムダのベストプラクティスの詳細ここ。
コードスニペットは、いつものように、GitHubのにあります。