- Learn Kotlin Programming(Second Edition)
- Stephen Samuel Stefan Bocutiu
- 687字
- 2021-06-24 14:13:36
Extension functions
Quite often, you will come across a situation where a type that you don't have control over will benefit from an extra function. Maybe you've always wished that String had a reverse() function, or perhaps that list had a drop function that would return a copy of list with the first k elements removed.
An object-orientated approach would be to extend the type, thereby creating a subtype that adds the required new functions:
abstract class DroppableList<E> : ArrayList<E>() { fun drop(k: Int): List<E> { val resultSize = size - k when { resultSize <= 0 -> return emptyList<E>() else -> { val list = ArrayList<E>(resultSize) for (index in k..size - 1) { list.add(this[index]) } return list } } } }
However, this isn't always possible. A class may be defined as final, so you cannot extend it. It may also be the case that you may not control when instances are created, so you can't substitute your subtype for the existing type.
A typical solution in this case is to create a function in a separate class that accepts the instance as another argument. In Java, for example, it is quite common to see classes that consist entirely of helper functions for other instances. A good example of this is the java.util.Collections class. This contains dozens of static functions that offer the functionality for working with collections:
fun <E> drop(k: Int, list: List<E>): List<E> { val resultSize = list.size - k when { resultSize <= 0 -> return emptyList<E>() else -> { val newList = ArrayList<E>(resultSize) for (index in k..list.size - 1) { newList.add(list[index]) } return newList } } }
The issue with this solution is two-fold. Firstly, we cannot use code completion in the integrated development environment (IDE) to see which function is available. This is because we write the function name first. Secondly, if we have many of these functions and we want to compose them, we end up with code that isn't particularly readable. For example, if we look at the following example of a nested function calling the reverse function:
reverse(take(3, drop(2, list)))
Wouldn't it be nice if we could access this function directly on the list instance so that the code could be composed in a fluent style, as in the following code:
list.drop(2).take(3).reverse()
Extension functions allow us to achieve exactly this without having to create a new subtype, modify the original type, or wrap the class.
An extension function is declared by defining a top-level function as normal, but with the intended type prefixed before the function name. The type of instance that the function will be used on is called the receiver type. The receiver type is said to be extended with the extension function. Here is our preceding drop function again; this time, it is implemented as an extension function:
fun <E> List<E>.drop(k: Int): List<E> { val resultSize = size - k when { resultSize <= 0 -> return emptyList<E>() else -> { val list = ArrayList<E>(resultSize) for (index in k..size - 1) { list.add(this[index]) } return list } } }
You will notice the use of the this keyword inside the function body. This is used to reference the receiver instance, that is, the object that the function was invoked on. Whenever we are inside an extension function, the this keyword always refers to the receiver instance, and the instances in the outer scope need to be qualified.
To use an extension function, we import it, as we would any other top-level function, by using the name of the function and the package it lives in:
import com.packt.chapter4.drop val list = listOf(1,2,3) val droppedList = list.drop2(2)
As you can see, after the extension function has been imported, it can be invoked on an instance of the type that the extension function has been defined on.
It should be noted that there are some drawbacks to extension functions. The first is that they decouple a class from its implementations. For example, you may not know of an extension function that was implemented elsewhere, so you are reliant on good tooling. The second drawback is that extension functions must always be stateless – they cannot keep state between invocations.
- C# 7 and .NET Core Cookbook
- 高效微控制器C語言編程
- PHP 7底層設計與源碼實現(xiàn)
- C語言程序設計實訓教程
- Android 9 Development Cookbook(Third Edition)
- 編寫高質(zhì)量代碼:改善C程序代碼的125個建議
- 小程序,巧運營:微信小程序運營招式大全
- Visual C
- Securing WebLogic Server 12c
- Scala謎題
- Kotlin從基礎到實戰(zhàn)
- SSM開發(fā)實戰(zhàn)教程(Spring+Spring MVC+MyBatis)
- Unity 3D腳本編程:使用C#語言開發(fā)跨平臺游戲
- Spring Security Essentials
- Python:Deeper Insights into Machine Learning