Métodos de extensão no Kotlin
1. Introdução
Kotlin introduz o conceito de Métodos de Extensão - que sãoa handy way of extending existing classes with a new functionality without using inheritance or any forms of the Decorator pattern - após definir uma extensão. podemos essencialmente usá-lo - como fazia parte da API original.
Isso pode ser muito útil para tornar nosso código fácil de ler e manter, pois podemos adicionar métodos que são específicos às nossas necessidades e fazer com que pareçam fazer parte do código original, mesmo quando não temos acesso a as fontes.
Por exemplo, podemos precisar executar o escape XML em umString.. No código Java padrão, precisaríamos escrever um método que possa fazer isso e chamá-lo:
String escaped = escapeStringForXml(input);
Enquanto escrito em Kotlin, o trecho poderia ser substituído por:
val escaped = input.escapeForXml()
Além de ser mais fácil de ler, os IDEs serão capazes de oferecer o método como uma opção de preenchimento automático da mesma forma que se fosse um método padrão na classeString.
2. Métodos de extensão de biblioteca padrão
A Biblioteca Padrão Kotlin vem com alguns métodos de extensão prontos para uso.
2.1. Métodos de extensão de ajuste de contexto
Some generic extensions exist and can be applied to all types in our application. Eles podem ser usados para garantir que o código seja executado em um contexto apropriado e, em alguns casos, para garantir que uma variável não seja nula.
Acontece que, provavelmente, estamos aproveitando as extensões sem perceber.
Um dos mais populares é possivelmente o métodolet(), que pode ser chamado em qualquer tipo no Kotlin - vamos passar uma função para ele que será executada no valor inicial:
val name = "example"
val uppercase = name
.let { n -> n.toUpperCase() }
É semelhante ao métodomap() das classesOptional ouStream - neste caso, passamos uma função que representa uma ação que converte um determinadoString em sua representação em maiúsculas .
The variable name is known as the receiver of the call porque é a variável sobre a qual o método de extensão atua.
Isso funciona muito bem com o operador de chamada segura:
val name = maybeGetName()
val uppercase = name?.let { n -> n.toUpperCase() }
In this case, the block passed to let() is only evaluated if the variable name was non-null. Isso significa que, dentro do bloco, o valorn é garantido como não nulo. Mais sobre estehere.
Existem outras alternativas paralet() que também podem ser úteis, dependendo de nossas necessidades.
A extensãorun() funciona da mesma forma quelet(), mas um receptor é fornecido como o valorthis dentro do bloco chamado:
val name = "example"
val uppercase = name.run { toUpperCase() }
apply() works the same as run(), but it returns a receiver instead of returning the value from the provided block.
Vamos aproveitarapply() para chamadas relacionadas à cadeia:
val languages = mutableListOf()
languages.apply {
add("Java")
add("Kotlin")
add("Groovy")
add("Python")
}.apply {
remove("Python")
}
Observe como nosso código se torna mais conciso e expressivo, não tendo que usar explicitamentethis ouit.
A extensãoThe also() funciona exatamente comolet(), mas retorna o receptor da mesma maneira queapply():
val languages = mutableListOf()
languages.also { list ->
list.add("Java")
list.add("Kotlin")
list.add("Groovy")
}
The takeIf() extension is provided with a predicate acting on the receiver, and if this predicate returns true then it returns the receiver ounull caso contrário - funciona de forma semelhante a uma combinação de métodos()efilter() de um mapa comum:
val language = getLanguageUsed()
val coolLanguage = language.takeIf { l -> l == "Kotlin" }
A extensãoThe takeUnless() é igual atakeIf(), mas com a lógica de predicado invertida.
val language = getLanguageUsed()
val oldLanguage = language.takeUnless { l -> l == "Kotlin" }
2.2. Métodos de extensão para coleções
Kotlin adds a large number of extension methods to the standard Java Collections which can make our code easier to work with.
Esses métodos estão localizados emCollections.kt,_ Ranges.kt_ eSequences.kt_, as well as Arrays.kt_ para métodos equivalentes a serem aplicados aArrays. (Lembre-se de que, em Kotlin,Arrays pode ser tratado da mesma forma queCollections)
Existem muitos desses métodos de extensão para discutir aqui, então dê uma olhada nesses arquivos para ver o que está disponível.
In addition to Collections, Kotlin adds a significant number of extension methods to the String class - definido emStrings.kt_. These allow us to treat Strings as if they were collections of characters .__
Todos esses métodos de extensão trabalham juntos para nos permitir escrever código significativamente mais limpo e mais fácil de manter, independentemente do tipo de coleção com a qual estamos trabalhando.
3. Escrevendo nossos métodos de extensão
Então, e se precisarmos estender uma classe com uma nova funcionalidade - seja da Biblioteca Java ou Kotlin Standard ou de uma biblioteca dependente que estamos usando?
Extension methods are written as any other method, mas a classe receptora é fornecida como parte do nome da função, separada pelo ponto.
Por exemplo:
fun String.escapeForXml() : String {
....
}
Isso definirá uma nova função chamadaescapeForXml como uma extensão da classeString, permitindo-nos chamá-la conforme descrito acima.
Dentro desta função, podemos acessar o receptor usandothis, o mesmo como se tivéssemos escrito isso dentro da própria classeString:
fun String.escapeForXml() : String {
return this
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
}
3.1. Escrevendo Métodos de Extensão Genéricos
E se quisermos escrever um método de extensão que deve ser aplicado a vários tipos, genericamente? Poderíamos apenas estender o tipoAny, - que é o equivalente da classeObject em Java - mas existe uma maneira melhor.
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()
}
Isso pode ser aplicado a qualquer tipo que atenda aos requisitos genéricos e, dentro da funçãothis, o valor é seguro para tipos.
Por exemplo, usando o exemplo acima:
5.concatAsString(10) // compiles
"5".concatAsString("10") // compiles
5.concatAsString("10") // doesn't compile
3.2. Escrevendo Métodos de Extensão Infix
Os métodos infix são úteis para escrever código no estilo DSL, pois permitem que os métodos sejam chamados sem o ponto ou colchetes:
infix fun Number.toPowerOf(exponent: Number): Double {
return Math.pow(this.toDouble(), exponent.toDouble())
}
Agora podemos chamar isso da mesma forma que qualquer outro método de infix:
3 toPowerOf 2 // 9
9 toPowerOf 0.5 // 3
3.3. Escrevendo métodos de extensão de operador
Também poderíamos escrever um método de operador como uma extensão.
Operator methods are ones that allow us to take advantage of the operator shorthand instead of the full method name - por exemplo, o método do operadorplus pode ser chamado usando o operador+:
operator fun List.times(by: Int): List {
return this.map { it * by }
}
Novamente, isso funciona da mesma forma que qualquer outro método de operador:
listOf(1, 2, 3) * 4 // [4, 8, 12]
4. Chamando a função de extensão Kotlin de Java
Vamos agora ver como o Java opera com funções de extensão Kotlin.
In general, every extension method we define in Kotlin is available for us to use in Java. Devemos lembrar, porém, que o métodoinfix ainda precisa ser chamado com ponto e parênteses. O mesmo com as extensões da operadora - não podemos usar apenas o caractere mais (+). Essas facilidades estão disponíveis apenas emKotlin.
No entanto, não podemos chamar alguns dos métodos da biblioteca Kotlin padrão em Java, comolet ouapply, porque eles são marcados com@InlineOnly.
4.1. Visibilidade da função de extensão personalizada em Java
Vamos usar uma das funções de extensão definidas anteriormente -String.escapeXml(). Nosso arquivo contendo o método de extensão é chamadoStringUtil.kt.
Agora, quando precisamos chamar um método de extensão de Java, precisamos usar um nome de classeStringUtilKt. Note that we have to add the Kt suffix:
Preste atenção ao primeiro parâmetroescapeForXml. Este argumento adicional é um tipo de receptor de função de extensão. Kotlin with top-level extension function is a pure Java class with a static method. É por isso que precisa de alguma forma passar oString. original
E, claro, assim como emJava,, podemos usar importação estática:
import static com.example.kotlin.StringUtilKt.*;
4.2. Chamando um método de extensão Kotlin embutido
Kotlin nos ajuda a escrever código de maneira mais fácil e rápida, fornecendo muitas funções de extensão integradas. Por exemplo, existe o métodoString.capitalize(), que pode ser chamado diretamente deJava:
String name = "john";
String capitalizedName = StringsKt.capitalize(name);
assertEquals("John", capitalizedName);
No entanto,we can’t call extension methods marked with @InlineOnly from Java, for example:
inline fun T.let(block: (T) -> R): R
4.3. Renomeando a classe estática Java gerada
Já sabemos que a função de sextensãoKotlin é um métodoJavaestático. Vamos renomear uma classeJava gerada com uma anotação@file:JvmName(name: String).
Isso deve ser adicionado na parte superior do arquivo:
@file:JvmName("Strings")
package com.example.kotlin
fun String.escapeForXml() : String {
return this
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
}
Agora, quando queremos chamar um método de extensão, simplesmente precisamos adicionar o nome da classeStrings:
Strings.escapeForXml(xml);
Além disso, ainda podemos adicionar uma importação estática:
import static com.example.kotlin.Strings.*;
5. Sumário
Métodos de extensão são ferramentas úteis para estender os tipos que já existem no sistema - seja porque eles não têm a funcionalidade que precisamos ou simplesmente para tornar alguma área específica do código mais fácil de gerenciar.
Vimos aqui alguns métodos de extensão que estão prontos para uso no sistema. Além disso, exploramos várias possibilidades de métodos de extensão. Alguns exemplos desta funcionalidade podem ser encontradosover on GitHub.