官术网_书友最值得收藏!

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.

主站蜘蛛池模板: 黑龙江省| 南投市| 伊宁市| 江源县| 洛川县| 扶风县| 深圳市| 库伦旗| 沙坪坝区| 宝应县| 青铜峡市| 尚义县| 会同县| 杭锦旗| 烟台市| 茂名市| 合川市| 武冈市| 绥阳县| 延长县| 景泰县| 景谷| 武乡县| 宁夏| 石首市| 兴隆县| 德格县| 左贡县| 苍梧县| 大埔县| 东乡县| 大连市| 芷江| 饶阳县| 时尚| 定边县| 拉萨市| 东莞市| 卢龙县| 沙坪坝区| 南丰县|