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
//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
- Cat, Dog and Corgi are all subclasses of Animal, so
Result<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. - 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
- 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.
- 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
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
// 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