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?
  1. Multiple types of the same executing code get better reuse
  2. 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>{

        get(a);

}

// Implement generic interface 1

class View<Timplements 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> 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.

image-20200802141342399

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

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

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.