when new method many constructors kotlin code-structure

new - package kotlin



¿Cuándo debería uno preferir las funciones de extensión de Kotlin? (4)

En Kotlin, una función con al menos un argumento puede definirse como una función regular no miembro o como una función de extensión con un argumento como receptor.

En cuanto a la definición del alcance, parece que no hay diferencia: ambos pueden declararse dentro o fuera de las clases y otras funciones, y ambos pueden o no tener modificadores de visibilidad por igual.

La referencia del lenguaje parece no recomendar el uso de funciones regulares o funciones de extensión para diferentes situaciones.

Entonces, mi pregunta es: ¿ cuándo las funciones de extensión dan ventaja sobre las que no son miembros regulares? Y cuando los regulares sobre las extensiones?

foo.bar(baz, baq) vs bar(foo, baz, baq) .

¿Es solo una insinuación de semántica de una función (el receptor está definitivamente enfocado) o hay casos en que el uso de funciones de extensiones hace que el código sea mucho más limpio / abre oportunidades?


Hay al menos un caso en el que las funciones de extensión son imprescindibles: encadenamiento de llamadas, también conocido como "estilo fluido":

foo.doX().doY().doZ()

Supongamos que desea extender la interfaz de Stream desde Java 8 con sus propias operaciones. Por supuesto, puede usar funciones comunes para eso, pero se verá feo como el infierno:

doZ(doY(doX(someStream())))

Claramente, quiere usar funciones de extensión para eso. Además, no puede convertir infi funciones comunes, pero puede hacerlo con funciones de extensión:

infix fun <A, B, C> ((A) -> B).`|`(f: (B) -> C): (A) -> C = { a -> f(this(a)) } @Test fun pipe() { val mul2 = { x: Int -> x * 2 } val add1 = { x: Int -> x + 1 } assertEquals("7", (mul2 `|` add1 `|` Any::toString)(3)) }


Hay casos donde tienes que usar métodos de extensión. Por ejemplo, si tiene alguna implementación de lista MyList<T> , puede escribir un método de extensión como

fun Int MyList<Int>.sum() { ... }

Es imposible escribir esto como un método "normal".


Las funciones de extensión funcionan muy bien con el operador de llamada segura ?. . Si espera que el argumento de la función a veces sea null , en lugar de regresar pronto, conviértalo en el receptor de una función de extensión.

Función ordinaria:

fun nullableSubstring(s: String?, from: Int, to: Int): String? { if (s == null) { return null } return s.substring(from, to) }

Función de extensión:

fun String.extensionSubstring(from: Int, to: Int) = substring(from, to)

Llamar al sitio:

fun main(args: Array<String>) { val s: String? = null val maybeSubstring = nullableSubstring(s, 0, 1) val alsoMaybeSubstring = s?.extensionSubstring(0, 1)

Como puede ver, ambos hacen lo mismo, sin embargo, la función de extensión es más corta y en el sitio de llamadas, queda inmediatamente claro que el resultado será anulable.


Las funciones de extensión son útiles en algunos casos y obligatorias en otras:

Casos idiomáticos:

  1. Cuando desee mejorar, extender o cambiar una API existente. Una función de extensión es la forma idiomática de cambiar una clase al agregar una nueva funcionalidad. Puede agregar funciones de extensión y propiedades de extensión . Vea un ejemplo en el Módulo Jackson-Kotlin para agregar métodos a la clase ObjectMapper simplificando el manejo de TypeReference y genéricos.

  2. Agregar seguridad nula a métodos nuevos o existentes que no se pueden invocar en un null . Por ejemplo, la función de extensión para String of String?.isNullOrBlank() permite usar esa función incluso en una cadena null sin tener que hacer primero su propia comprobación null . La función en sí misma realiza la comprobación antes de llamar a las funciones internas. Consulte la documentación de las extensiones con receptor Nullable

Casos obligatorios:

  1. Cuando desee una función predeterminada en línea para una interfaz, debe usar una función de extensión para agregarla a la interfaz porque no puede hacerlo dentro de la declaración de interfaz (las funciones en línea deben ser final y no están permitidas actualmente en una interfaz). Esto es útil cuando necesita funciones reificadas en línea, por ejemplo este código de Injekt

  2. Cuando desee agregar soporte for (item in collection) { ... } a una clase que actualmente no admite ese uso. Puede agregar un método de extensión iterator() que siga las reglas descritas en la documentación de bucles for : incluso el objeto similar al iterador devuelto puede usar extensiones para satisfacer las reglas de proporcionar next() y hasNext() .

  3. Agregar operadores a clases existentes como + y * (especialización de # 1 pero no puede hacer esto de otra manera, por lo tanto es obligatorio). Ver documentación para sobrecarga del operador

Casos opcionales:

  1. Desea controlar el alcance de cuando algo está visible para una persona que llama, por lo que amplía la clase solo en el contexto en el que permitirá que la llamada sea visible. Esto es opcional porque puedes permitir que las extensiones se vean siempre. ver respuesta en otra pregunta SO para funciones de extensión de alcance

  2. Tiene una interfaz que desea simplificar la implementación requerida, al tiempo que permite más funciones de ayuda para el usuario. Opcionalmente, puede agregar métodos predeterminados para la interfaz para ayudar o usar funciones de extensión para agregar las partes de la interfaz que no se espera implementar. Uno permite anular los valores predeterminados, el otro no (excepto por la precedencia de las extensiones frente a los miembros).

  3. Cuando desee relacionar funciones con una categoría de funcionalidad; las funciones de extensión usan su clase de receptor como un lugar desde donde encontrarlas. Su espacio de nombres se convierte en la clase (o clases) desde la que se pueden activar. Mientras que las funciones de nivel superior serán más difíciles de encontrar, y llenarán el espacio de nombre global en los diálogos de finalización del código IDE. También puede corregir los problemas de espacio existente en el nombre de la biblioteca. Por ejemplo, en Java 7 tiene la clase Path y es difícil encontrar el Files.exist(path) porque su nombre está espaciado de manera extraña. La función podría colocarse directamente en Path.exists() lugar. (@kirill)

Reglas de precedencia:

Al extender las clases existentes, tenga en cuenta las reglas de precedencia. Se describen en KT-10806 como:

Para cada receptor implícito en el contexto actual, probamos los miembros, luego las funciones de extensión local (también los parámetros que tienen el tipo de función de extensión), y luego las extensiones no locales.