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

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

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

La reference 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 function (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?

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 function de extensión es la forma idiomática de cambiar una class al agregar una nueva funcionalidad. Puede agregar funciones de extensión y properties de extensión . Vea un ejemplo en el Módulo Jackson-Kotlin para agregar methods a la class ObjectMapper simplificando el event handling TypeReference y generics.

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

Casos obligatorios:

  1. Cuando desee una function pnetworkingeterminada en línea para una interfaz, debe usar una function de extensión para agregarla a la interfaz porque no puede hacerlo dentro de la statement 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 class que actualmente no admite ese uso. Puede agregar un método de extensión iterator() que siga las reglas descritas en la documentation de loops for : incluso el object similar al iterador devuelto puede usar extensiones para satisfacer las reglas de proporcionar next() y hasNext() .

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

Casos opcionales:

  1. Desea controlar el scope de cuando algo está visible para una persona que llama, por lo que amplía la class solo en el context 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 scope

  2. Tiene una interfaz que desea simplificar la implementación requerida, al time que permite más funciones de ayuda para el usuario. Opcionalmente, puede agregar methods pnetworkingeterminados 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 pnetworkingeterminados, 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 class de receptor como un lugar desde donde encontrarlas. Su espacio de nombre se convierte en la class (o classs) 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 dialogs 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 class Path y es difícil encontrar el Files.exist(path) porque su nombre está espaciado de manera extraña. La function podría colocarse directamente en Path.exists() lugar. (@kirill)

Reglas de precedencia:

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

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

Hay al less 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)) } 

Las funciones de extensión funcionan muy bien con el operador de llamada segura ?. . Si espera que el argumento de la function a veces sea null , en lugar de regresar pronto, conviértalo en el receptor de una function 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 function de extensión es más corta y en el sitio de llamadas, queda inmediatamente claro que el resultado será anulable. }

  • Cómo convertir una Colección Mutable en una Inmutable
  • Acceder a las properties de una tarea en gradle kotlin
  • Kotlin AAR library w / proguard: ¿cómo mantener los methods de extensión?
  • Método hashMapOf () en Kotlin
  • Tokens inesperados (uso; para separar expresiones en la misma línea) en kotlin
  • Fragmento Button onClickListener no se está ejecutando
  • Hibernate OneToMany enlace incorrecto desde el padre
  • ¿Insertar o agregar un elemento en ArrayList en Kotlin?
  • ¿Cómo acceder a los campos estáticos con reflect en kotlin?
  • ¿Por qué usamos "object complementario" como un tipo de reemploop para los campos estáticos de Java en Kotlin?
  • Obtener el porcentaje de orientación en píxeles mediante progtwigción