Estaba programando por diversión y me preguntaba cuál debería usar. Probé los dos y me dieron el mismo resultado. Entonces, ¿cuál es la diferencia entre los dos?
Ejemplos:
fun Any?.foo() = this != null fun <T> T?.foo() = this != null La función real es un poco más complicada y en realidad hace algo basado en el tipo real del objeto (como un when con algunas opciones)
La segunda función le brinda una oportunidad que no se usa en este caso particular: captura el tipo del receptor en el parámetro de tipo T , para que pueda usarlo en otro lugar de la firma, como en los tipos de parámetros o el valor devuelto. type, o en el cuerpo de la función.
Como ejemplo bastante sintético, una listOf(this, this) dentro de la segunda función se escribiría como List<T?> , conservando el conocimiento de que el tipo de elementos es el mismo que el tipo de receptor, mientras que la misma expresión en la primera función sería una List<Any?> .
La primera función no permite utilizar el tipo de receptor de forma genérica para almacenar elementos de este tipo, aceptar elementos adicionales del mismo tipo como parámetros o utilizar el tipo de receptor en el tipo de valor de retorno de la función, mientras que la segunda función permite todos los estos.
Estas funciones son equivalentes desde la perspectiva del tiempo de ejecución, ya que los genéricos se borran del código de bytes de JVM cuando se compila el código, por lo que no podrá determinar el tipo T en el tiempo de ejecución y actuar en función de él, a menos que convierta la función en un función inline con un parámetro de tipo reified .
Como caso especial muy importante, capturar el tipo de un sitio de llamada en un parámetro de tipo permite que una función de orden superior acepte otra función usando T en su firma. La biblioteca estándar tiene un conjunto de funciones de alcance ( run , apply , let , also ) que muestran la diferencia.
Supongamos que la firma de also usó genéricos y se veía así:
fun Any?.also(block: (Any?) -> Unit): Any? { ... } Se puede llamar a esta función en cualquier objeto, pero su firma no muestra que es el objeto receptor el que se pasa al block y se devuelve desde la función; el compilador no podrá garantizar la seguridad de tipos y, por ejemplo, permitir una llamada a un miembro del objeto receptor sin verificación de tipo:
val s: String = "abc" // won't compile: `it` is typed as `Any?`, the returned value is `Any?`, too val ss1: String = (s + s).also { println(it.length) } // this will work, but it's too noisy val ss2: String = (s + s).also { println((it as String).length) } as StringAhora, capturar el parámetro de tipo es exactamente la forma de mostrar que es el mismo tipo en los tres lugares. Modificamos la firma de la siguiente manera:
fun <T : Any?> T.also(block: (T) -> Unit): T { ... } Y el compilador ahora puede inferir los tipos, sabiendo que es el mismo tipo T en todas partes donde aparece:
val s: String = "abc" // OK! val ss: String = (s + s).also { println(it.length) }Si ejecuta esto en la JVM, obtendrá lo siguiente
java.lang.ClassFormatError: nombre y firma del método duplicado en el archivo de clase...
Esto es interesante, por lo que desde el punto de vista de la firma son idénticos.
Depende en gran medida de su caso de uso, pero en la mayoría de los casos es posible que desee utilizar la variante genérica, ya que el tipo puede ser variable pero fijo en tiempo de compilación. Esta ventaja se vuelve obvia aquí:
fun Any?.foo() = this fun <T> T?.bar() = this fun main(args: Array<String>) { val x = 5.foo() // Any? val y = 5.bar() // Int? } Todas las propiedades y funciones disponibles en Int? no se podrá usar para x hasta que lo emita explícitamente (¿to e Int? ). y por otro lado "sabe" que devolvió un Int? .
En su ejemplo, no haría una diferencia ya que siempre devolverá un Boolean y, como ya se mostró, la firma es la misma.