Generics and delegates

In this section we’ll look at Kotlin’s generics and delegates.

Basic usage of generics

Generics are not exactly new. Java introduced generics as early as version 1.5, and Kotlin naturally supports generics. But generics in Kotlin are different from generics in Java. We’ll start with the basics of generics in this section, the same parts as in Java, and move on to Kotlin’s special generics capabilities.

First, explain what generics are. In normal programming mode, we need to specify a specific type for any variable. Generics allow us to write code without specifying a specific type, which makes the code more extensible.

For example, List is a List that can store data, but List does not restrict us to storing integer data or string data, because it does not specify a specific type, but uses generics. This is why we can use syntax like List and List to build lists of specific types.

So how do you define your generic implementation? Here’s a look at some basic grammar.

There are two main ways to define generics: one is to define generic classes, and the other is to define generic methods, using the syntax structure

. Of course, T in parentheses is not required, in fact you can use any letter or word, but in general, T is a generic way of writing.

If we wanted to define a generic class, we could write:

class MyClass<T> {
    fun method(param: T): T {
        return param
    }
}
Copy the code

MyClass is a generic class, and the methods in MyClass allow arguments and return values of type T.

When we call MyClass and method(), we can specify the generic type as a concrete type, as follows:

val myClass = MyClass<Int> ()val result = myClass.method(123)
Copy the code

Here we specify the generics of the MyClass class as Int, so the method() method can take an Int and return an Int.

What if we don’t want to define a generic class, but just want to define a generic method? It’s as simple as writing the syntactic structure that defines generics above the method, as follows:

class MyClass {
    fun <T> method(param: T): T {
        return param
    }
}
Copy the code

The call method also needs to be adjusted accordingly:

val myClass = MyClass()
val result = myClass.method<Int> (123)
Copy the code

As you can see, it is now time to specify a generic type when calling the method() method. In addition, Kotlin has an excellent type inference mechanism. For example, if we pass in an Int parameter, it automatically deduces that the generic type is Int, so we can omit the generic specification:

val myClass = MyClass()
val result = myClass.method(123)
Copy the code

Kotlin also allows us to restrict the types of generics. Currently you can specify the generics of the method() method as any type, but if this is not what you want, you can also restrict the type of the generics by specifying an upper bound, such as the method() method whose upper bound is set to type Number, as shown below:

class MyClass {
    fun <T : Number> method(param: T): T {
        return param
    }
}
Copy the code

This means that we can only specify the generics of the method() method as numeric types, such as Int, Float, Double, and so on. But if you specify it as a string, you’re bound to get an error because it’s not a number.

In addition, all generics are nullable by default, because the default upper bound of generics is Any? . To make the type of a generic non-null, manually specify the upper bound of the generic as Any.

Next, we’ll try to apply what we learned about generics in this section. Recall that while learning higher-order functions, we wrote a build function that looks like this:

fun StringBuilder.build(block: StringBuilder. () - >Unit): StringBuilder {
    block()
    return this
}
Copy the code

This function is basically the same as apply, except that build only applies to StringBuilder classes, whereas apply applies to all classes. Now let’s use what we learned about generics in this section to extend the build function to do exactly what apply does.

If you think about it, it’s not that complicated, just define the build function as a generic function and replace it with T wherever StringBuilder is mandatory. Create a new build.kt file and write the following code:

fun <T> T.build(block: T. () - >Unit): T {
    block()
    return this
}
Copy the code

And you’re done! Now you can use the build function just as you would use the apply function. For example, here we use the build function to simplify Cursor traversal:

contentResolver.query(uri, null.null.null.null)? .build {while (moveToNext()) {
            ...
    }
    close()
}
Copy the code

Ok, so that’s the basic usage of Kotlin generics, which is basically the same as generics in Java, so it should be easier to understand. Let’s move on to the other important theme of Kotlin’s lecture — delegation.

Class delegates and delegate properties

Delegation is a design pattern based on the basic idea that an operating object does not process a certain piece of logic itself, but rather delegates work to another auxiliary object. This concept may be relatively foreign to Java programmers, as Java has no language-level implementation of delegates, whereas languages such as C# have native support for delegates.

