Clases genéricas
Parte de la sección Programación orientada a objetos del Journey de Kotlin de Coddy. Lección 38 de 57.
Una clase también puede tener parámetros de tipo. Box<T> contiene un valor de cualquier tipo, y cada instancia fija el tipo: un Box<Int> solo acepta valores Int:
class Box<T>(var value: T) {
fun describe() = "box with $value"
}Dentro de main:
val numberBox = Box(42)
val textBox = Box("hello")
numberBox.value += 1
println(numberBox.describe())
println(textBox.describe())
// numberBox.value = "x" // error: un String no es un IntSalida:
box with 43
box with helloLos contenedores son las clases genéricas habituales. Una implementación de Stack<T> funciona para cualquier tipo de elemento, y el compilador comprueba qué entra y qué sale:
class Stack<T> {
private val items = mutableListOf<T>()
val size: Int
get() = items.size
fun push(item: T) { items.add(item) }
fun pop(): T? = items.removeLastOrNull()
fun peek(): T? = items.lastOrNull()
}Dentro de main:
val stack = Stack<String>()
stack.push("a")
stack.push("b")
println(stack.pop())
println(stack.peek())
println(stack.size)Salida:
b
a
1El argumento de tipo se infiere a partir de los argumentos del constructor cuando los hay, como en Box(42). De lo contrario, escríbelo: Stack<String>(). Las instancias con argumentos de tipo diferentes son tipos diferentes, por lo que un Stack<String> nunca se confunde con un Stack<Int>. Las funciones de una clase genérica también pueden añadir sus propios parámetros de tipo:
class Box<T>(val value: T) {
fun <R> map(f: (T) -> R): Box<R> = Box(f(value))
}Dentro de main:
val length = Box("kotlin").map { it.length }
println(length.value + 1)Salida:
7Las interfaces genéricas siguen las mismas reglas. Una clase puede implementar una de forma genérica o para un argumento de tipo concreto:
interface Repository<T> {
fun add(item: T)
fun all(): List<T>
}
class MemoryRepository<T> : Repository<T> {
private val items = mutableListOf<T>()
override fun add(item: T) { items.add(item) }
override fun all() = items.toList()
}
class TagRepository : Repository<String> {
private val tags = sortedSetOf<String>()
override fun add(item: String) { tags.add(item.lowercase()) }
override fun all() = tags.toList()
}Dentro de main:
val ids = MemoryRepository<Int>()
ids.add(3); ids.add(1)
val tags = TagRepository()
tags.add("Kotlin"); tags.add("JVM")
println("${ids.all()} ${tags.all()}")Salida:
[3, 1] [jvm, kotlin]Desafío
FácilEscribe la clase genérica History<T>(limit) que recuerda los últimos limit elementos. add(item) añade un elemento y elimina el más antiguo cuando hay más de limit. latest es el elemento más reciente o null, y all() devuelve los elementos del más antiguo al más reciente como una lista de solo lectura. Después, escribe la extensión History<Int>.average(), que existe únicamente para historiales de números y devuelve el promedio entero de los números conservados (0 cuando está vacío).
El código proporcionado lee el límite, una línea de palabras y una línea de números. Rellena un History<String> y un History<Int>, e imprime las palabras conservadas, la palabra más reciente, los números conservados, su promedio y empty latest: null.
Tu código debe estar en History.kt. Main.kt contiene el código proporcionado de entrada/salida y no se puede editar.
Pruébalo tú mismo
fun main() {
// Código de entrada/salida suministrado: déjalo como está
val input = generateSequence(::readLine).toList()
val limit = input[0].toInt()
val words = History<String>(limit)
for (w in input[1].split(" ")) words.add(w)
val numbers = History<Int>(limit)
for (n in input[2].split(" ")) numbers.add(n.toInt())
println("words: ${words.all()}")
println("latest word: ${words.latest}")
println("numbers: ${numbers.all()}")
println("average: ${numbers.average()}")
println("empty latest: ${History<Char>(3).latest}")
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación orientada a objetos
1Clases y objetos
Trabajar con archivosDefinir clasesConstructores primariosBloques init y validaciónConstructores secundariosRepaso - Tarjeta de biblioteca4Herencia
Clases abiertas y subclasesSobrescritura de miembrossuper y constructoresClases abstractasRepaso: directorio del personal2Propiedades y encapsulación
Accesores personalizadosModificadores de visibilidadSetters privadoslazy y lateinitRepaso: cuenta bancaria5Interfaces
Declaración de interfacesMétodos predeterminadosInterfaces múltiplesInterfaces como tiposRepaso: métodos de pago8Genéricos
Funciones genéricasClases genéricasRestricciones de tiposVariancia con in y outRepaso - Cola de prioridad11Proyecto: Gestión de biblioteca
Libros y miembrosPréstamo de librosPractica por tu cuenta: Playground de Kotlin