Overview: The convenience and flexibility of generics in java, I believe most programmers have a deep understanding, the use of generics provides infinite possibilities for code encapsulation, and good things naturally have to be preserved, and Kotlin also provides generics This article will briefly summarize the use of generics in java and kotlin from the perspective of their own learning, so as to better understand the use of generics. Let's start learning. Similar to Java, classes in Kotlin can also have type parameters, which may be the most basic use of generics:
class Box<T>(t: T) { var value = t }Create a class Box to declare that it accepts parameters of type T. At this time, T is a generic type. When using it, specify the type of the incoming parameter. Now let's create an object:
val box: Box<Int> = Box<Int>(1)This kind of creation method, everyone has seen it, and it is not described too much here. One thing I mentioned when creating an object is that in kotlin, if the program can infer the type of the parameter, it is allowed to omit the parameter type:
val box = Box(1)The above simple example allows you to understand the basic use of generics in kotlin. Next, we will learn more in detail by comparing with java.
1. Covariation
//Create a collection of objects ArrayList< Object> objects = new ArrayList<>(); objects.add(str); //Then create List<String> ArrayList< String> strings = new ArrayList<>(); objects=strings;
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }The parameter here is not passed in E but passed in? extends E, the wildcard type parameter? extends E means that this method accepts a collection of objects of E or some subtype of E, not just E itself. This means that we can safely read E from it (the elements in the collection are instances of a subclass of E), but not write because we don't know what objects conform to that unknown subtype of E. This restriction, in turn, allows Collection<String> to be represented as a subtype of Collection<? extends Object>. In short, a wildcard type with an extends qualification (upper bound) makes the type covariant.
Now we modify the above object collection:
ArrayList<? extends Object> objects = new ArrayList<>(); objects=strings; Compiled OKAt this point, adding a collection to it will cause a compilation error, that is, only the object property in it is allowed to be read, and data is not allowed to be written:
//At this time, the code written below reports an error: objects.addAll(integers); objects.addAll(strings); objects.add(str); //Indicates that the collection cannot be added, Can read Object o = objects.get(0);
public class TypeClass<T > { }Next we also create the object:
TypeClass<String> stringTypeClass = new TypeClass<>(); TypeClass<Object> objectTypeClass = stringTypeClass;//报错 TypeClass<String> stringTypeClass = new TypeClass<>(); TypeClass<? extends String> objectTypeClass = stringTypeClass;//通过
Covariance in Kotlin:
Covariant at declaration:
To fix this we have to declare the type of the object as <? extends Object> which is pointless as we can call all the same methods on that object as before, so the more complex type doesn't bring value. But the compiler doesn't know that. In Kotlin, there is a way to explain this situation to the compiler. This is called declaration-site variation: we can annotate a class's type parameter T to ensure that it is only returned (produced) from <T> members, and never consumed. For this we provide the out modifier:class TypeClass<out T> { } var string = TypeClass<String>() var any : TypeClass<Any> = stringA generic T marked out can accept the type of its subclasses when the object created is Any. In addition to out , Kotlin adds another type variable annotation: in . It makes a type parameter contravariant : it can only be consumed but not produced:
TypeClass<Object> stringTypeClass = new TypeClass<>(); TypeClass<? super String> typeClass = stringTypeClass;typeClass can accept objects of its parent class
class TypeClass<in T> { } var any = TypeClass<Any>() var String : TypeClass<String> = any
Type covariance:
Declaring the type parameter T as out is very convenient and avoids the hassle of subtyping where it is used, but some classes can't actually be restricted to just returning T!Following the example above. Consider a situation: in Java
private void action(){ ArrayList<String> from = new ArrayList<>(); ArrayList<Object> to = new ArrayList<>(); copy(from,to);//Error: because the Object in from is not covariant } private void copy(ArrayList<Object> from ,ArrayList<Object> to){ }At this time, it is not possible to covariate the use declaration in ArrayList, so the solution is to covariate in the copy method when using it, and modify it as follows:
private void copy(ArrayList<? extends Object> from ,ArrayList<Object> to){ }At this point, from covariance can accept its subtype, and the situation in Kotlin is basically the same:
fun action(){ var from : Array<Int> = arrayOf(3) var to : Array<Any> = arrayOf("B") copy(from,to) } // use out declaration here to use covariant fun copy(from : Array<out Any> ,to :Array< Any>){ }
fun fill(dest: Array<in String>, value: String) { // …… }
star projection
Sometimes you want to say that you don't know anything about a type parameter, but still want to use it in a safe way. The safe way here is to define such a projection of a generic type that every concrete instantiation of that generic type will be a subtype of that projection.
Kotlin provides the so-called star projection syntax for this:
- For
Foo <out T>
, where is a covariant type parameterT
with an upper bound , which is equivalent to . This means that when unknown, you can safely read from the value.TUpper
Foo <*>
Foo <out TUpper>
T
Foo <*>
TUpper
- For
Foo <in T>
, whereT
is a contravariant type parameter,Foo <*>
equivalent toFoo <in Nothing>
. This means that whenT
unknown, nothing can be writtenFoo <*>
to in a safe way . - For
Foo <T>
, where is an invariant type parameterT
with an upper bound , which is equivalent to reading the value and equivalent to writing the value .TUpper
Foo<*>
Foo<out TUpper>
Foo<in Nothing>
If the generic type has multiple type parameters, each type parameter can be projected separately. interface Function <in T, out U>
For example, we can imagine the following star projection if the type is declared as :
Function<*, String>
to expressFunction<in Nothing, String>
;Function<Int, *>
to expressFunction<Int, out Any?>
;Function<*, *>
saidFunction<in Nothing, out Any?>
.
Note : Star projections are very similar to Java's primitive types, but are safe.
Generic function:
private <T> void test(T t){ t.toString(); }此时传入任何对象都会调用其toString的方法。
fun <T> test(t : T){ t.toString() }
泛型约束:
虽然泛型是可以传入不同的类型,但任何事情都没有绝对的自由,所以泛型的泛也是在一定范围内的泛型,那这个范围的控制就是泛型约束,在java中的约束采用 extends 约束上限,只允许传入其本身和其子类,super限定传入其父类,在kotlin中的范围限制:fun <T : Comparable<T>> sort(list: List<T>) { // …… }冒号之后指定的类型是上界:只有 Comparable<T> 的子类型可以替代 T。默认的上界(如果没有声明)是 Any?。在尖括号中只能指定一个上界。 如果同一类型参数需要多个上界,我们需要一个单独的 where-子句:
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String> where T : CharSequence, T : Comparable<T> { return list.filter { it > threshold }.map { it.toString() } }