Kotlin also supports delegation, which is divided into two types: class delegate and delegate attribute. Let’s go through them one by one.

Commissioned by class

Let’s start with class delegation. The core idea is to delegate the implementation of a class to another class. In the previous section, we used a data structure called Set, which is similar to a List except that it stores unordered data and cannot store duplicate data. Set is an interface, and if you want to use it, you need to use its concrete implementation class, such as HashSet. With the delegate pattern, we can easily implement an implementation class of our own. For example, here we define a MySet and let it implement the Set interface as follows:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> {

    override val size: Int
        get() = helperSet.size

    override fun contains(element: T) = helperSet.contains(element)

    override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)

    override fun isEmpty(a) = helperSet.isEmpty()

    override fun iterator(a) = helperSet.iterator()
}
Copy the code

As you can see, the constructor of MySet receives a HashSet parameter, which acts as a helper object. However, in all the method implementations of the Set interface, we do not implement our own, but call the corresponding method implementation in the auxiliary object, which is actually a delegation mode.

So, what’s the advantage of writing this way? Since they are all method implementations that call helper objects, it is better to use helper objects directly. That’s true, but if we just let most of our method implementations call methods in auxiliary objects, and a few of our own methods are rewritten, or even some of our own, then MySet becomes a whole new data structure class, and that’s where the delegate pattern comes in.

However, there are some disadvantages to this method, if the interface is relatively few methods to implement, if there are dozens or hundreds of methods, each to call the corresponding method implementation in the auxiliary object, it is really sad to write. So is there any solution to this problem? It doesn’t exist in Java, but it can be fixed in Kotlin with the class delegate feature.

In Kotlin, the delegate keyword is BY. We simply use the by keyword at the end of the interface declaration, followed by the delegate helper object, and we can avoid writing a lot of template code like this:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {

}
Copy the code

The two pieces of code achieve exactly the same effect, but with the help of class delegate, the code is significantly simplified. In addition, if we want to re-implement a method, we can just rewrite that method separately, and the other methods can still enjoy the convenience of class delegation, as shown below:

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {

	fun helloWorld(a) = println("Hello World")

	override fun isEmpty(a) = false

}
Copy the code

Here we add a new helloWorld() method and override the isEmpty() method to always return false. This is, of course, the wrong way to do it, just for demonstration purposes. MySet is now a new data structure class that will never be empty and will print helloWorld(), as well as the rest of the Set interface functions that are consistent with HashSet. This is what Kotlin’s class delegate can do.

Entrusted property

With class delegates in hand, let’s move on to delegate properties. The basic idea is easy to understand, but the real challenge is how to apply it flexibly.

The core idea of class delegation is to delegate the concrete implementation of a class to another class, while the core idea of delegate attribute is to delegate the concrete implementation of an attribute (field) to another class.

Let’s look at the syntax structure of the delegate attribute, as follows:

class myClass {
    var p by Delegate()
}
Copy the code

As you can see, the by keyword is used to connect the p property on the left to the Delegate instance on the right. What does that mean? This means delegating the implementation of the p property to the Delegate class. The Delegate class’s getValue() method is automatically called when the p property is called, and the Delegate class’s setValue() method is automatically called when assigning a value to the P property.

Therefore, we need to implement the Delegate class concretely, as shown below:

class Delegate {

    var propValue: Any? = null

    operator fun getValue(myClass: MyClass, prop: KProperty< * >): Any? {
        return propValue
    }

    operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?). {
        propValue = value
    }
	
}
Copy the code

This is a standard code implementation template, and in the Delegate class we must implement getValue() and setValue() methods, both declared with the operator keyword.

The getValue() method takes two arguments: the first argument declares the class in which the Delegate function can be used, written as MyClass to indicate that the Delegate function can only be used in MyClass. The second argument, KProperty<>, is a property manipulation class in Kotlin that can be used to get various property-related values. It is not needed in the current scenario, but must be declared on method parameters. In addition, <*> is written to indicate that you do not know or care about the specific type of generics, but only for syntactic compilation, similar to <? > < p style = “max-width: 100%; clear: both; The return value can be declared as any type, depending on the implementation logic, the above code is just an example.

