Szukaj…


Składnia

  • fun TypeName.extensionName (parametry, ...) {/ * body * /} // Deklaracja
  • fun <T: Any> TypeNameWithGenerics <T> .extensionName (parametry, ...) {/ * body * /} // Deklaracja z danymi rodzajowymi
  • myObj.extensionName (argumenty, ...) // wywołanie

Uwagi

Rozszerzenia są rozwiązywane statycznie . Oznacza to, że zastosowana metoda rozszerzenia zależy od typu odniesienia zmiennej, do której uzyskujesz dostęp; nie ma znaczenia, jaki typ zmiennej jest w czasie wykonywania, zawsze będzie wywoływana ta sama metoda rozszerzenia. Wynika to z faktu, że zadeklarowanie metody rozszerzenia tak naprawdę nie dodaje członka do typu odbiornika .

Rozszerzenia najwyższego poziomu

Metody rozszerzenia najwyższego poziomu nie są zawarte w klasie.

fun IntArray.addTo(dest: IntArray) {
    for (i in 0 .. size - 1) {
        dest[i] += this[i]
    }
}

Powyżej metody rozszerzenia zdefiniowano dla typu IntArray . Zauważ, że do obiektu, dla którego zdefiniowano metodę rozszerzenia (zwanego odbiornikiem ), można uzyskać dostęp za pomocą słowa kluczowego this .

To rozszerzenie można nazwać tak:

val myArray = intArrayOf(1, 2, 3)
intArrayOf(4, 5, 6).addTo(myArray)

Potencjalna pułapka: rozszerzenia są rozwiązywane statycznie

Metoda rozszerzenia, która ma zostać wywołana, jest określana w czasie kompilacji na podstawie typu referencyjnego zmiennej, do której uzyskiwany jest dostęp. Nie ma znaczenia, jaki typ zmiennej jest w czasie wykonywania, zawsze będzie wywoływana ta sama metoda rozszerzenia.

open class Super

class Sub : Super()

fun Super.myExtension() = "Defined for Super"

fun Sub.myExtension() = "Defined for Sub"

fun callMyExtension(myVar: Super) {
    println(myVar.myExtension())
}

callMyExtension(Sub())

Powyższy przykład wypisze "Defined for Super" , ponieważ zadeklarowany typ zmiennej myVar to Super .

Próbka rozciągająca się długo, aby renderować ciąg czytelny dla człowieka

Biorąc pod uwagę dowolną wartość typu Int lub Long aby wyświetlić ciąg czytelny dla człowieka:

fun Long.humanReadable(): String {
    if (this <= 0) return "0"
    val units = arrayOf("B", "KB", "MB", "GB", "TB", "EB")
    val digitGroups = (Math.log10(this.toDouble())/Math.log10(1024.0)).toInt();
    return DecimalFormat("#,##0.#").format(this/Math.pow(1024.0, digitGroups.toDouble())) + " " + units[digitGroups];
}

fun Int.humanReadable(): String {
    return this.toLong().humanReadable()
}

Następnie łatwo używać jako:

println(1999549L.humanReadable())
println(someInt.humanReadable())

Przykład rozszerzenia klasy Java 7+ Path

Typowym przypadkiem użycia metod rozszerzeń jest poprawa istniejącego interfejsu API. Oto przykłady dodawania exist , notExists i deleteRecursively do klasy Java 7+ Path :

fun Path.exists(): Boolean = Files.exists(this)
fun Path.notExists(): Boolean = !this.exists()
fun Path.deleteRecursively(): Boolean = this.toFile().deleteRecursively()

Które można teraz wywołać w tym przykładzie:

val dir = Paths.get(dirName)
if (dir.exists()) dir.deleteRecursively()

Korzystanie z funkcji rozszerzeń w celu poprawy czytelności

W Kotlin możesz napisać kod taki jak:

val x: Path = Paths.get("dirName").apply { 
    if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}

Ale zastosowanie „ apply nie jest tak jasne, jak w przypadku twoich intencji. Czasami łatwiej jest stworzyć podobną funkcję rozszerzenia, aby w efekcie zmienić nazwę akcji i uczynić ją bardziej oczywistą. Nie powinno to pozwolić wymknąć się spod kontroli, ale w przypadku bardzo częstych działań, takich jak weryfikacja:

