Ekspresi Lambda di Kotlin

1. Ikhtisar

Pada artikel ini, kita akan menjelajahi Lambdas dalam bahasa Kotlin. Perlu diingat bahwa lambda tidak unik di Kotlin dan telah ada selama bertahun-tahun di banyak bahasa lain.

Ekspresi Lambdas pada dasarnya adalah fungsi anonim yang dapat kita perlakukan sebagai nilai - kita dapat, misalnya, meneruskannya sebagai argumen ke metode, mengembalikannya, atau melakukan hal lain yang dapat kita lakukan dengan objek normal.

2. Mendefinisikan Lambda

Seperti yang akan kita lihat, Kotlin Lambdas sangat mirip dengan Java Lambdas. Anda dapat mengetahui lebih lanjut tentang cara bekerja dengan Java Lambdas dan beberapa praktik terbaik di sini.

Untuk mendefinisikan lambda, kita harus tetap berpegang pada sintaks:

val lambdaName : Type = { argumentList -> codeBody }

Satu-satunya bagian lambda yang bukan opsional adalah codeBody.

Daftar argumen dapat dilewati saat menentukan paling banyak satu argumen dan Type sering kali dapat disimpulkan oleh compiler Kotlin. Kami tidak selalu membutuhkan variabel juga, lambda dapat dikirimkan secara langsung sebagai argumen metode.

Jenis perintah terakhir dalam blok lambda adalah jenis yang dikembalikan.

2.1. Ketik Inferensi

Jenis inferensi Kotlin memungkinkan jenis lambda dievaluasi oleh compiler.

Penulisan lambda yang menghasilkan kuadrat bilangan akan ditulis sebagai:

val square = { number: Int -> number * number } val nine = square(3)

Kotlin akan mengevaluasi contoh di atas menjadi fungsi yang mengambil satu Int dan mengembalikan Int: (Int) -> Int

Jika kita ingin membuat lambda yang mengalikan angka argumen tunggalnya dengan 100 maka kembalikan nilai itu sebagai String:

val magnitude100String = { input : Int -> val magnitude = input * 100 magnitude.toString() } 

Kotlin akan memahami bahwa lambda ini berjenis (Int) -> String .

2.2. Jenis Deklarasi

Terkadang Kotlin tidak dapat menyimpulkan tipe kita dan kita harus secara eksplisit mendeklarasikan tipe lambda kita; seperti yang kami bisa dengan tipe lainnya.

Polanya adalah input -> output , namun, jika kode tidak mengembalikan nilai, kami menggunakan tipe Unit :

val that : Int -> Int = { three -> three }
val more : (String, Int) -> String = { str, int -> str + int }
val noReturn : Int -> Unit = { num -> println(num) }

Kita dapat menggunakan lambda sebagai ekstensi kelas:

val another : String.(Int) -> String = { this + it }

Pola yang kami gunakan di sini sedikit berbeda dengan lambda lain yang telah kami tentukan. Tanda kurung kami masih berisi argumen kami tetapi sebelum tanda kurung kami, kami memiliki jenis yang akan kami lampirkan lambda ini.

Untuk menggunakan pola ini dari String, kita memanggil Type.lambdaName (argumen) sehingga memanggil contoh 'lain' kita:

fun extendString(arg: String, num: Int) : String { val another : String.(Int) -> String = { this + it } return arg.another(num) }

2.3. Kembali dari Lambda

Ekspresi terakhir adalah nilai yang akan dikembalikan setelah lambda dijalankan:

val calculateGrade = { grade : Int -> when(grade) { in 0..40 -> "Fail" in 41..70 -> "Pass" in 71..100 -> "Distinction" else -> false } }

Cara terakhir adalah dengan memanfaatkan definisi fungsi anonim - kita harus mendefinisikan argumen dan tipe kembalian secara eksplisit dan dapat menggunakan pernyataan return sama seperti metode apa pun:

val calculateGrade = fun(grade: Int): String { if (grade  100) { return "Error" } else if (grade < 40) { return "Fail" } else if (grade < 70) { return "Pass" } return "Distinction" }

3. itu

Singkatan dari lambda argumen tunggal adalah dengan menggunakan kata kunci ' itu' . Nilai ini mewakili setiap argumen yang kita berikan ke fungsi lambda.

Kami akan melakukan metode forEach yang sama pada array Ints berikut :

val array = arrayOf(1, 2, 3, 4, 5, 6)

Pertama-tama kita akan melihat bentuk longhand dari fungsi lambda, diikuti dengan bentuk singkatan dari kode yang sama, di mana ' it ' akan mewakili setiap elemen dalam larik berikut.

Tulisan tangan:

array.forEach { item -> println(item * 4) }

Steno:

array.forEach { println(it * 4) }

4. Menerapkan Lambdas

