[Kotlin beginners] simple analysis and use of generics

About the author: CSDN blog expert, Huawei cloud sharing expert certification

Series column: Kotlin beginner

Learning and communication: three people must have my teacher; Choose the good and follow it, and change the bad.

catalogue

1, Generic usage

1.1 advantages of generics

1.2 generic classes

1.3 generic functions

1.4 generic interfaces

2, Generic type constraints

3, Deformation

3.1 unchanged

3.2 out covariance

3.3 in inverter

4, inline + reified

5, vararg

1, Generic usage

Generics, or "parameterized types", parameterize types and can be used on classes, interfaces and functions.

Like Java, Kotlin also provides generics to ensure type safety and eliminate the trouble of type coercion.

1.1 advantages of generics

  • Type safe: generic allows only objects of a single type to be retained. Generics do not allow other objects to be stored.

  • No type conversion required: no type conversion is required for the object.

  • Compile time checking: checks generic code at compile time to avoid any problems at run time.

1.2 generic classes

The generic parameters specified by the TFood class are represented by the letter T placed in a pair of < >. T is a placeholder representing the item type. The TFood class accepts any type of item as the main constructor value (item: T).

//1. Create generic class
class TFood<T>(item:T){
    init {
/// / use is to check the type of item.
//        if(item is TApple){
/// / use as? First perform safe type conversion, and then use Avoid null pointers
//            println((item as? TApple)?.price)
//        }else{
//            println(item)
//        }
        //The above can be done with the following line.
        println(if(item is TApple)((item as? TApple)?.price)else item)
    }
}
//2. Create an incoming class
class TApple(var price :Int)
//3. Use
fun main() {
    //Incoming Int type
    TFood(30)//30
    //Incoming String type
    TFood("Fruits")//Fruits
    //Incoming entity object
    TFood(TApple(13))//13
}

If generics are not applicable here, you may need to create multiple classes to receive different reference types.

Generic parameters are usually represented by the letter T (representing English type). Of course, they can also be represented by other letters. However, other languages that support generics are using this conventional T, so it is recommended that you continue to use it, so that the code written will be easier for others to understand.

1.3 generic functions

Generic parameters can also be used for functions.

Define a function to get elements, which can be obtained only when generic classes are available.

class TFood<T>(item:T){
    var tem:T = item
    //Add generic function
    fun getItem():T?{
        return tem
    }
    
    //Multiple generic parameters, < R > return generic type
    fun <R> getItem(itemFun: (T) -> R): R {
        return itemFun(tem)
    }
}
    //Incoming String type
    var s = TFood("Fruits")//Fruits
    //Incoming entity object
    var apple = TFood(TApple(13))//13
    //Using generic functions
    s.getItem().run {
        println(this)//Fruits
    }
    apple.getItem().run {
        println(this)//Instance object: TApple@3f3afe78
        println(this?.price)//13
    }
    
    //Type is passed in and Int type is returned
    var intType = apple.getItem {
        //it:TApple
        it.price
    }

1.4 generic interfaces

//Defining generic interfaces
interface IFoodEffect<T>{
    fun effect(item:T)
}
//Implementation interface
class Banana:IFoodEffect<String>{
    override fun effect(item: String) {
        println(item)//item
    }

}
    //use
    Banana().effect("Often eating bananas is good for the brain, preventing nerve fatigue, moistening the lungs, relieving cough and preventing constipation")
    

2, Generic type constraints

Specify parameter type: I want this generic class to pass in only one type.

open class Vip(price:Int)

class TApple(var price: Int): Vip(price)
class TFood<T:Vip>(item: T) {
    ...
}

T:Vip, which means that only Vip and its subclasses can be passed in here. This is similar to Java <? Extensions T > upper bound wildcard

Because Int and String types do not inherit Vip class, an error is reported.

3, Deformation

  • Out (covariance): it can only appear in the output position of the function and can only be used as the return type, that is, the producer.

  • In (inverse): it can only appear at the input position of the function. As a parameter, it can only be used as a consumption type, that is, a consumer.

  • Default (unchanged): if the generic class takes both the generic type as a function parameter and the generic type as the output of the function, neither out nor in is used.

3.1 unchanged

Generic types are used both as output and as parameters.

//unchanged
interface IUnchanged<T> {
    //Returnable T
    fun originally(): T
    //T can be passed in as a parameter
    fun originally(t:T)
}

class BigStore:IUnchanged<Fruit>{
    override fun originally(): Fruit {
        return Fruit()
    }

    override fun originally(t: Fruit) {
    }
}
class SmallStore:IUnchanged<AppleHn>{
    override fun originally(): AppleHn {
        return AppleHn()
    }

    override fun originally(t: AppleHn) {
    }
}
fun main() {
    println("-------------")
    var bs:IUnchanged<Fruit> = BigStore()
    println(bs)
    var ss:IUnchanged<AppleHn> = SmallStore()
    println(ss)
}

3.2 out covariance

It can only appear at the output of the function and can only be used as a return type, that is, the producer.

Function: you can assign a subclass generic object to a parent generic object.

//out
interface IReturn<out T>{
    fun effect():T
}

open class Fruit()
class AppleHn():Fruit()

//producer
class FruitMarket:IReturn<Fruit>{
    override fun effect(): Fruit {
        println("FruitMarket effect")
        return Fruit()
    }
}
class AppleHnMarket:IReturn<AppleHn>{
    override fun effect(): AppleHn {
        println("AppleHnMarket effect")
        return AppleHn()
    }
}