The setValue() method is similar, except that it takes three arguments. The first two arguments are identical to the getValue() method, and the last argument represents the specific value to be assigned to the delegate property. This parameter must be of the same type as the value returned by getValue().

This is how the whole Delegate property workflow is implemented. Now when we assign a value to the p property of MyClass, we call the setValue() method of the Delegate class. When we get the value of the P property of MyClass, The Delegate class’s getValue() method is called. Is that easy to understand?

However, there is one case where you can avoid implementing the setValue() method in the Delegate class, and that is when the p property in MyClass is declared using the val keyword. If the p attribute is declared using the val keyword, it means that the p attribute cannot be reassigned after initialization, so there is no need to implement setValue(), just getValue().

Okay, so that’s all we have to say about Kotlin’s delegate function. As mentioned earlier, delegation itself is not difficult to understand, but the real challenge is to apply it flexibly. Let’s use an example to see how delegate is used in action.

Implement a lazy function of your own

By lazy A lazy loading technique. Put code that you want to delay execution into a by lazy block, so that the code in the block does not execute in the first place and only executes when the variable is called for the first time.

With Kotlin’s delegation, we can decrypt by lazy, which has the following basic syntax:

var p by lazy {

}
Copy the code

Now that you look at the code, does it make sense? In fact, by lazy is not a keyword linked together. Only by is the key in Kotlin, and lazy is just a higher-order function. The lazy function creates and returns a Delegate object, and when we call the p property, we actually call the Delegate object’s getValue() method, The getValue() method then calls the Lambda expression passed in by the lazy function, so that the code in the expression can be executed and the p attribute is the return value of the last line of code in the Lambda expression.

So Kotlin’s lazy loading technique isn’t that mysterious. Once you know how it works, you can implement your own lazy function.

So without further ado, let’s begin. Create a new later.kt file and write the following code:

class Later<T>(val block: () -> T) {
}
Copy the code

Here we first define a Later class and specify it as a generic class. The constructor for Later takes a function type argument that takes no arguments and returns the generic type specified by the Later class.

We then implement the getValue() method in the Later class as follows:

class Later<T>(val block: () -> T) {

    var value: Any? = null

    operator fun getValue(any: Any? , prop:KProperty< * >): T {
        if (value == null) {
            value = block()
        }
        return value as T
    }
	
}
Copy the code

The first argument to the getValue() method is specified as Any? Type, indicating that we want Later’s delegate functionality to be available to all classes. A value variable is then used to cache the value, calling the function type parameter passed in the constructor to fetch the value if the value is empty, or returning it otherwise.

Since lazy loading techniques do not assign values to properties, we will not implement the setValue() method here.

At this point, the delegate property is done. We can use it right away, but to make its use more lazy, we’d better define a top-level function. This function is written directly in the later. kt file, but is defined outside of the Later class, because only functions that are not defined in any class are top-level functions. The code looks like this:

fun <T> later(block: () -> T) = Later(block)
Copy the code

We define the top-level function as a generic function, and it also takes a function type parameter. This top-level function is simple: it creates an instance of the Later class and passes the received function type arguments to the Later class constructor.

Now that we’ve written our own lazy loading function for Later, you can use it directly to replace the lazy function, as shown below:

val uriMatcher by later {
    val matcher = UriMatcher(UriMatcher.NO_MATCH)
    matcher.addURI(authority, "book", bookDir)
    matcher.addURI(authority, "book/#", bookItem)
    matcher.addURI(authority, "category", categoryDir)
    matcher.addURI(authority, "category/#", categoryItem)
    matcher
}
Copy the code

In addition, it is important to note that although we have written our own lazy loading function, the basic implementation principle of lazy function is only roughly implemented for the sake of simplicity, and some aspects such as synchronization and null value handling are not strictly implemented. Therefore, in a formal project, using Kotlin’s built-in lazy function is the best choice.

Okay, that’s all for Kotlin’s lecture.