preface
Nice to meet you
In the last installment of this series, we covered most of Kotlin and experienced the convenience, power, and efficiency of Kotlin’s syntax for functional programming. For those of you who haven’t read the previous article, check out the “Kotlin” series first: First, Kotlin introduction, next we will enter the study of Kotlin generics, generics in my opinion is more complex, but also often asked in the interview, for a long time, I have a vague understanding of generics, so when using more confused, so this article hopes to take you to overcome this knowledge point
The problem
Here’s a list of questions that we can take with us:
1. What are generics?
2. What do generics do?
3. How to define and use generics?
1. What are generics?
Generics, popularly known as many types, allow us to program without specifying specific types by using the concept of parameterized types
2. What do generics do?
Generics are a security mechanism introduced in JDK 1.5 and a technique used by compilers:
1. Improved code reusability
2. Type conversion exceptions at runtime are advanced to compile time to ensure type safety and avoid type conversion exceptions
3. How to define and use generics?
We can specify generics for a class, method, or interface, specifying specific types where they are used
Java generics
To learn Kotlin generics well, we need to know enough about Java generics, because Kotlin generics are basically the same as Java generics, with some things written in a new way on Kotlin
1. Easy use of generics
In Java, we can specify generics for a class, method, or interface, specifying specific types where they are used
1) Define a generic class by adding
to the name of the class
// Define a generic class
public class JavaGenericClass<T> {
private T a;
public JavaGenericClass(T a) {
this.a = a;
}
public T getA(a) {
return a;
}
public void setA(T a) {
this.a = a;
}
// Use generic classes
public static void main(String[] args) {
// The compiler can infer generic types, so the generic type after the new object can be omitted
JavaGenericClass<String> javaGenericClass1 = new JavaGenericClass<String>("erdai");
JavaGenericClass<Integer> javaGenericClass2 = new JavaGenericClass<>(Awesome!); System.out.println(javaGenericClass1.getA()); System.out.println(javaGenericClass2.getA()); }}// Print the result
erdai
Awesome!
Copy the code
2) Define a generic method by adding
to the return value of the method. There can be any number of generics. The generics of a generic method have nothing to do with the class in which it belongs
public class JavaGenericMethod {
public <T> void getName(T t){
System.out.println(t.getClass().getSimpleName());
}
public static void main(String[] args) {
JavaGenericMethod javaGenericMethod = new JavaGenericMethod();
// The compiler can infer generic types, so generic types can also be omitted here
javaGenericMethod.<String>getName("erdai666"); }}// Print the result
String
Copy the code
3) Define a generic interface
Adding
to the end of the interface name defines a generic interface, and there can be as many generics as you like
public interface JavaGenericInterface<T> {
T get(a);
}
class TestClass<T> implements JavaGenericInterface<T>{
private final T t;
public TestClass(T t) {
this.t = t;
}
@Override
public T get(a) {
returnt; }}class Client{
public static void main(String[] args) {
JavaGenericInterface<String> javaGenericInterface = new TestClass<>("erdai666"); System.out.println(javaGenericInterface.get()); }}// Print the result
erdai666
Copy the code
2. Generic erase
What is generic erasure?
Take a look at this code:
// Use different generic types to get the same data type
public class JavaGenericWipe {
public static void main(String[] args) {
Class a = new ArrayList<String>().getClass();
Class b = new ArrayList<Integer>().getClass();
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("a == b: "+ (a == b)); }}// Print the result
a = class java.util.ArrayList
b = class java.util.ArrayList
a == b: true
Copy the code
Why does this happen?
Because generics in Java are implemented using erase technology: generic erase refers to associating instances of generic types to the same bytecode by merging type parameters. The compiler generates only one copy of the bytecode for the generic type and associates instances with it
The reason for using generic erase is to be compatible with the classloaders of the runtime prior to JDK 1.5 and to avoid unnecessary classes being created at runtime due to the introduction of generics
2. Specific steps for generic erasure
1) Erases all type parameter information, and replaces each parameter with its first boundary if the type parameter is bounded; If the type parameter is unbounded, replace it with the rule for Object type erasure:
Becomes Object after erasure
Becomes A after erasure
becomes A after erasure
becomes Object after erasing
Insert type conversions (if necessary) to preserve type safety
3) generate bridge methods (if necessary) to preserve polymorphism in subclasses
// Case 1: Erases all type parameter information, and replaces each parameter with its first boundary if the type parameter is bounded; If the type parameter is unbounded, replace it with Object
class Paint {
void draw(a) {
System.out.println("Paint.draw() called"); }}// if T is not bounded, then T in work cannot call draw
class Painter<T extends Paint> {
private T t;
public Painter(T t) {
this.t = t;
}
public void work(a) { t.draw(); }}// case 2 :(if necessary) insert type conversions to preserve type safety
public class JavaGenericWipe {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("erdai");
stringList.add("666");
for(String s : stringList) { System.out.println(s); }}}// The bytecode file generated at compile time is roughly translated as follows
public class JavaGenericWipe {
public JavaGenericWipe(a) {}public static void main(String[] args) {
List<String> stringList = new ArrayList();
stringList.add("erdai");
stringList.add("666");
Iterator var2 = stringList.iterator();
while(var2.hasNext()) {
// The compiler gives us some strong workString s = (String)var2.next(); System.out.println(s); }}}// Case 3 (if necessary) generate bridge methods to preserve polymorphism in subclasses
class Node {
public Object data;
public Node(Object data) {
this.data = data;
}
public void setData(Object data) {
this.data = data; }}class MyNode extends Node {
public MyNode(Integer data) {
super(data);
}
public void setData(Integer data) {
super.setData(data); }}// The bytecode file generated at compile time is roughly translated as follows
class MyNode extends Node {
public MyNode(Integer data) {
super(data);
}
// Bridge method generated by the compiler
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data); }}Copy the code
Pseudo generics
Generics in Java are a special syntactic sugar that is implemented by type erasing. Such generics are called pseudo-generics, which we can reflect to bypass the compiler generics check and add a parameter of a different type
// Reflection bypasses compiler checks
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("erdai");
stringList.add("666");
// Add a new element using reflection
Class<? extends List> aClass = stringList.getClass();
try {
Method method = aClass.getMethod("add", Object.class);
method.invoke(stringList,123);
} catch (Exception e) {
e.printStackTrace();
}
Iterator iterator = stringList.iterator();
while(iterator.hasNext()){ System.out.println(iterator.next()); }}// Print the result
erdai
Awesome!
123
Copy the code
4, generic erase advanced
Here’s a common problem I have at work:
When passing in the actual type of a generic on a network request, why is it possible to get the generic type correctly and use Gson to convert it to the actual object?
A: Because at runtime we can use reflection to get concrete generic types
What? Aren’t generics erased at compile time? Why can we get concrete generic types at run time? 🤔 ️
A: The so-called type erasure in generics only erases the generic information in the Code attribute. The generic information is retained in the class constant pool attribute (Signature attribute, LocalVariableTypeTable attribute). The attributes in the class constant pool can be erased by the class file, the field table, This allows the generics information we declare to be preserved, which is the basis for the reflection of generics information at run time
// This is the decomcompiled javagenericClass.class file, you can see T
public class JavaGenericClass<T> {
private T a;
public JavaGenericClass(T a) {
this.a = a;
}
public T getA(a) {
return a;
}
public void setA(T a) {
this.a = a;
}
/ /...
}
Copy the code
Note: Java was introduced with generics in JDK 1.5, and changes have been made to the JVM class file to make up for the lack of generic erasure. The most important changes are the Signature attribute table and the LocalVariableTypeTable attribute table
Let’s look at the following code:
class ParentGeneric<T> {}class SubClass extends ParentGeneric<String>{}class SubClass2<T> extends ParentGeneric<T> {}public class GenericGet {
// Get the actual generic type
public static <T> Type findGenericType(Class<T> cls) {
Type genType = cls.getGenericSuperclass();
Type finalNeedType = null;
if (genType instanceof ParameterizedType) {
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
finalNeedType = params[0];
}
return finalNeedType;
}
public static void main(String[] args) {
SubClass subClass = new SubClass();
SubClass2<Integer> subClass2 = new SubClass2<Integer>();
// Prints the generics obtained by subClass
System.out.println("subClass: " + findNeedClass(subClass.getClass()));
// Print the generic type obtained by subClass2
System.out.println("subClass2: "+ findGenericType(subClass2.getClass())); }}// Run this code to print the following
subClass: class java.lang.String
subClass2: T
Copy the code
Code above:
ParentGeneric (T = String); SubClass = ParentGeneric (T = String)
2. SubClass2 does no assignment to ParentGeneric, we get T by reflection
I’m sure you have a lot of questions here, right?
1. Why do we get generic types without passing in any generic information in 1?
2. Why did I pass in an Integer as the generic type when I created the object, but changed it to T when I fetched it?
Now let’s take a closer look at the wave:
As I mentioned above, type erasure is really just erasing generics in the Code property. Generics are also retained in the class constant pool property, so the SubClass and SubClass2 above actually keep their generics in the bytecode file when compiled, a String and a T. SubClass and subClass2 are created dynamically at runtime, so even if you pass in a generic type, it gets erased, so you get the result above. Is that clear?
If it’s a little vague, let’s look at another example:
class ParentGeneric<T> {}public class GenericGet {
// Get the actual generic type
public static <T> Type findGenericType(Class<T> cls) {
Type genType = cls.getGenericSuperclass();
Type finalNeedType = null;
if (genType instanceof ParameterizedType) {
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
finalNeedType = params[0];
}
return finalNeedType;
}
public static void main(String[] args) {
ParentGeneric<String> parentGeneric1 = new ParentGeneric<String>();
ParentGeneric<String> parentGeneric2 = new ParentGeneric<String>(){};
// Prints the generic obtained by parentGeneric1
System.out.println("parentGeneric1: " + findGenericType(parentGeneric1.getClass()));
// Prints the generic obtained by parentGeneric2
System.out.println("parentGeneric2: "+ findGenericType(parentGeneric2.getClass())); }}// Run this code to print the following
parentGeneric1: null
parentGeneric2: class java.lang.String
Copy the code
ParentGeneric1 = parentGeneric2;}
ParentGeneric1 is created at run time. Due to generic erasure, we cannot get the type from reflection, so null is printed
If you keep the generic type T, then I should get it as T. Why print null?
If you have this question in your mind, you are thinking very carefully. To understand this question, we need to know something about the Java Type system, which relates to the method I wrote above to get generic types:
// Get the actual generic type
public static <T> Type findGenericType(Class<T> cls) {
// Get the current parent class with generics
Type genType = cls.getGenericSuperclass();
Type finalNeedType = null;
// Enter the condition body if genType is a parameterized type
if (genType instanceof ParameterizedType) {
// Get the values in the argument type <>, such as Map
, then get an array of [K,V]
,v>
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
// Assign the first generic type to finalNeedType
finalNeedType = params[0];
}
return finalNeedType;
}
Copy the code
In the above code, we need to get the generic parent of the class, or if it’s a parameterized type, go into the condition body, get the actual generic type and return it. If not, return finalNeedType, which is null at this point
In example 1:
SubClass1 subClass1 = new SubClass1();
SubClass2<Integer> subClass2 = new SubClass2<>();
System.out.println(subClass1.getClass().getGenericSuperclass());
System.out.println(subClass2.getClass().getGenericSuperclass());
// Run the program to print the following result
com.dream.java_generic.share.ParentGeneric<java.lang.String>
com.dream.java_generic.share.ParentGeneric<T>
Copy the code
You can see that you got the generic parent, so you go into the condition body and get the actual generic type and return it
In example 2:
ParentGeneric<String> parentGeneric1 = new ParentGeneric<String>();
System.out.println(parentGeneric1.getClass().getGenericSuperclass());
// Run the program to print the following result
class java.lang.Object
Copy the code
As you can see, the parent of the generic is Object, so the condition body cannot be entered, so null is returned
ParentGeneric2 is created with {}, which makes parentGeneric2 an anonymous inner class. The parent class is ParentGeneric, because anonymous inner classes are created at compile time. The specific generic information is created and carried at compile time, so parentGeneric2 can get the generic types in it
Through two examples above we may safely draw the conclusion: if is preserved the generic type at compile time to bytecode, so we can at run time by reflection is available, if at runtime to actual generic types, this time will be erased, reflected for less than the current incoming generic types
In example 1 we specify that the actual type of the generic type is String, which is stored in the bytecode file at compile time, so we get the generic type. In Example 2 we created an anonymous inner class, which is also created at compile time and saves the actual generics to the bytecode so that we can retrieve them. ParentGeneric1 is created at run time. Although the generic T declared by ParentGeneric remains in the bytecode file at compile time, the actual type it passed in is erased, and this generic1 cannot be retrieved by reflection. Then you should be comfortable getting generic types
5. Generics acquisition experience
In fact, the above two examples show that when we define a subclass that inherits a generic parent class and gives that generic a type, we can get that generic type
// Define a subclass that inherits the generic parent and gives the generic an actual type
class SubClass extends ParentGeneric<String>{}// The anonymous inner class is also a subclass that inherits the generic parent and gives the generic an actual type
ParentGeneric<String> parentGeneric2 = new ParentGeneric<String>(){};
Copy the code
Therefore, if we want to retrieve a generic type, we can use the help of subclasses to retrieve the generic type. It is a good programming practice to declare the current generic class as abstract
3, boundary
Boundaries are restrictions placed on the parameters of a generic so that you can enforce the types that a generic can use and, more importantly, call a method on its own boundary type
2), can set multiple boundaries, use & in the middle of the boundary, only one of the boundaries is a class, and the class must be the first, similar syntax structure
<T extends ClassBound & InterfaceBound1 & InterfaceBound2>
Copy the code
Here’s a demonstration:
abstract class ClassBound{
public abstract void test1(a);
}
interface InterfaceBound1{
void test2(a);
}
interface InterfaceBound2{
void test3(a);
}
class ParentClass <T extends ClassBound & InterfaceBound1 & InterfaceBound2>{
private final T item;
public ParentClass(T item) {
this.item = item;
}
public void test1(a){
item.test1();
}
public void test2(a){
item.test2();
}
public void test3(a){ item.test3(); }}class SubClass extends ClassBound implements InterfaceBound1.InterfaceBound2 {
@Override
public void test1(a) {
System.out.println("test1");
}
@Override
public void test2(a) {
System.out.println("test2");
}
@Override
public void test3(a) {
System.out.println("test3"); }}public class Bound {
public static void main(String[] args) {
SubClass subClass = new SubClass();
ParentClass<SubClass> parentClass = newParentClass<SubClass>(subClass); parentClass.test1(); parentClass.test2(); parentClass.test3(); }}// Print the result
test1
test2
test3
Copy the code
4. Wildcards
1, generic covariant, contravariant and invariant
To consider a problem, the code looks like this:
Number number = new Integer(Awesome!);
ArrayList<Number> numberList = new ArrayList<Integer>();// The compiler reported an error type mismatch
Copy the code
ArrayList
cannot be instantiated by ArrayList
.
To understand this question, we first need to ask, what are covariants, contravariants and invariants of generics
1) Generic covariant, if I define A generic Class
where A is A subclass of B and Class
is A subclass of Class, then Class is covariant on T
If I define A generic Class
where A is A subclass of B and Class
is A subclass of Class, then we say that Class is contravariant on T
If I define A generic Class
, where A is A subclass of B and there is no inheritance relationship between Class
and Class, then we say that Class is invariant on T
ArrayList
cannot be instantiated by ArrayList
because the current generics of ArrayList are immutable.
Number number = new Integer(Awesome!);
ArrayList<? extends Number> numberList = new ArrayList<Integer>();
Copy the code
2. The upper boundary wildcard of the generic
1), the upper boundary of the generic wildcard syntax:
makes generics covariant. The type it qualifies is the current upper Bound class or a subclass of it, or in the case of an interface, the current upper Bound interface or implementation class. Variables using upper Bound wildcards are read-only, cannot be written to, can be added to, but have no meaning
public class WildCard {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<Integer>();
List<Number> numberList = new ArrayList<Number>();
integerList.add(Awesome!);
numberList.add(123);
getNumberData(integerList);
getNumberData(numberList);
}
public static void getNumberData(List<? extends Number> data) {
System.out.println("Number data :" + data.get(0)); }}// Print the result
Number data: Awesome!
Number data: 123
Copy the code
Question: why variables with upper boundary wildcards are read-only and cannot be written?
1, the
, which limits the type of the current upper Bound class or a subclass of it. It cannot determine its own type, so the compiler cannot verify that the type is safe and therefore cannot write
2, If we can write, we add several subclasses to it, and then use a concrete subclass to receive, will cause a cast exception
3. The wildcard on the bottom boundary of the generic
allows inverting of generics. It limits the type of the current lower Bound class or its parent, or in the case of interfaces, the current lower Bound interface or its parent. Variables that use lower Bound wildcards are only written, not read
public class WildCard {
public static void main(String[] args) {
List<Number> numberList = new ArrayList<Number>();
List<Object> objectList = new ArrayList<Object>();
setNumberData(numberList);
setNumberData(objectList);
}
public static void setNumberData(List<? super Number> data) {
Number number = new Integer(Awesome!); data.add(number); }}Copy the code
Question: Why variables with lower bound wildcards can be written but not read?
1, the
, which limits the type of the current lower Bound class or its parent. Although it can’t be certain of its own type, it can guarantee that the element it adds is safe from polymorphism, so it can write
2. When fetching a value, it will return a value of type Object instead of the type represented by the actual type parameter. Therefore, do not read it
4. Unbounded wildcards for generics
1), no boundary wildcard syntax:
, which is actually equivalent to
, which means that its upper boundary is Object or a subclass of it, so variables that use unbounded wildcards are also read-only and cannot be written. Null can be added, but it makes no sense
public class WildCard {
public static void main(String[] args) {
List<String> stringList = new ArrayList<String>();
List<Number> numberList = new ArrayList<Number>();
List<Integer> integerList = new ArrayList<Integer>();
stringList.add("erdai");
numberList.add(Awesome!);
integerList.add(123);
getData(stringList);
getData(numberList);
getData(integerList);
}
public static void getData(List
data) {
System.out.println("data: " + data.get(0)); }}// Print the result
data: erdai
data: Awesome!
data: 123
Copy the code
5. PECS Principles
The design of generic code should follow PECS principles:
1) If you only need to get elements, use
2), if only need to store, use
// This is the source code for the copy method in collections.java
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
/ /...
}
Copy the code
This is a classic example of SRC representing the original collection, using
extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T> extends T = dest
6. Summarize with wildcard characters
1) when you want to read only values, use
2) when you want to write values, use
3) Do not use wildcards when you want to read and write values
5. Limitations of generics
1) Generics do not explicitly refer to operations of runtime types, such as instanceof operations and new expressions. Runtime types only apply to native types
public class GenericLimitedClass<T> {
private void test(a){
String str = "";
// The compiler does not allow this operation
if(str instanceof T){
}
// The compiler does not allow this operation
T t = newT(); }}Copy the code
You cannot create an array of a generic type. You can only declare a reference to an array of a generic type
public class GenericLimitedClass<T> {
private void test(a){
GenericLimitedClass<Test>[] genericLimitedClasses;
// The compiler does not allow it
genericLimitedClasses = new GenericLimitedClass<Test>[10]; }}Copy the code
3) Static fields of type generic cannot be declared
public class GenericLimitedClass<T> {
// The compiler does not allow it
private static T t;
}
Copy the code
4) Generic classes cannot directly or indirectly inherit Throwable
// The compiler does not allow it
public class GenericLimitedClass<T> extends Throwable {}Copy the code
5) You cannot capture instances of type parameters in methods, but you can use type parameters in throws statements
public class GenericLimitedClass<T> {
private <T extends Throwable> void test1(a) throws T{
try {
// The compiler does not allow it
}catch (T exception){
}
}
}
Copy the code
A class may not override methods that have the same method signature after type erasure
public class GenericLimitedClass<T> {
// The compiler does not allow it
private void test2(List<String> stringList){}private void test2(List<Integer> integerList){}}Copy the code
6, problem,
What is the difference between a type boundary and a wildcard boundary?
There can be multiple type boundaries and only one wildcard boundary
2), a List
is the same as List
Not the same
1, List
2, the List
can have many subclasses, but List
Kotlin generics
Kotlin generics are basically the same as Java generics, with some things written in a new way on Kotlin
Basic usage of generics
1) In Kotlin we define and use generics in the following way:
// define a generic class. Use
after the class name to define a generic class
class MyClass<T>{
fun method(params: T){}}// Generic calls
val myClass = MyClass<Int>()
myClass.method(12)
// define a generic method by adding
to the name of the method
class MyClass{
fun <T> method(params: T){}}// Generic calls
val myClass = MyClass()
myClass.method<Int> (12)
// According to the Kotlin type inference mechanism, we can omit generics
myClass.method(12)
// define a generic interface by adding
to the name of the interface
interface MyInterface<T>{
fun interfaceMethod(params: T)
}
Copy the code
Comparing generics in Java, we can see that there is no difference between defining class and interface generics. When defining method generics, Kotlin adds generics in front of method names, whereas Java adds generics in front of return values
2, boundary
2) If there are multiple boundaries, use the where keyword, separated by:. Only one of these boundaries can be a class, and the class must be the first
// Case 1 single boundary
class MyClass1<T : Number> {
var data: T? = null
fun <T : Number> method(params: T){}}// Use the WHERE keyword for multiple boundaries
open class Animal
interface Food
interface Food2
class MyClass2<T> where T : Animal.T : Food.T : Food2 {
fun <T> method(params: T) where T : Animal, T : Food, T : Food2 {
}
}
Copy the code
3. Generic implementation
Generic realizations do not exist in Java. Kotlin makes them possible because the inline functions used replace the code, so using generics in inline functions eventually replaces the actual types
1) Use the inline function and reified keyword to implement a generic type as follows:
inline fun <reified T> getGenericType(a){}Copy the code
Let’s do it:
inline fun <reified T> getGenericType(a) = T::class.java
fun main(a) {
// Generic realizations are erased by type in Java
val result1 = getGenericType<String>()
val result2 = getGenericType<Number>()
println(result1)
println(result2)
}
// Print the result
class java.lang.String
class java.lang.Number
Copy the code
2) Practical application
We usually do this when we jump an Activity
val intent = Intent(mContext,TestActivity::class.java)
mContext.startActivity(intent)
Copy the code
TestActivity::class. Java syntax: TestActivity::class. Java syntax: TestActivity::class.
// Define a top-level function
inline fun <reified T> startActivity(mContext: Context){
val intent = Intent(mContext,T::class.java)
mContext.startActivity(intent)
}
// When used
startActivity<TestActivity>(mContext)
Copy the code
When we jump to an Activity, we might carry some parameters like this:
val intent = Intent(mContext,TestActivity::class.java)
intent.putExtra("params1"."erdai")
intent.putExtra("params2"."666")
mContext.startActivity(intent)
Copy the code
At this point we can add a function type argument and use Lambda expression to call it as follows:
inline fun <reified T> startActivity(mContext: Context, block: Intent. () - >Unit){
val intent = Intent(mContext,T::class.java)
intent.block()
mContext.startActivity(intent)
}
// When used
startActivity<SecondActivity>(mContext){
putExtra("params1"."erdai")
putExtra("params2"."666")}Copy the code
Generic covariant, contravariant and invariant
1) generic covariant syntax rules:
is similar to Java
, which limits the type of the current upper Bound class or a subclass of it, or in the case of an interface, the current upper Bound interface or implementation class. Covariant generic variables are read-only, cannot be written to, can be added to, but have no meaning
open class Person
class Student: Person(a)class Teacher: Person(a)class SimpleData<out T>{}fun main(a) {
val person: Person = Student()
val personGeneric: SimpleData<Person> = SimpleData<Student>()
val list1: ArrayList<out Person> = ArrayList<Student>()
}
Copy the code
2), generic contravariant syntax rules:
similar to Java
defines the type of the current lower Bound class or its parent, or if it is an interface, the current lower Bound or its parent. Contravariant generic variables can only be written, not read
open class Person
class Student: Person(a)class Teacher: Person(a)class SimpleData<in T>{}fun main(a) {
val person1: Person = Student()
val personGeneric1: SimpleData<Student> = SimpleData<Person>()
val list2: ArrayList<in Person> = ArrayList<Any>()
}
Copy the code
5) Generic immutable and Java syntax rules are the same
open class Person
class Student: Person(a)class Teacher: Person(a)class SimpleData<T>{}fun main(a) {
val person: Person = Student()
// The compiler does not allow it
val personGeneric: SimpleData<Person> = SimpleData<Student>()
}
Copy the code
6) Kotlin uses the syntax structure <*> to represent unbounded wildcards, which is equivalent to
, similar to
class KotlinGeneric<out T: Number>{}// Unbounded wildcards are equivalent to
, but my class limits the generic boundary to Number, so this is equivalent to
.
fun main(a) {
val noBound: KotlinGeneric<*> = KotlinGeneric<Int> ()// The compiler does not allow this according to the covariant rule
val noBound: KotlinGeneric<*> = KotlinGeneric<Any>()
}
Copy the code
Generics summary
To learn Kotlin generics well, you need to learn Java generics well, and to conclude this article:
1. Answers some questions about generics
2. Java generics, where I think generic erasure and covariant, contravariant and invariant generics are difficult to understand, so you can spend more time understanding this area
3, explained Kotlin generics, compared to Java generics, Kotlin generics are a little different in syntax structure, but the function is exactly the same, and Kotlin generics implementation is not in Java
Ok, here, Kotlin generics is finished, I believe that if you see here from the beginning, harvest must be a lot, if you think I write well, please give me a thumbs up 🤝, if you have any questions, welcome to discuss in the comments section
Thank you for reading this article
Full text here, the original is not easy, welcome to like, collect, comment and forward, your recognition is the power of my creation