When you’re learning about Kotlin generics, you’ll often see the words “in out”, “in” and “out”, why “out”? Why do you use “in”? What is out and when is in? Corresponding to the above question before I was confused, not very understand, so I decided to write this article to sort out, to find out what happened.

Java generics

Before we learn about Kotlin generics, let’s review generics in Java and introduce the following classes for illustrative purposes

Specific code

public class Animal {}public class Dog extends Animal{}public class Cat extends Animal{}public class Corgi extends Dog {}public class Result<T>{
    private T data;
    
    public Result(a) {}public Result(T data) { this.data = data; }
    
    public void setData(T data) { this.data = data; }

    public T getData(a) {   returndata; }}Copy the code

1. Be inflexible

Result<Dog> dogResult = new Result<>();
Result< Animal> animalResult = dogResult; // Error compiling
Copy the code

Although Dog is a subclass of Animal, Java generics cannot be typed. Result

objects cannot be assigned to Result

. There is no relationship between them. If Java generics are not designed this way they are prone to runtime exceptions, for example

 Result<Dog> dogResult = new Result<>();
 Result<Animal> animalResult = dogResult; // Compiler error ❌
// Assuming the above code at 👆 compiles
// We can call set to set an Animal object
 animalResult.setData(new Animal());
// If dogResult's get method is Dog but it is Animal, the ClassCastException will occur
 Dog dog = dogResult.getData();
Copy the code

So Java is designed this way for security reasons. For security, such a restriction would obviously take away some of the FLEXIBILITY of the API. So Java provides restricted wildcards? Extends, X? Super X,
to improve API flexibility.

2. Type change wildcard –? extends

Result<Dog> dogResult = new Result<>(new Dog());
  // The generic type can be Animal or a subclass of Animal
Result<? extends Animal> animalResult = dogResult; // The compiler passes
// animalResult=new Result(new Object()); // Parent class failed, compilation error ❌

//Result
       modify data data, ensure the security of the data, won't make dogResult. GetData () abnormal data conversion
// animalResult.setData(new Animal()); // Error compiling

//Result
       The generic restriction guarantees that animalResult.getData() is an Animal object.
// So it is ok to get data from animalResult
 Animal animal = animalResult.getData();
Copy the code

can mean that a generic is either X or a subclass of X, so Result
animalResult = dogResult; This is possible, but for safety, animalResult can only read data, not write data, to prevent type exceptions.

can safely be used to read data (functions that return X) but not to write data (functions that take X as an argument) because it is uncertain whether the argument needs X or a subclass of X, As in the example above animalResult points to a Result so setData should be put into a Dog object, but animalResult can also point to a Result

object, We can only determine if the object generic that animalResult refers to is Animal or a subclass of Animal, but we cannot determine which type it is (Dog, Cat, etc.). So when we setData, we can’t confirm which object to put. For security reasons, writing data in this wildcard case is not allowed.

Conclusion? Extends wildcard

  1. Cat, Dog and Corgi are all subclasses of Animal, soResult<Cat>Result<Dog>Result<Corgi>Can be usedResult<? extends Animal>Said. ? The extends wildcard defines an upper bound. The type of a generic can be Animal or a subclass of it, but cannot exceed it, that is, cannot be its parent.
  2. This wildcard, which can only be read but not written, is often called a consumer.

3. Inverse wildcard –? super

 Result<Dog> dogResult = new Result<>(new Dog());
 Result<Object> objResult = new Result<>(new Object());
// A generic type can be Animal or its parent
 Result<? super Animal> animalResult = objResult; // The compiler passes
 // error: Dog is a subclass of Animal
/ / animalResult = dogResult ❌

// Can write, use the set method
animalResult.setData(new Animal());
// If read, the return value is Object
Object data = animalResult.getData();
Copy the code

can indicate that a generic is X or a parent of X, so Result
animalResult = objResult; Yes, but animalResult=dogResult is not available. This wildcard restricts generics to be X or superclass of X, so the wildcard is safe to write to (functions of parameter X) but the type passed in must be X or a subclass of it, because? Super X guarantees that the generic is X or its parent, and depending on the polymorphic nature of the class, subclasses can be used instead of the parent. If you read it, you can’t confirm the exact type, because you only know that it’s X or its parent, but you don’t know which one. All the returned types are their top-level parent, Object.

Conclusion? Super wildcard

  1. You can invert it. ? The super wildcard defines the lower bound. The type of a generic can be Animal or its parent, but no lower, that is, cannot be a subclass of Animal.
  2. This wildcard, which can be written but cannot be typed, is often called a producer.

4. Use wildcards to qualify parameters

? Extends X as an argument

For example, the addAll method in the Collections framework in Java

public interface Collection<E> extends 可迭代<E> { boolean addAll(Collection<? extends E> items); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -// Suppose you declare an ArrayList
      
        then its element can be Animal or a subclass of it
      
ArrayList<Animal> list = new ArrayList<>();
/ / 0 ️ ⃣
list.addAll(new ArrayList<Dog>());
list.addAll(new ArrayList<Cat>());
/ / 1 ️ ⃣
// list.addAll(new ArrayList()); Compilation error ❌

Copy the code

The declared Collection type is ArrayList so addAll’s parameter type is Collection
this defines that the collection generic must be Animal or a subclass of Animal. This guarantees that the element added to the list by addAll must be an object of Animal or a subclass of Animal.

? Super X as the argument