Kami akan secara singkat membahas cara memanggil lambda yang ada dalam cakupan serta cara meneruskan lambda sebagai argumen.

Setelah objek lambda berada dalam cakupan, panggil sebagai metode dalam cakupan lainnya, menggunakan namanya diikuti oleh tanda kurung dan argumen apa pun:

fun invokeLambda(lambda: (Double) -> Boolean) : Boolean { return lambda(4.329) }

Jika kita perlu meneruskan lambda sebagai argumen ke metode tingkat tinggi, kita punya lima opsi.

4.1. Variabel Objek Lambda

Dengan menggunakan objek lambda yang sudah ada seperti yang dideklarasikan di bagian 2, kami meneruskan objek ke metode seperti yang kami lakukan dengan argumen lainnya:

@Test fun whenPassingALambdaObject_thenCallTriggerLambda() { val lambda = { arg: Double -> arg == 4.329 } val result = invokeLambda(lambda) assertTrue(result) }

4.2. Lambda Literal

Alih-alih menugaskan lambda ke variabel, kita bisa meneruskan literal langsung ke pemanggilan metode:

Test fun whenPassingALambdaLiteral_thenCallTriggerLambda() { val result = invokeLambda({ true }) assertTrue(result) }

4.3. Lambda Literal Outside the Brackets

Pola lain untuk lambda literal yang didorong oleh JetBrains - adalah meneruskan lambda sebagai argumen terakhir ke sebuah metode dan menempatkan lambda di luar pemanggilan metode:

@Test fun whenPassingALambdaLiteralOutsideBrackets_thenCallTriggerLambda() { val result = invokeLambda { arg -> arg.isNaN() } assertFalse(result) }

4.4. Referensi Metode

Finally, we have the option of using method references. These are references to existing methods.

In our example below, we take Double::isFinite. That function then takes on the same structure as a lambda, however, it's of type KFunction1 as it has one argument, takes in a Double and returns a Boolean:

@Test fun whenPassingAFunctionReference_thenCallTriggerLambda() { val reference = Double::isFinite val result = invokeLambda(reference) assertTrue(result) }

5. Kotlin Lambda in Java

Kotlin uses generated function interfaces to interop with Java. They exist in the Kotlin source code here.

We have a limit on the number of arguments that can be passed in with these generated classes. The current limit is 22; represented by the interface Function22.

The structure of a Function interface's generics is that the number and represents the number of arguments to the lambda, then that number of classes will be the argument Types in order.

The final generic argument is the return type:

import kotlin.jvm.functions.* public interface Function1 : Function { public operator fun invoke(p1: P1): R }

When there is no return type defined within the Kotlin code, then the lambda returns a Kotlin Unit. The Java code must import the class from the kotlin package and return with null.

Below is an example of calling a Kotlin Lambda from a project that is part Kotlin and part Java:

import kotlin.Unit; import kotlin.jvm.functions.Function1; ... new Function1() { @Override public Unit invoke(Customer c) { AnalyticsManager.trackFacebookLogin(c.getCreated()); return null; } } 

When using Java8, we use a Java lambda instead of a Function anonymous class:

@Test void givenJava8_whenUsingLambda_thenReturnLambdaResult() { assertTrue(LambdaKt.takeLambda(c -> c >= 0)); }

6. Anonymous Inner Classes

Kotlin has two interesting ways of working with Anonymous Inner Classes.

6.1. Object Expression

When calling a Kotlin Inner Anonymous Class or a Java Anonymous Class comprised of multiple methods we must implement an Object Expression.

To demonstrate this, we'll take a simple interface and a class that takes an implementation of that interface and calls the methods dependent on a Boolean argument:

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() } } }

Now to provide an anonymous inner class, we need to use the “object” syntax:

@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. Lambda Expression

On the other hand, we may also have the option of using a lambda instead. Using lambdas in lieu of an Anonymous Inner Class has certain conditions:

  1. The class is an implementation of a Java interface (not a Kotlin one)
  2. the interface must have max

If both of these conditions are met, we may use a lambda expression instead.

Lambda itu sendiri akan mengambil argumen sebanyak yang dilakukan metode tunggal antarmuka.

Contoh umum akan menggunakan lambda, bukan Konsumen Java standar :

val list = ArrayList(2) list.stream() .forEach({ i -> println(i) })

7. Kesimpulan

Meskipun mirip secara sintaksis, Kotlin dan Java lambda adalah fitur yang sangat berbeda. Saat menargetkan Java 6, Kotlin harus mengubah lambda-nya menjadi struktur yang dapat digunakan dalam JVM 1.6.

Meskipun demikian, praktik terbaik Java 8 lambda masih berlaku.

Selengkapnya tentang praktik terbaik lambda di sini.

Cuplikan kode, seperti biasa, dapat ditemukan di GitHub.