infix inline fun <T> T.verifiedBy(verifyWith: (T) -> Unit): T {
    verifyWith(this)
    return this
}

infix inline fun <T: Any> T.verifiedWith(verifyWith: T.() -> Unit): T {
    this.verifyWith()
    return this
}

Możesz teraz napisać kod jako:

val x: Path = Paths.get("dirName") verifiedWith {
    if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}

Który teraz pozwala ludziom wiedzieć, czego się spodziewać w ramach parametru lambda.

Zauważ, że parametr typu T dla verifiedBy jest taki sam jak T: Any? co oznacza, że nawet typy zerowalne będą mogły korzystać z tej wersji rozszerzenia. Chociaż verifiedWith wymaga wartości zerowej.

Przykład rozszerzenia klas Java 8 Temporal w celu renderowania łańcucha w formacie ISO

Dzięki tej deklaracji:

fun Temporal.toIsoString(): String = DateTimeFormatter.ISO_INSTANT.format(this)

Możesz teraz po prostu:

val dateAsString = someInstant.toIsoString()

Funkcje rozszerzające do obiektów towarzyszących (wygląd funkcji statycznych)

Jeśli chcesz rozszerzyć klasę tak, jakbyś był funkcją statyczną, na przykład dla klasy Something dodaj funkcję statycznie wyglądającą fromString , może to działać tylko wtedy, gdy klasa ma obiekt towarzyszący, a funkcja rozszerzenia została zadeklarowana na obiekcie towarzyszącym :

class Something {
    companion object {}
}

class SomethingElse {
}

fun Something.Companion.fromString(s: String): Something = ... 

fun SomethingElse.fromString(s: String): SomethingElse = ... 

fun main(args: Array<String>) {
    Something.fromString("") //valid as extension function declared upon the
                             //companion object

    SomethingElse().fromString("") //valid, function invoked on instance not
                                   //statically

    SomethingElse.fromString("") //invalid
}

Obejście właściwości leniwego rozszerzenia

Załóżmy, że chcesz utworzyć właściwość rozszerzenia, której obliczenie jest kosztowne. Dlatego chcesz buforować obliczenia, używając leniwego delegata właściwości i odwołać się do bieżącej instancji ( this ), ale nie możesz tego zrobić, jak wyjaśniono w numerach Kotlin KT-9686 i KT-13053 . Jednak nie jest oficjalnym obejście umieszczono tutaj .

W tym przykładzie właściwością rozszerzenia jest color . Używa jawnego colorCache którego można używać z this ponieważ nie jest konieczne lazy :

class KColor(val value: Int)

private val colorCache = mutableMapOf<KColor, Color>()

val KColor.color: Color
    get() = colorCache.getOrPut(this) { Color(value, true) }

Rozszerzenia dla łatwiejszego przeglądania Widok z kodu

Możesz użyć rozszerzeń dla odniesienia Zobacz, nie więcej płyty kotłowej po utworzeniu widoków.

Oryginalny pomysł autorstwa Anko Library

Rozszerzenia

inline fun <reified T : View> View.find(id: Int): T = findViewById(id) as T
inline fun <reified T : View> Activity.find(id: Int): T = findViewById(id) as T
inline fun <reified T : View> Fragment.find(id: Int): T = view?.findViewById(id) as T
inline fun <reified T : View> RecyclerView.ViewHolder.find(id: Int): T = itemView?.findViewById(id) as T

inline fun <reified T : View> View.findOptional(id: Int): T? = findViewById(id) as? T
inline fun <reified T : View> Activity.findOptional(id: Int): T? = findViewById(id) as? T
inline fun <reified T : View> Fragment.findOptional(id: Int): T? = view?.findViewById(id) as? T
inline fun <reified T : View> RecyclerView.ViewHolder.findOptional(id: Int): T? = itemView?.findViewById(id) as? T

Stosowanie

val yourButton by lazy { find<Button>(R.id.yourButtonId) }
val yourText by lazy { find<TextView>(R.id.yourTextId) }
val yourEdittextOptional by lazy { findOptional<EditText>(R.id.yourOptionEdittextId) }


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow