English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Generics no Kotlin

Genéricos, ou "tipos parametrizados", parametrizam tipos e podem ser usados em classes, interfaces e métodos.

Como o Java, o Kotlin também oferece genéricos, garantindo segurança de tipo e eliminando a necessidade de conversões de tipo forçadas.

Declarar uma classe genérica:

class Box<T>(t: T) {
    var value = t
}

Ao criar exemplos de classe, precisamos especificar os parâmetros de tipo:

val box: Box<Int> = Box<Int>(1)
// ou
val box = Box(1) // O compilador fará a inferência de tipo,1 O tipo é Int, então o compilador sabe que estamos falando de Box<Int>.

A seguir, um exemplo de como passar dados de tipo inteiro e string para a classe genérica Box:

class Box<T>(t: T) {
    var value = t
}
fun main(args: Array<String>) {
    var boxInt = Box<Int>(10)
    var boxString = Box<String>("w3codebox")
    println(boxInt.value)
    println(boxString.value)
}

O resultado da saída é:

10
w3codebox

Para definir uma variável de tipo genérica, você pode escrever completamente os parâmetros de tipo, e se o compilador puder inferir automaticamente os parâmetros de tipo, eles também podem ser omitidos.

A declaração de função genérica do Kotlin é a mesma que a do Java, os parâmetros de tipo devem ser colocados antes do nome da função:

fun <T> boxIn(value: T) = Box(value)
// Os seguintes são exemplos válidos
val box4 = boxIn<Int>(1)
val box5 = boxIn(1)     // O compilador fará a inferência de tipo

Ao chamar uma função genérica, se o tipo do parâmetro de tipo puder ser inferido, o parâmetro de tipo genérico pode ser omitido.

A seguir, um exemplo de função genérica doPrintln, que trata diferentes tipos de entrada de acordo com a necessidade:

fun main(args: Array<String>) {
    val age = 23
    val name = "w3codebox"
    val bool = true
    doPrintln(age)    // Integer
    doPrintln(name)   // String
    doPrintln(bool)   // Boolean
}
fun <T> doPrintln(content: T) {
    when (content) {
        is Int -> println("Número inteiro é ${content}")
        is String -> println("Converter string para maiúsculas: ${content.toUpperCase()}")
        else -> println("T não é inteiro, nem string")
    }
}

O resultado da saída é:

Número inteiro é 23
Converter string para maiúsculas: w3codebox
T não é inteiro, nem string

Restrição genérica

Podemos usar restrições genéricas para definir os tipos permitidos para um parâmetro dado.

O Kotlin usa : para restringir o limite superior de tipos genéricos.

A restrição mais comum é o limite superior (upper bound):

fun <T : Comparable<T>> sort(list: List<T>) {
    // ……
}

Os subtipos de Comparable podem substituir T. Por exemplo:

sort(listOf(1, 2, 3)) // OK. Int é subtipo de Comparable<Int>
sort(listOf(HashMap<Int, String>())) // Erro: HashMap<Int, String> não é subtipo de Comparable<HashMap<Int, String>>

O limite superior padrão é Any?.

Para múltiplas condições de limite superior, pode-se usar a cláusula where:

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
    where T : CharSequence,
          T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }
}

variação

O Kotlin não tem tipos de coringa, ele tem outras duas coisas: variação de tipo na declaração (declaration-site variação) e projeções de tipo (type projections).

Variação de tipo na declaração

A variação de tipo na declaração é anotada com o modificador de anotação covariante: in, out, consumidor in, produtor out.

Usar out para tornar um parâmetro de tipo covariante, os parâmetros de tipo covariante podem ser usados apenas como saída, podem ser usados como tipo de retorno mas não podem ser usados como tipo de entrada:

// definir uma classe que suporta covariância
class w3codebox<out A>(val a: A) {
    fun foo(): A {
        return a
    }
}
fun main(args: Array<String>) {
    var strCo: w3codebox<String> = w3codebox("a")
    var anyCo: w3codebox<Any> = w3codebox<Any>("b")
    anyCo = strCo
    println(anyCo.foo())   // Saída a
}

in faz um parâmetro de tipo contravariante, os parâmetros de tipo contravariantes podem ser usados apenas como entrada, podem ser usados como tipos de entrada mas não podem ser usados como tipos de saída:

// Definir uma classe que suporte covariância
class w3codebox<in A>(a: A) {
    fun foo(a: A) {
    }
}
fun main(args: Array<String>) {
    var strDCo = w3codebox("a")
    var anyDCo = w3codebox<Any>("b")
    strDCo = anyDCo
}

Projeção de estrela

Algumas vezes, você pode querer expressar que não sabe nenhuma informação sobre os parâmetros de tipo, mas ainda assim deseja usá-lo de forma segura. Aqui, "usar de forma segura" significa definir uma projeção de tipo para um tipo genérico, exigindo que todos os exemplos de entidade desse tipo sejam subtipos dessa projeção.

Para esse problema, o Kotlin oferece uma sintaxe chamada projeção de estrela (star-projeção):

  • Se o tipo for definido como Foo<out T>, onde T é um parâmetro de tipo covariante, com limite superior (upper bound) TUpper, Foo<> é equivalente a Foo<out TUpper>. Isso significa que, quando T é desconhecido, você pode usar de forma segura Foo<> lê valores do tipo TUpper.

  • Se o tipo for definido como Foo<in T>, onde T é um parâmetro de tipo contravariante, Foo<> é equivalente a Foo<inNothing>. Isso significa que, quando T é desconhecido, você não pode usar de forma segura Foo<> escreve qualquer coisa.

  • Se o tipo for definido como Foo<T>, onde T é um parâmetro de tipo covariante, com limite superior (upper bound) TUpper, para ocasiões de leitura de valores, Foo<*> é equivalente a Foo<out TUpper>, para ocasiões de escrita de valores, é equivalente a Foo<in Nothing>.

Se um tipo genérico contiver múltiplos parâmetros de tipo, cada parâmetro de tipo pode ser projetado separadamente. Por exemplo, se o tipo for definido como interface Function<in T, out U>, então podem surgir os seguintes tipos de projeção de estrela:

  • Function<*, String> , representa Function<in Nothing, String> ;

  • Function<Int, *>, representa Function<Int, out Any?> ;

  • Function<, >, representa Function<in Nothing, out Any?> .

Atenção: O projeção de estrela com Java é muito semelhante ao tipo nativo (raw type), mas pode ser usado de forma segura