Los genéricos son las características poderosas que nos permiten definir clases, métodos y propiedades a las que se puede acceder utilizando diferentes tipos de datos mientras se controla la seguridad del tipo en tiempo de compilación .
Creación de clases parametrizadas:
un tipo genérico es una clase o método que se parametriza sobre tipos. Siempre usamos corchetes angulares() para especificar el parámetro de tipo en el programa.
La clase genérica se define de la siguiente manera:
class MyClass<T>(text: T) { var name = text }
Para crear una instancia de dicha clase, debemos proporcionar los argumentos de tipo:
val my : MyClass<String> = Myclass<String>("GeeksforGeeks")
Si los parámetros se pueden deducir de los argumentos de constructor, se permite omitir los argumentos de tipo:
val my = MyClass("GeeksforGeeks")
Aquí, GeeksforGeeks tiene el tipo String, por lo que el compilador se da cuenta de que estamos hablando de Myclass<String>
Ventajas de los genéricos –
- La conversión de tipos es evitable: no es necesario encasillar el objeto.
- Tipo de seguridad : Genérico permite solo un tipo de objeto a la vez.
- Seguridad en el tiempo de compilación : el código genérico se verifica en el tiempo de compilación para el tipo parametrizado para evitar errores en el tiempo de ejecución.
Uso genérico en nuestro programa-
En el siguiente ejemplo, creamos una clase de empresa con un constructor principal que tiene un solo parámetro. Ahora, tratamos de pasar los diferentes tipos de datos en el objeto de la clase Company como String y Integer. El constructor principal de la clase Company acepta el tipo de string ( «GeeskforGeeks» ) pero da un error de tiempo de compilación cuando pasa el tipo Integer ( 12 ).
Programa Kotlin sin clase genérica-
class Company (text: String) { var x = text init{ println(x) } } fun main(args: Array<String>){ var name: Company = Company("GeeksforGeeks") var rank: Company = Company(12)// compile time error }
Producción:
Error:(10, 33) Kotlin: The integer literal does not conform to the expected type String
Para resolver el problema anterior, podemos crear una clase de tipo genérico definida por el usuario que acepte diferentes tipos de parámetros en una sola clase. La clase Company of type es una clase de tipo general que acepta los tipos de parámetros Int y String.
programa Kotlin usando clase genérica-
class Company<T> (text : T){ var x = text init{ println(x) } } fun main(args: Array<String>){ var name: Company<String> = Company<String>("GeeksforGeeks") var rank: Company<Int> = Company<Int>(12) }
Producción:
GeeksforGeeks 1234
Varianza –
A diferencia de Java, Kotlin hace que las arrays sean invariables de forma predeterminada. Por extensión, los tipos genéricos son invariantes en Kotlin. Esto puede ser administrado por las palabras clave out y in . La invariancia es la propiedad por la cual una función/clase genérica estándar ya definida para un tipo de datos en particular, no puede aceptar o devolver otro tipo de datos. Any es el supertipo de todos los demás tipos de datos.
La varianza es de dos tipos:
- Variación del sitio de declaración (usando entrada y salida)
- Variación del sitio de uso: Tipo de proyección
Kotlin fuera y dentro Palabras clave –
La out
palabra clave –
En Kotlin, podemos usar la out
palabra clave en el tipo genérico, lo que significa que podemos asignar esta referencia a cualquiera de sus supertipos. El out
valor solo puede ser producido por la clase dada pero no puede ser consumido:
class OutClass<out T>(val value: T) { fun get(): T { return value } }
Arriba, hemos definido una OutClass
clase que puede producir un valor de tipo T. Luego, podemos asignar una instancia de OutClass a la referencia que es un supertipo de ella:
val out = OutClass("string") val ref: OutClass<Any> = out
Nota: si no hemos usado el out
tipo en la clase anterior, la declaración dada producirá un error de compilación.
La in
palabra clave –
Si queremos asignarlo a la referencia de su subtipo, podemos usar la in
palabra clave en el tipo genérico. La in
palabra clave solo se puede usar en el tipo de parámetro que se consume, no se produce:
class InClass<in T> { fun toString(value: T): String { return value.toString() } }
Aquí, hemos declarado un método toString() que solo consume un valor de tipo T. Luego, podemos asignar una referencia de tipo Número a la referencia de su subtipo – Int:
val inClassObject: InClass<Number> = InClass() val ref<Int> = inClassObject
Nota: si no hemos usado el tipo in en la clase anterior, la declaración dada producirá un error de compilación.
Covarianza –
La covarianza implica que la sustitución de subtipos es aceptable, pero los supertipos no, es decir, la función/clase genérica puede aceptar subtipos del tipo de datos para el que ya está definida, por ejemplo, una clase genérica definida para Número puede aceptar Int, pero una clase genérica definida para Int no puede aceptar Número. Esto se puede implementar en Kotlin usando la out
palabra clave de la siguiente manera:
fun main(args: Array<String>) { val x: MyClass<Any> = MyClass<Int>() // Error: Type mismatch val y: MyClass<out Any> = MyClass<String>() // Works since String is a subtype of Any val z: MyClass<out String> = MyClass<Any>() // Error since Any is a supertype of String } class MyClass<T>
Podemos permitir directamente la covarianza agregando una palabra clave al sitio de declaración. El siguiente código funciona bien.
fun main(args: Array<String>) { val y: MyClass<Any> = MyClass<String>() // Compiles without error } class MyClass<out T>
Contracovarianza –
Se utiliza para sustituir un valor de supertipo en los subtipos, es decir, la función/clase genérica puede aceptar supertipos del tipo de datos para el que ya está definida, por ejemplo, una clase genérica definida para Número no puede aceptar Int, pero una clase genérica definida para Int puede aceptar Número. Se implementa en Kotlin usando la palabra clave in de la siguiente manera:
fun main(args: Array<String>) { var a: Container<Dog> = Container<Animal>() //compiles without error var b: Container<Animal> = Container<Dog>() //gives compilation error } open class Animal class Dog : Animal() class Container<in T>
Tipo de proyecciones –
Si queremos copiar todos los elementos de una array de algún tipo en la array de Cualquier tipo, entonces puede ser posible, pero para permitir que el compilador compile nuestro código, debemos anotar el parámetro de entrada con la out
palabra clave. Esto hace que el compilador deduzca que el argumento de entrada puede ser de cualquier tipo que sea un subtipo de Any:
Programa Kotlin para copiar elementos de una array en otra:
fun copy(from: Array<out Any>, to: Array<Any>) { assert(from.size == to.size) // copying (from) array to (to) array for (i in from.indices) to[i] = from[i] // printing elements of array in which copied for (i in to.indices) { println(to[i]) } } fun main(args :Array<String>) { val ints: Array<Int> = arrayOf(1, 2, 3) val any :Array<Any> = Array<Any>(3) { "" } copy(ints, any) }
Producción:
1 2 3
Proyecciones de estrellas –
Cuando no conocemos el tipo específico del valor y solo queremos imprimir todos los elementos de una array, usamos la proyección de estrella (*).
Programa Kotlin de uso de proyecciones de estrellas –
// star projection in array fun printArray(array: Array<*>) { array.forEach { print(it) } } fun main(args :Array<String>) { val name = arrayOf("Geeks","for","Geeks") printArray(name) }
Producción:
GeeksforGeeks
Publicación traducida automáticamente
Artículo escrito por SagnikMazumder y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA