Kotlin
Методы расширения
Поиск…
Синтаксис
- fun ТипName.extensionName (params, ...) {/ * body * /} // Объявление
- fun <T: Any> TypeNameWithGenerics <T> .extensionName (params, ...) {/ * body * /} // Объявление с помощью Generics
- myObj.extensionName (args, ...) // вызов
замечания
Расширения разрешаются статически . Это означает, что используемый метод расширения определяется ссылочным типом переменной, к которой вы обращаетесь; неважно, какой тип переменной находится во время выполнения, всегда будет вызываться тот же метод расширения. Это связано с тем, что объявление метода расширения фактически не добавляет член к типу приемника .
Расширения верхнего уровня
Методы расширения верхнего уровня не содержатся в классе.
fun IntArray.addTo(dest: IntArray) {
for (i in 0 .. size - 1) {
dest[i] += this[i]
}
}
Выше метода расширения определен для типа IntArray
. Обратите внимание, что объект, для которого определен метод расширения (называемый получателем ), доступен с использованием ключевого слова this
.
Это расширение можно вызвать так:
val myArray = intArrayOf(1, 2, 3)
intArrayOf(4, 5, 6).addTo(myArray)
Потенциальная Pitfall: расширения разрешаются статически
Вызываемый метод расширения определяется во время компиляции на основе ссылочного типа доступной переменной. Неважно, какой тип переменной находится во время выполнения, всегда будет вызываться тот же метод расширения.
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())
В приведенном выше примере будет напечатан "Defined for Super"
, потому что объявленный тип переменной myVar
- Super
.
Образец, простирающийся долго, чтобы отобразить читаемую пользователем строку
Для любого значения типа Int
или Long
для визуализации строки, читаемой человеком:
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()
}
Затем легко использовать как:
println(1999549L.humanReadable())
println(someInt.humanReadable())
Пример расширения Java 7+ Path class
Общим вариантом использования методов расширения является улучшение существующего API. Вот примеры добавления exist
, notExists
и deleteRecursively
к классу Java 7+ Path
:
fun Path.exists(): Boolean = Files.exists(this)
fun Path.notExists(): Boolean = !this.exists()
fun Path.deleteRecursively(): Boolean = this.toFile().deleteRecursively()
Который теперь можно вызвать в этом примере:
val dir = Paths.get(dirName)
if (dir.exists()) dir.deleteRecursively()
Использование функций расширения для повышения удобочитаемости
В Котлине вы можете написать код вроде:
val x: Path = Paths.get("dirName").apply {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Но использование apply
не совсем ясно в отношении ваших намерений. Иногда более понятно создать аналогичную функцию расширения, чтобы фактически переименовать действие и сделать его более очевидным. Это не должно выходить из-под контроля, но для очень распространенных действий, таких как проверка:
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
}
Теперь вы можете написать код как:
val x: Path = Paths.get("dirName") verifiedWith {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Что теперь даст людям знать, чего ожидать в пределах параметра лямбда.
Обратите внимание, что параметр типа T
для verifiedBy
такой же, как T: Any?
что даже типы с нулевым значением смогут использовать эту версию расширения. Хотя verifiedWith
требует не-nullable.
Пример расширения Java 8 Временные классы для отображения строки в формате ISO
С этим заявлением:
fun Temporal.toIsoString(): String = DateTimeFormatter.ISO_INSTANT.format(this)
Теперь вы можете просто:
val dateAsString = someInstant.toIsoString()
Функции расширения для объектов-компаньонов (появление статических функций)
Если вы хотите расширить класс так: если вы статическая функция, например, для класса Something
добавьте статическую функцию fromString
, это может работать, только если класс имеет сопутствующий объект и что функция расширения объявлена на сопутствующем объекте :
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
}
Ложное расширение
Предположим, вы хотите создать свойство расширения, которое дорого вычислить. Таким образом, вы хотели бы кэшировать вычисление, используя делегат lazy property и ссылаться на текущий экземпляр ( this
), но вы не можете этого сделать, как объяснено в Kotlin выпусках KT-9686 и KT-13053 . Тем не менее, существует официальное обходное решение, представленное здесь .
В этом примере свойство расширения является color
. Он использует явный colorCache
который может быть использован с this
поскольку нет lazy
:
class KColor(val value: Int)
private val colorCache = mutableMapOf<KColor, Color>()
val KColor.color: Color
get() = colorCache.getOrPut(this) { Color(value, true) }
Расширения для упрощения ссылки Вид из кода
Вы можете использовать расширения для ссылочного вида, без шаблонов после создания представлений.
Оригинальная идея - библиотека Anko
расширения
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
использование
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) }