Kotlin’s generic, reified generic implementation
Java generics
So let’s start with the simplest way to use JAVA generics
Generics were introduced after JAVA1.5. Before JAVA1.5, we didn’t have generics
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
arrayList.add(4);
for (Object s : arrayList) {
System.out.println((String) s);
}
}
/ / output
1
2
3
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at TestDemo.main(TestDemo.java:16)
Copy the code
I’m going to start by creating a collection that is of type Object and I can insert any type
The above code should have been fine, but then it inserted an int into the last collection and then it would send a cast exception when it was strong. Using a collection without generics, it would be very easy to insert more than two types into the collection and if the types were not consistent during the cast it would get an exception. Such collections are not type safe
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList();
arrayList.add("1");
arrayList.add("2");
arrayList.add("3");
arrayList.add(4);// There will be an error because.add(int o); The method of
for (String s : arrayList) {
System.out.println(s);
}
}
Copy the code
You can see that by adding generics we can detect errors at compile time and make changes to prevent unexpected crashes at run time.
What do generics solve for us?
- Multiple types of the same executing code get better reuse
- Type safety Using generics improves the stability of our code without having to worry about casting type discovery exceptions to crash
The use of generics
A generic class
class Box<T> {
private T t;
public T getBox(a) {
return t;
}
public void setT(T t) {
this.t = t;
}
}
Copy the code
A generic interface
// Define the generic interface
interface Base<T>{
T get(a);
}
// Implement generic interface 1
class View<T> implements TestDemo.Base<T> {
@Override
public T get(a) {
return null;
}
}
// Implement generic interface 2
class View implements TestDemo.Base<String> {
@Override
public String get(a) {
return "";
}
}
Copy the code
Generic method
public <T> T getEntity(T t) {
return t;
}
Copy the code
So that’s the basic operation of generics and using generics covariant and contravariant is going to be left to Kotlin and Kotlin is going to be a little bit easier to understand.
Generic type erasure
One last word about generics in Java is generic type erasure
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
System.out.println(strings.getClass().equals(integers.getClass()));
}
Copy the code
Before you know generic type erasure you should think that the output is false and then the result is true.
Generics become Object when they are erased at compile time so you can see why it’s true. Is equal to
List<Object> objects = new ArrayList<>();
List<Object> objects2 = new ArrayList<>();
objects.equals(objects2)
Copy the code
We can verify that with an array
public static void main(String[] args) {
String[] strings1 = new String[0];
int[] ints = new int[0];
System.out.println(ints.getClass().equals(strings1.getClass()));
}
Copy the code
The result is a false array that has no type erasure
So why does Java do type erasure? Earlier we pointed to the fact that there were no generics before java1.5 and then the introduction of generics after 1.5 so why was it compatible with earlier versions of 1.5 so type erasers were used. However, type erasure also brings a number of problems. All LANGUAGE generics based on the JVM are implemented through type erasure. Kotlin solves the problem of generic erasure with a few tricks.
Generics in Kotlin
So much for Generics in Java and then Kotlin
Kotlin claims to be 100% Java compatible, which kotlin must have since it is based on JVM language types, and erasing Kotlin is no exception.
fun <T> printNum(t: T) {
println(t)
}
fun main(a) {
printNum("11")
printNum(1)
printNum(1.234)ko
}
Copy the code
You can see that generics are pretty much the same as using Java and then we’re going to move on
Generics materialize
inline
Generic materialization sounds amazing. Doesn’t the JVM do type erasure in the compiler? Next, we use inline from Kotlin to implement generic implementations of inline. Since this chapter is mainly about generics, inline is not covered too much.
The official description
There are some run-time efficiency costs associated with using higher-order functions: each function is an object, and a closure is captured
The package. That is, the variables that are accessed in the body of the function. Memory allocation (for function objects and classes) and virtual calls are introduced
Run time overhead.
“
Kotlin’s function is declared inline with an inline modifier. This function is directly inlined into the source code by the compiler
fun main(a) {
hello()
println("world")
}
private inline fun hello(a) {
println("Hello")
}
Copy the code
The above is a simple inline function generated by Java code decompiled by bytecode
This is the code generated by the inline function
public static void main(a) {
String var1 = "Hello";
System.out.println(var1);
String var3 = "world";
System.out.println(var3);
}
Copy the code
This is the code generated by ordinary functions
public static void main(a) {
hello();
String var0 = "world";
System.out.println(var0);
}
private static final void hello(a) {
String var0 = "Hello";
System.out.println(var0);
}
Copy the code
reified
When using inline functions, you can find a generic implementation by adding the reified modifier to the front of the declared generic
fun main(a) {
isString(1)
isString("")
}
private inline fun <reified T> isString(t: T) {
println("" is T)
}
false
true
Copy the code
It’s almost impossible for Java to implement generics with syntax like IS, T:class: Java. So what’s the benefit of this generic implementation?
- We can get the type of the generic
- The syntax is much more concise
For example, the normal syntax for starting another activity during Android development is as follows
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
Copy the code
Now let’s change it
startActivity<MainActivity>(this)
inline fun <reified T> startActivity(context: Context) {
val intent = Intent(context, T::class.java)
context.startActivity(intent)
}
Copy the code
Does it make the code more elegant? That’s right. Now, Gson Retrofit is something you can do to make your code more elegant.
Generic covariant
Covariant and contravariant generics are not very common but Kotlin uses a lot of covariant and contravariant features and the List we use is one that is inherently covariant, so let’s talk about covariant.
The place where you take the parameter is in and the place where you return the value is out and those two things you have to remember are covariant and contravariant just because of this picture.
open class A
class B : A(a)
fun main(a) {
val b = B()
get(b)
}
fun get(a: A) {
/ /...
}
Copy the code
This code is fine, right? Let’s try adding generics
open class A
class B : A(a)
fun main(a) {
val b = B()
val mutableListOf = mutableListOf(b)
get(mutableListOf)// There is an error here
}
fun get(a: MutableList<A>) {
/ /...
}
Copy the code
Found an error while calling GET. Why is that wrong? In the first piece of code you pass in an instance of B in the get function and that’s fine because B is A subclass of A why is the second generation of code wrong because MutableList is not A subclass of MutableList so you can’t use that. But now let’s change the code
open class A
class B : A(a)
fun main(a) {
val b = B()
val list = listOf(b)
get(list)
}
fun get(a: List<A>) {
/ /...
}
Copy the code
Isn’t it amazing that you can see that your code is okay and why is that because we talked about lists being covariant by nature let’s look at the source code for lists
public interface List<out E> : Collection<E> {
.
override fun iterator(a): Iterator<E>
public fun listIterator(a): ListIterator<E>
public fun listIterator(index: Int): ListIterator<E>
public fun subList(fromIndex: Int, toIndex: Int): List<E>
}
Copy the code
List is preceded by an out keyword. The out keyword is a covariant declaration, so the List is now only output about generics. This is why the collection declared by List is read-only and not writable.
Let’s go back and see if the picture above makes sense.
So let me summarize this a little bit
- B is A subclass of A but MutableListNot MutableListA subclass of
- If we add the out keyword and make it read-only that doesn’t make it any less type-safe.
- When we add the out keyword we can only make generics appear on the return value if you look at the picture above
Generic inverter
Contravariant, as opposed to covariant, which can only be written but not read, uses the in keyword before declaring generics
open class A
class B : A(a)
fun main(a) {
val a = A()
val mutableListOf = mutableListOf(a)
get(mutableListOf)
}
fun get(b: MutableList<in B>) {
/ /...
}
Copy the code
- The get function takes a MutableList argument
- We pass in a MutableList(note that B is A subclass of A)
- We’re not allowed to do this by declaring generics in get as contravariant additionsinKeyword At this point the generic can only be written but not read
- The covariant and contravariant bounds of generics are completely opposite if you look at the figure above
So we’re done with covariant and contravariant generics and we’re going to be using them more and more as we go through Kotlin and more and more.
More code than theory and more experimentation will help you understand the use of generics.