Funciones y propiedades de extensión

Funciones y propiedades de extensión

Imagina que estás utilizando una clase pero ésta no tiene una funcionalidad concreta que necesitas; una solución es crear una nueva clase derivada que herede de ella y a la que agregamos el atributo o el método que necesitamos. Pero existe otra solución más sencilla que permite simplificar el código.

Y es que Kotlin ofrece, al igual que otros lenguajes de programación, la posibilidad de extender una clase con nuevas funcionalidades sin necesidad de heredar de esa clase ni alterar su código. Esto se hace utilizando declaraciones especiales llamadas extensiones, soportando tanto propiedades de extensión como funciones de extensión.

Con las funciones y las propiedades de extensión podemos extender cualquier librería o clase y luego utilizar esas funciones y propiedades como si fueran propias de la clase.

Las funciones de extensión nos permiten añadir nuestras propias funciones a cualquier objeto que usemos en nuestra aplicación. Para definir una función de extensión se escribe como cualquier otra función pero precedida del nombre de la clase para la que queremos crear la función separado por un punto, siguiendo esta sintaxis:
fun Clase.nombreFuncion(...) { ... }
En esta entrada vamos a ver varios ejemplos para ilustrar el concepto de extensión.

Por ejemplo, podemos crear un nuevo método llamado multiplicar que extienda la clase Int para que al incovocarlo sobre un objeto de la clase (un número entero) devuelva el resultado del producto de ese número por el valor del argumento utilizado, y lo podríamos utilizar como cualquier otra función de la clase Int (de hecho, sería parecida al método times):
// función que extiende la clase Int
fun Int.multiplicar(numero: Int): Int {
    return this * numero
}

fun main(args: Array<String>) {

    val n = 4
    val factor = 15

    // utilizando el método times de la clase Int
    var producto = n.times(factor)
    println("$n x $factor = $producto") // 4 x 15 = 60

    // con la función de extensión
    var productoEx = n.multiplicar(factor)
    println("$n x $factor = $productoEx") // 4 x 15 = 60

    println(20.multiplicar(2)) // 40
}

Otro ejemplo creando una nueva funcionalidad a la clase String con un método llamado capitalizar que devuelve el mismo String sobre el que se pasa con la primera letra en mayúsculas:
// función que extiende la clase String
fun String.capitalizar(): String {
    return this.substring(0, 1).toUpperCase().plus(this.substring(1))
}

fun main(args: Array<String>) {
    println("bilbao".capitalizar()) // Bilbao
}

Observa que en los dos ejemplos anteriores se utiliza la palabra this en el cuerpo de la función de extensión para hacer referencia al objeto o instancia de la clase, aunque en ocasiones se puede omitir, como en el último ejemplo:
// omisión de this
return substring(0, 1).toUpperCase().plus(this.substring(1))

Otro ejemplo para crear una función de extensión de la clase Int para que nos indique si el número entero sobre el que se aplica esta función es par:
// función que extiende la clase Int
fun Int.esPar(): Boolean = (this % 2 == 0)

fun main(args: Array<String>) {
    println(4.esPar()) // true
    println(7.esPar()) // false
}

También podemos aplicar las funciones de extensión a nuestras propias clases, por ejemplo podemos añadir un nuevo método a la clase Persona:
class Persona(nombre: String, apellido: String, edad: Int) {
    var nombre: String = ""
    var apellido: String = ""
    var edad: Int = 0

    init {
        this.nombre = nombre
        this.apellido = apellido
        this.edad = edad
    }
}

// función que extiende la clase Persona
fun Persona.esTerceraEdad() = edad >= 65

fun main(args: Array<String>) {
    var persona1 = Persona("Juan", "Palomo", 67)
    println(persona1.esTerceraEdad()) // true
}

Ejemplo de función de extensión de la clase IntArray para intercambiar la posición de dos elementos del array:
// función que extiende la clase IntArray
fun IntArray.intercambiar(index1:Int, index2:Int){
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
}

fun main(args: Array<String>) {

    val arrayEnteros = intArrayOf(1, 2, 3, 4, 5)

    arrayEnteros.intercambiar(0, arrayEnteros.lastIndex)

    for (a in arrayEnteros) print("$a ") // 5 2 3 4 1
}

A la hora de utilizar funciones de extensión ten en cuenta que éstas no pueden sobreescribir funciones propias de la clase, así que si defines una función de extensión con el mismo nombre y mismo número y tipos de parámetros, cuando intentes llamar a la función de extensión realmente estarás invocando a la función miembro de la clase, la cual tiene preferencia sobre las funciones de extensión.

De manera similar a las funciones, Kotlin soporta las propiedades de extensión, con una sintaxis similar aunque solo se puede definir usando explícitamente get y set:
val IntArray.ultimoIndice: Int
    get() = this.size - 1 // igual a get() = size - 1 por omisión de this

fun main() {
    val arrayEnteros = intArrayOf(1, 2, 3, 4, 5)
    println(arrayEnteros.ultimoIndice) // 4
    println(arrayEnteros[arrayEnteros.ultimoIndice]) // 5
}

Comentarios

Entradas populares

I/O: entrada y salida de datos en consola

Recursos gratis para aprender Kotlin

Lectura y escritura de archivos