Las funciones de extensión (extension functions) son una de las diferencias clave de Kotlin respecto a Java, apareciendo por primera vez con las primeras versiones del lenguaje inspiradas en C#. Permiten añadir nuevos métodos a clases existentes sin necesidad de herencia o modificación de la clase original. Esto aumenta drásticamente la expresividad y concisión del código.
En Java y muchos otros lenguajes, no es posible añadir métodos a una clase ya existente (por ejemplo, String o List) sin usar herencia o el patrón Decorador. Kotlin resuelve esta limitación a través del mecanismo de funciones de extensión.
Muchas clases de la biblioteca (como String, Int, List) no se pueden modificar directamente. A menudo se requieren métodos adicionales para hacer el código más legible y evitar la duplicación de lógica. Sin embargo, las funciones de extensión no alteran la clase real; se crean estáticamente y no tienen acceso a miembros private/protected.
En Kotlin, la declaración de una función de extensión se ve así:
fun String.lastChar(): Char = this[this.length - 1]
Ahora se puede llamar a este método en cualquier String:
"Kotlin".lastChar() // devolverá 'n'
¿Puede la función de extensión ser sobrescrita en un heredero de la clase?
No. Las funciones de extensión no son miembros de la clase, no se pueden sobrescribir con override: su estática se manifiesta incluso para los herederos; el compilador elige la función según el tipo de la expresión, no por el tipo real del objeto.
open class Base class Derived : Base() fun Base.foo() = "base" fun Derived.foo() = "derived" fun printFoo(b: Base) { println(b.foo()) } val d = Derived() printFoo(d) // mostrará "base"
¿Pueden las funciones de extensión cambiar el estado del objeto?
Las funciones de extensión no pueden acceder a estados private/protected, pero si el tipo es mutable (por ejemplo, List o tu propia clase), pueden cambiar el estado externo accesible a través de métodos públicos.
fun MutableList<Int>.addTwice(elem: Int) { add(elem) add(elem) }
¿Las funciones de extensión chocan con los miembros de la clase que tienen el mismo nombre?
Si el nombre de la extensión coincide con el nombre de un método real, la prioridad es para el miembro de la clase. La extensión solo se llamará si los miembros no están presentes o no son visibles.
class Foo { fun bar() = "member" } fun Foo.bar() = "extension" val f = Foo() println(f.bar()) // "member"
Desarrollador amplía el estándar String con una función que establece un formato específico del proyecto y la usa en todas partes; otros desarrolladores no se dan cuenta de que esta no es una operación estándar de la clase String.
Pros:
Contras:
Extensión de la clase estándar con un método de utilidad que está claramente localizado en el paquete utils y bien documentado. Su uso está limitado a las secciones del código explícitamente acordadas.
Pros:
Contras: