Metode Ekstensi di Kotlin

1. Perkenalan

Kotlin memperkenalkan konsep Metode Ekstensi - yang merupakan cara praktis untuk memperluas kelas yang ada dengan fungsionalitas baru tanpa menggunakan pewarisan atau bentuk pola Dekorator apa pun - setelah menentukan ekstensi. kami pada dasarnya dapat menggunakannya - karena ini adalah bagian dari API asli.

Ini bisa sangat berguna dalam membuat kode kita mudah dibaca dan dipelihara, karena kita dapat menambahkan metode yang khusus untuk kebutuhan kita dan membuatnya tampak seperti bagian dari kode asli, bahkan ketika kita tidak memiliki akses ke sumber.

Misalnya, kita mungkin perlu melakukan pelolosan XML pada String. Dalam kode Java standar, kita perlu menulis metode yang dapat melakukan ini dan memanggilnya:

String escaped = escapeStringForXml(input);

Sedangkan yang ditulis di Kotlin, cuplikan tersebut dapat diganti dengan:

val escaped = input.escapeForXml()

Ini tidak hanya lebih mudah dibaca, tetapi IDE akan dapat menawarkan metode sebagai opsi pelengkapan otomatis sama seperti jika itu adalah metode standar pada kelas String .

2. Metode Penyuluhan Perpustakaan Standar

Pustaka Standar Kotlin hadir dengan beberapa metode ekstensi out-of-the-box.

2.1. Metode Penyesuaian Konteks

Beberapa ekstensi umum ada dan dapat diterapkan ke semua jenis di aplikasi kita . Ini dapat digunakan untuk memastikan bahwa kode dijalankan dalam konteks yang sesuai, dan dalam beberapa kasus untuk memastikan bahwa variabel tidak null.

Ternyata, kemungkinan besar, kami memanfaatkan ekstensi tanpa menyadarinya.

Salah satu yang paling populer kemungkinan adalah metode let () , yang bisa dipanggil pada semua tipe di Kotlin - mari kita berikan fungsi yang akan dieksekusi pada nilai awal:

val name = "Baeldung" val uppercase = name .let { n -> n.toUpperCase() }

Ini mirip dengan metode map () dari kelas Opsional atau Stream - dalam hal ini, kita meneruskan fungsi yang mewakili tindakan yang mengubah String yang diberikan menjadi representasi huruf besar.

Variabel Nama ini dikenal sebagai penerima panggilan karena variabel bahwa metode ekstensi bertindak atas.

Ini berfungsi baik dengan operator panggilan aman:

val name = maybeGetName() val uppercase = name?.let { n -> n.toUpperCase() }

Dalam kasus ini, blok yang diteruskan ke let () hanya dievaluasi jika nama variabel bukan null . Ini berarti bahwa di dalam blok, nilai n dijamin bukan nol. Lebih lanjut tentang ini di sini.

Ada alternatif lain untuk let () yang dapat berguna juga, tergantung pada kebutuhan kita.

The run () ekstensi bekerja sama dengan membiarkan () , tapi penerima disediakan sebagai ini nilai dalam disebut blok:

val name = "Baeldung" val uppercase = name.run { toUpperCase() }

apply () berfungsi sama seperti run () , tetapi mengembalikan penerima alih-alih mengembalikan nilai dari blok yang disediakan .

Mari manfaatkan apply () ke panggilan terkait rantai:

val languages = mutableListOf() languages.apply { add("Java") add("Kotlin") add("Groovy") add("Python") }.apply { remove("Python") } 

Perhatikan bagaimana kode kita menjadi lebih ringkas dan ekspresif tanpa harus menggunakan ini atau itu secara eksplisit .

Ekstensi juga () bekerja seperti let () , tetapi mengembalikan penerima dengan cara yang sama seperti apply () :

val languages = mutableListOf() languages.also { list -> list.add("Java") list.add("Kotlin") list.add("Groovy") } 

The takeIf () ekstensi disediakan dengan predikat yang bekerja pada penerima, dan jika ini kembali predikat benar maka kembali penerima atau nol sebaliknya - ini bekerja mirip dengan kombinasi peta umum () dan filter () metode:

val language = getLanguageUsed() val coolLanguage = language.takeIf { l -> l == "Kotlin" } 

Ekstensi takeUnless () sama dengan takeIf () tetapi dengan logika predikat terbalik.

val language = getLanguageUsed() val oldLanguage = language.takeUnless { l -> l == "Kotlin" } 

2.2. Extension Methods for Collections

Kotlin adds a large number of extension methods to the standard Java Collections which can make our code easier to work with.

These methods are located inside _Collections.kt, _Ranges.kt, and _Sequences.kt, as well as _Arrays.kt for equivalent methods to apply to Arrays instead. (Remember that, in Kotlin, Arrays can be treated the same as Collections)

There're far too many of these extension methods to discuss here, so have a browse of these files to see what's available.