 public void forEach(Consumer<? superE > action) {...for(int i = 0; this.modCount == expectedModCount && i < size; ++i) {
            / /? The super E wildcard can call write methods (functions with arguments of E)action.accept(elementAt(es, i)); }... } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - the ArrayList < Animal > list = new ArrayList < > ();/ / 0 ️ ⃣
 list.forEach(new Consumer<Animal>() {
            @Override
            public void accept(Animal data) {
                System.out.println(data); }});/ / 0 ️ ⃣
 list.forEach(new Consumer<Object>() {
            @Override
            public void accept(Object data) {
                System.out.println(data); }});Copy the code

The forEach parameter type in the above code is Consumer
, so the generic can be Animal or its parent, so it can be either Consumer

or Consumer
.

Kotlin generic

Kotlin generics are similar to Java, though not? Extends and? A wildcard like super, but a modifier like in out is easy to understand, right? Extends corresponds to out,? Super corresponds to the in

1. Be inflexible

val dogResult = Result<Dog>()
val animalResult: Result<Animal> = dogResult // Error compiling
Copy the code

Consistent and Java

2. Type change modifier — out

    val dogResult = Result(Dog())
    // The generic type can be Animal or a subclass of Animal
    val animalResult: Result<out Animal> = dogResult // The compiler passes
// animalResult=new Result(new Object()

// animalresult.data =Animal()// Compilation error
    
    val animal = animalResult.data
Copy the code

And Java? Extends is consistent, having a type variant line that can be read but not written

3. Type modifier — int

    val dogResult = Result(Dog())
    val objResult = Result<Any>(Any())
    
    // A generic type can be Animal or its parent
    val animalResult: Result<in Animal> = objResult // The compiler passes
    // error: Dog is a subclass of Animal
   / / animalResult = dogResult ❌
    
    // Can write, use the set method
    animalResult.data = Animal()
    // The return value is Any? The specific type cannot be confirmed
    val data:Any? = animalResult.data
Copy the code

And Java? Super consistent, reversible, writable and unreadable

Kotlin generics – declaration variant

In Java generics, we listed the ways in which wildcards can be used to qualify parameters. We saw how the JAVa.util.arrayList Api uses wildcards to improve the flexibility of the Api while ensuring data security. Kotlin can also follow? Extends replaces out,? The way super replaces in qualifiers can also achieve the same effect as Java. But if you look at kotlin. Collections. The ArrayList addAll method isn’t what we expected. AddAll method of java.util.ArrayList

 public boolean addAll(Collection<? extends E> item)
Copy the code

According to the above analysis, we think kotlin. Collections. The ArrayList addAll is probably so

   fun addAll(elements: Collection<out E>): Boolean
Copy the code

But the actual source code is written like this

override fun addAll(elements: Collection<E>): Boolean
Copy the code

The argument type is Collection

instead of Collection

and although there is no out, the argument can still be limiting.

    val list = mutableListOf<Animal>() //public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()
    list.addAll(mutableListOf<Dog>())
// list.addall (mutableListOf
      
       ()) compilation error
      
// list.addall (mutableListOf
      
       ()) compilation error
      
Copy the code

If you look at the Collection, you’ll see that it adds out to the definition

public interface Collection<out E> : 可迭代<E> {
 
    public val size: Int... }Copy the code

This is a type that specifies out in a class or interface definition, which is called declarative type-variant. An interface or class that declares it so that it can only provide methods for reading, but not methods for writing, is a type that we intend to make type-variant. For example, I changed the Result above to a declaration type

// Original Java to Kotlin
class Result<T>(var data: T? = null)
Copy the code

Var = val; var = val; var = val



So we’re covariant when we use Result

 val dogResult:Result<Dog> = Result(Dog())
    // The default is covariant, so the Result
      
        object is assigned to the Result
       
         variable
       
      
  val animalResult: Result<Animal> = dogResult // The compiler passes
  val animal = animalResult.data
Copy the code

When you declare the type of out, you don’t need to write it

The same thing with out, in, we can specify on a class or an interface

class Result<in T>{
    private var data: T? = null
    
    fun setData(data: T?).{ this.data=data} compile error, write only, cannot read// fun getData(): T? =data
}
Copy the code

So we’re contravariant when we use Result

    val objResult: Result<Any> = Result()
    val animalResult: Result< Animal> = objResult // The compiler passes
    // error: Dog is a subclass of Animal
    / / animalResult = Result < Dog > () / / ❌
    // Can write, use the set method
    animalResult.setData(Animal())
Copy the code

Kotlin the where

Before looking at Kotlin’s Wherect, let’s look at similar syntactic generic functions in Java

// Define a generic type to be declared with 
      
        before a method returns a type
      
public static <T> void test(T data) {} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -// Use also put any type
test("");
test(new Animal());
Copy the code

Generic functions with restrictions

/ / define
public static <T extends Animal>  void test(T data) {} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -// Only Animal or its subclass can be passed in
test(new Dog());
test(new Animal());
Copy the code

Generic functions with multiple constraints

 // Define multiple qualifiers & connections can have only one class and must be in the first place, and can have multiple interfaces
public static <T extends Animal & Serializable & Closeable> void test(T data) {}

    static class Data extends Animal implements Serializable.Closeable{

        @Override
        public void close(a) throws IOException {}}public static void main(String[] args) {
        // The received generic type must inherit from the Animal implementation Serializable,Closeable interface
        test(new Data());
// test(Animal()); // Error compiling
    }
Copy the code

Where in Kotlin is a generic function that implements several restrictions in Java

fun <T> test(data: T) where T : Serializable, T : Animal, T : Closeable {}

 class Data : Animal(), Serializable.Closeable {
    override fun close(a) {}}fun main(a) {
    test(Data())
// test(Animal())// error compiling
}
Copy the code

Reference documentation

Generics: in, out, where – Kotlin language Chinese station