fun main() {
    //out: the subclass generic object (AppleHn) can be assigned to the parent generic object (Fruit)
    var fm:IProduction<Fruit> = FruitMarket()
    println(fm.effect())
    //The reference type of am is Fruit object
    //However, AppleHnMarket returns the AppleHn object.
    //Here, the AppleHn object is assigned to the Fruit object and returned.
    var am:IProduction<Fruit> = AppleHnMarket()
    println(am.effect())
}

3.3 in inverter

It can only appear at the input position of the function and can only be used as a parameter, that is, the consumer.

Function: you can assign a parent generic object to a child generic object.

//in
interface IConsumer<in T>{
    fun spend(t:T)
}

class Animal:IConsumer<Fruit>{
    override fun spend(t: Fruit) {
        println("Animal spend Fruit")
    }
}

class People:IConsumer<AppleHn>{
    override fun spend(t: AppleHn) {
        println("People spend AppleHn")
    }
}
fun main() {
    //in: you can assign a parent generic object (Fruit) to a child generic object (AppleHn)
    var fca: IConsumer<AppleHn> = Animal()
    fca.spend(AppleHn())
    println(fca)
    var fcp: IConsumer<AppleHn> = People()
    fcp.spend(AppleHn())
    println(fcp)
}

4, inline + reified

Both Java and Kotlin generics will be erased at run time, but Kotlin can use inline functions to avoid this limitation, and the type parameters of inline functions can be implemented.

In addition to the inline function, which can improve performance and inline code, another scenario is that type parameters can be implemented.

If you declare the function as inline and mark the type parameter with reified, you can implement the type parameter.

class BookS<T : AndroidS> {
    //    fun <T> readBookT(anonymous: () -> T): T {
//        var list = listOf(
//KotlinS("Kotlin beginners", 12),
//JavaS("Java Shuai times", 28)
//        )
//        var data = list.shuffled().first()
//        return if (data is T) {
//            data
//        } else {
//            anonymous()
//        }
//    }
    //The type of T is determined by anonymous() return type inference
    inline fun <reified T> readBook(anonymous: () -> T): T {
        var list = listOf(
            KotlinS("Kotlin beginner", 12),
            JavaS("Java Shuai Ci", 28)
        )
        var data = list.shuffled().first()
        println(data)
        //If data is T, return data; otherwise, execute anonymous()
        return if (data is T) {
            data
        } else {
            anonymous()
        }
    }
}

open class AndroidS(name: String)
class KotlinS(var name: String, var price: Int) : AndroidS(name){
    override fun toString(): String {
        return "KotlinS(name='$name', price=$price)"
    }
}
class JavaS(var name: String, var price: Int) : AndroidS(name){
    override fun toString(): String {
        return "JavaS(name='$name', price=$price)"
    }
}

fun main() {
    var bookS:BookS<AndroidS> = BookS()
    //The type of T is inferred from anonymous(), where t is KotlinS
    var data = bookS.readBook {
        KotlinS("Anonymous-K",23)
    }
    println(data)
}

Generic T is KotlinS, and there are two random results of list: KotlinS and JavaS

Generate KotlinS running results: directly return the generated KotlinS

"E:\Android\Android StudioO\jre\bin\java.exe"
KotlinS(name='Kotlin beginner', price=12)
KotlinS(name='Kotlin beginner', price=12)

Process finished with exit code 0

Generate Java s running results: call anonymous() function to return KotlinS

"E:\Android\Android StudioO\jre\bin\java.exe"
JavaS(name='Java Shuai Ci', price=28)
KotlinS(name='Anonymous-K', price=23)

Process finished with exit code 0

5, vararg

You can only put one generic class at a time. What if you need to put multiple instances?

class BookMany<T : AndroidMany>(vararg item: T) {
    var data: Array<out T> = item
}

open class AndroidMany(name: String)
class KotlinMany(var name: String, var price: Int) : AndroidMany(name) {
    override fun toString(): String {
        return "KotlinS(name='$name', price=$price)"
    }
}

class JavaMany(var name: String, var price: Int) : AndroidMany(name) {
    override fun toString(): String {
        return "JavaS(name='$name', price=$price)"
    }
}

fun main() {
    var book = BookMany(
        KotlinMany("beginner", 18),
        KotlinMany("Initiate ", 28),
        KotlinMany("terminator", 38),
        JavaMany("Comprehensive person", 35),
    )
    println(book)//com.scc.kotlin.primary.classkotlin.BookMany@3d24753a
}

After adding multiple objects, if you print book directly, you get a BookMany object. How do you get the objects in book?

Overloaded operator function, get function, value through [] operator.

class BookMany<T : AndroidMany>(vararg item: T) {
    var data: Array<out T> = item
    operator fun get(index:Int) = data[index]
}

fun main() {
    var book = BookMany(
        KotlinMany("beginner", 18),
        KotlinMany("Initiate ", 28),
        KotlinMany("terminator", 38),
        JavaMany("Comprehensive person", 35),
    )
    println(book)//com.scc.kotlin.primary.classkotlin.BookMany@3d24753a
    println(book[0])//KotlinS(name = 'Beginner', price=18)
    println(book[2])//KotlinS(name = 'terminator', price=38)
}

Keywords: Java Android kotlin

Added by JoeF on Sun, 02 Jan 2022 22:37:07 +0200