In addition to Collections, Kotlin adds a significant number of extension methods to the String class – defined in _Strings.kt. These allow us to treat Strings as if they were collections of characters.

All of these extension methods work together to allow us to write significantly cleaner, easier to maintain code regardless of the kind of collection we're working with.

3. Writing Our Extension Methods

So, what if we need to extend a class with a new functionality – either from the Java or Kotlin Standard Library or from a dependent library that we're using?

Extension methods are written as any other method, but the receiver class is provided as part of the function name, separated with the period.

For example:

fun String.escapeForXml() : String { .... }

This will define a new function called escapeForXml as an extension to the String class, allowing us to call it as described above.

Inside this function, we can access the receiver using this, the same as if we had written this inside the String class itself:

fun String.escapeForXml() : String { return this .replace("&", "&") .replace("<", "", ">") }

3.1. Writing Generic Extension Methods

What if we want to write an extension method that is meant to be applied to multiple types, generically? We could just extend the Any type, – which is the equivalent of the Object class in Java – but there is a better way.

Extension methods can be applied to a generic receiver as well as a concrete one:

fun  T.concatAsString(b: T) : String { return this.toString() + b.toString() }

This could be applied to any type that meets the generic requirements, and inside the function this value is typesafe.

For example, using the above example:

5.concatAsString(10) // compiles "5".concatAsString("10") // compiles 5.concatAsString("10") // doesn't compile

3.2. Writing Infix Extension Methods

Infix methods are useful for writing DSL-style code, as they allow for methods to be called without the period or brackets:

infix fun Number.toPowerOf(exponent: Number): Double { return Math.pow(this.toDouble(), exponent.toDouble()) }

We can now call this the same as any other infix method:

3 toPowerOf 2 // 9 9 toPowerOf 0.5 // 3

3.3. Writing Operator Extension Methods

We could also write an operator method as an extension.

Operator methods are ones that allow us to take advantage of the operator shorthand instead of the full method name – e.g., the plus operator method might be called using the + operator:

operator fun List.times(by: Int): List { return this.map { it * by } }

Again, this works the same as any other operator method:

listOf(1, 2, 3) * 4 // [4, 8, 12]

4. Calling Kotlin Extension Function from Java

Let's now see how Java operates with Kotlin extension functions.

In general, every extension method we define in Kotlin is available for us to use in Java. We should remember, though, that the infix method still needs to be called with dot and parentheses. Same with operator extensions — we can't use only the plus character (+). These facilities are only available in Kotlin.

However, we can't call some of the standard Kotlin library methods in Java, like let or apply, because they're marked with @InlineOnly.

4.1. Visibility of the Custom Extension Function in Java

Let's use one of the previously defined extension functions — String.escapeXml(). Our file containing the extension method is called StringUtil.kt.

Now, when we need to call an extension method from Java, we need to use a class name StringUtilKt. Note that we have to add the Kt suffix:

String xml = "hi"; String escapedXml = StringUtilKt.escapeForXml(xml); assertEquals("hi", escapedXml);

Please pay attention to the first escapeForXml parameter. This additional argument is an extension function receiver type. Kotlin with top-level extension function is a pure Java class with a static method. That's why it needs to somehow pass the original String.

And of course, just like in Java, we can use static import:

import static com.baeldung.kotlin.StringUtilKt.*;

4.2. Calling a Built-in Kotlin Extension Method

Kotlin helps us write code easier and faster by providing many built-in extensions functions. For example, there's the String.capitalize() method, which can be called directly from Java:

String name = "john"; String capitalizedName = StringsKt.capitalize(name); assertEquals("John", capitalizedName);

However, we can't call extension methods marked with @InlineOnly from Java, for example:

inline fun  T.let(block: (T) -> R): R

4.3. Renaming the Generated Java Static Class

We already know that a Kotlin extension function is a static Java method. Let's rename a generated Java class with an annotation @file:JvmName(name: String).

This has to be added at the top of the file:

@file:JvmName("Strings") package com.baeldung.kotlin fun String.escapeForXml() : String { return this .replace("&", "&") .replace("<", "", ">") }

Sekarang, ketika kita ingin memanggil metode ekstensi, kita hanya perlu menambahkan nama kelas Strings :

Strings.escapeForXml(xml);

Selain itu, kami masih dapat menambahkan impor statis:

import static com.baeldung.kotlin.Strings.*;

5. Ringkasan

Metode Ekstensi adalah alat yang berguna untuk memperluas tipe yang sudah ada di sistem - baik karena mereka tidak memiliki fungsionalitas yang kita butuhkan atau hanya untuk membuat beberapa area kode tertentu lebih mudah untuk dikelola.

Kami telah melihat di sini beberapa metode ekstensi yang siap digunakan dalam sistem. Selain itu, kami menjelajahi berbagai kemungkinan metode penyuluhan. Beberapa contoh dari fungsi ini dapat ditemukan di GitHub.