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
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) }