Kotlin advanced generic

Posted by mydog on Sat, 19 Feb 2022 15:11:00 +0100

We often use generics in Android development, such as List, Map, Set, Adapter, etc. generics are also supported in Kotlin.
What is generics?
Generics: special types that postpone the type specific work until the object is created or the method is called.

1, Defining generic methods in Kotlin

Defining generics in Kotlin is the same as Java in the following two ways:

  • Defined on class
  • Defined in function
    Examples defined on classes:
class MagicBox<T>(val item: T) {
    var available = false

    fun fetch(): T? {
        return item.takeIf { available }
    }
}

Defined in function

class MagicBox<T>(val item: T) {
    var available = false

    /**
     * Add normal form type in function, similar to Java
     */
    fun <R> fetch(anotherGenericity: (T) -> R): R? {
        return anotherGenericity(item).takeIf { available }
    }
}

2, Defining constraint generics in Kotlin

Defining constraint generics in Kotlin is similar to defining generics in Java < T extend XXX >, which means that the type specified by generics must be a class of the specified type or a subclass that inherits the specified type class. For example: define a generic class a < T: b > (), and the incoming generic t must be B or a subclass of B. A complete example is as follows:

//Define a constraint generic
private class ConstraintMagicBox<T:Human>(item:T){

}

//Define a parent class
private open class Human(val name: String,val age:Int)
//Define a subclass of Human class
private class Male(name: String,age: Int):Human(name, age)
//Define a subclass of Human class
private class Female(name: String,age: Int):Human(name, age)

fun main() {
    //The same type of Human can be passed in
    val male = ConstraintMagicBox(Male("Jack", 20))
    val female = ConstraintMagicBox(Female("Jim", 20))
}

3, Kotlin defines a number of variable parameter generics

This is similar to defining a variable parameter (private void method(int.. num)) for a method in Java. The definition method in Kotlin depends on the keyword * * vararg * *. Ordinary functions and constructors can be used.
Examples are as follows:

private class VarMagicBox<T : VarHuman>(vararg val items: T) {

    //Get data from items according to the parameters, where the items type is not Array
    fun fetch(index: Int): T? {
        return items.getOrNull(0)
    }

    //Adding variable parameters to functions
    fun fetch(vararg indexs: Int):List<T>{
        indexs.takeIf {
            indexs.isNotEmpty()
        }.run {
            return items.filterIndexed { index, t ->
                indexs.contains(index)
            }
        }
    }
}

private open class VarHuman(val name: String, val age: Int)

4, Modifying generics with in and out in Kotlin

In Java, specify a specific type in the definition of a List. When creating an instance, you can only new instances of this type, and you cannot new instances of subclasses or parent classes,
example:
ArrayList<String> list = new ArrayList<CharSequence>()
perhaps
ArrayList<CharSequence> list = new ArrayList<String>()
These two writing methods are not supported in JAVA. The correct writing method is: ArrayList < string > List = new ArrayList < string > ().
However, support can be achieved in Kotlin.
Of course, the generic examples defined above are not supported, so how can Kotlin support such a writing?
Because Kotlin has two important keywords: in (covariance) and out (inversion)
Let's look at how the following two keywords support the above writing.
The usage is very simple. Let's start with the above example and see how to use it:

//The out decorated generic type only takes the generic type as the return value of the function
//Function: let the subclass generic object assign value to the parent generic object
interface OutTest<out T>{
    fun outTest():T
}

//in modified generics only take generics as function parameters, and generics cannot be used as return values
//A generic object can be assigned to a subclass
interface InTest<in T>{
    fun inTest(param : T)
}

The test code is as follows:

open class Food()
open class FastFood():Food()
class Hamburg():FastFood()

class FastFoodStore() : OutTest<FastFood>{
    override fun outTest(): FastFood {
        println("FastFoodStore ----------")
        return FastFood()
    }
}

class HamburgStore():InTest<FastFood>{
    override fun inTest(param: FastFood) {
        println("HamburgStore-----------")
    }
}

fun main() {
    //Subclass objects can be passed to parent generic objects
    val food1 : OutTest<Food> = FastFoodStore()

    //The parent class object can be passed to the child class generic object in
    val food2 : InTest<Hamburg> = HamburgStore()
}

Keyword in and out Usage Summary
There are two main points:

  • The generic type modified by out can only be used in the return value of the function, and the generic type modified by in can only be used in the parameters of the function;
  • The out modified generic can only assign the subclass generic object to the parent generic object, and the in modified generic can only assign the parent generic object to the subclass generic object, as shown in the figure below;

Welcome to leave a message and learn from each other!

Sample source address kotlin_demo

Topics: Java Android kotlin