Kotlin series four ----- Generics

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

Generics in Java are invariant. For example, String is a subclass of Object, but List<String> is not a subtype of List<Object>. Let’s further try to see which cases in Java are typed. of
//Create a collection of objects
ArrayList< Object> objects = new ArrayList<>();
objects.add(str);
//Then create List<String>
ArrayList< String> strings = new ArrayList<>();
objects=strings;
Create a collection of object and strings respectively, and assign the type of string to object, an error will be reported at this time, which means that List<String> is not a subtype of List<Object>, we call addAll() to add string to object , the compilation is passed, and view the source code of addAll:

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 OK
At 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);
Because the object of object can be read from it, but I don't know which subclass of object it is, so I can't add an object. The above is a brief introduction to the wildcards of type variables using the existing collections in java. Let's take a look at generics Use when creating a class:
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> = string
A 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:
Inverse covariance in java:
TypeClass<Object> stringTypeClass = new TypeClass<>();
TypeClass<? super String> typeClass = stringTypeClass;
typeClass can accept objects of its parent class
Inverse covariance in kotlin:
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>){
 }
Array<T> is invariant on T , so neither Array<Int> nor Array<Any> is a subtype of the other. Why? Again, because copy can do bad things, that is, for example it might try to write a String to from , and if we actually pass an array of Int , a ClassCastException will be thrown after a while .                          
Well, the only thing we have to make sure is that copy() doesn't do anything bad. We want to prevent it from writing to from, using: from : Array<out Any>  , what happens here is called type projection : we say from is not just an array, but a restricted ( projected ) array: we only A method with a return type of type parameter T can be called as above, which means we can only call get() . This is how our use-case variant is used, and corresponds to Java's Array<? extends Object> , but in a simpler way.           
You can also use in to project a type:    
fun fill(dest: Array<in String>, value: String) {
// ……
}
Array<in String> corresponds to Java's Array<? super String> , that is, you can pass an array of CharSequence or an array of Object to the fill() function.                
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 parameter T with an upper bound  , which is  equivalent to  . This means that when   unknown, you can safely  read  from the value.TUpperFoo <*>Foo <out TUpper>TFoo <*>  TUpper
  • For  Foo <in T>, where  T is a contravariant type parameter, Foo <*> equivalent to  Foo <in Nothing>. This means that when  Tunknown, nothing can be written Foo <*> to in a safe way .
  • For  Foo <T>, where  is an  invariant type parameter T with an upper bound  , which is  equivalent to reading the value and equivalent   to writing the value  .TUpperFoo<*>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 express  Function<in Nothing, String>;
  • Function<Int, *> to express  Function<Int, out Any?>;
  • Function<*, *> said  Function<in Nothing, out Any?>.

Note : Star projections are very similar to Java's primitive types, but are safe.

Generic function:
Generic functions are also widely used in java. Generic functions only define the method and logic of their execution, and can process different types of data at the same time. The form of use is relatively simple:
private <T> void test(T t){
t.toString();
}
此时传入任何对象都会调用其toString的方法。
kotlin中的使用和java中基本一致,都是在修饰次之后。方法名之前声明参数类型:
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() }
}
类型擦除:
在运行和编译过程中,所有泛型只会被认为是同样的对象,不再区分具体的类型数据,简单的说虽然你传入的时object的不同子类,但对于系统来说他们都只是object,擦去其自己的类型。
Kotlin 为泛型声明用法执行的类型安全检测仅在编译期进行。 运行时泛型类型的实例不保留关于其类型实参的任何信息。 其类型信息称为被擦除。例如,Foo<Bar> 与 Foo<Baz?> 的实例都会被擦除为 Foo<*>。





Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325773369&siteId=291194637