Kotlin泛型和方差(与Java相比)

本文涵盖了Kotlin的泛型和差异的概念,并将其与Java进行了比较。 Kotlin泛型与Java的不同之处在于用户如何定义它们在子彩票3d字谜关系中的行为方式。 与Java相反,Kotlin允许在声明站点上定义方差,而Java只知道使用现场方差。

Kotlin Generics - 方差是什么?

许多编程语言支持子键入的概念,这允许实现代表关系的层次结构"A cat IS-an animal".  In Java, we can either use the extends keyword to change/expand behavior of an existing class (inheritance) or use implements to provide implementations for an interface.  According to 利斯科州的替代原则, every instance of a class A can be substituted by instances of its subtype B. 通常在数学中引用的单词方差,也用于描述如何在方法返回彩票3d字谜,彩票3d字谜声明,通用彩票3d字谜或数组中的复杂方面中的子彩票3d字谜 涉及所涉及的课程的遗传方向。我们需要考虑到三个术语:协方差,逆向和不变性。

实践中的差异(Java)

协方差

在Java中,需要覆盖方法 协商 in its return type, i.e., the return types of the overridden and the overriding method must be in line with the direction of the inheritance tree of the involved classes. A method treat(): Animal of class AnimalDoctor can be overridden with treat(): Cat in class CatDoctor, which extends AnimalDoctor. Another example of covariance would be type conversion, shown here:

public class Animal {}
public class Cat extends Animal {}
(Animal) new Cat() //works fine
(Cat) new Animal() //will not work

We can cast subclasses up the inheritance tree, while down-casting causes an error. This is also the case if we take a look at variable declarations. It isn’t a problem to assign an instance of Cat to a variable of type Animal, whereas doing the opposite will cause failure.

抵押品

抵押品, on the other hand, describes the exact opposite. In Java, we have to deal with it when working with generics, which we're going to look at later. To make it clear, we can imagine another programming language that allows contravariant method arguments in overriding methods (In Java, this would be an overloaded method instead). Let’s say we have a class ClassB which extends another class ClassA and overrides a method by changing the original parameter type T' to its supertype T.

ClassA::someMethod(t: T')
ClassB::someMethod(t: T)

You can see that the type hierarchy of method parameter t is contrary to the hierarchy of the surrounding classes. Up versus down the tree, someMethod is contravariant in its parameter type.

不变性

最后但并非最不重要的是,最简单的一个:不变性。当我们在以前在示例中刚刚看到的时候,我们可以在再次考虑Java中覆盖方法时,我们可以遵守此概念。覆盖方法必须仅接受与覆盖方法相同的参数。我们谈论 不变性 如果彩票3d字谜的彩票3d字谜,例如方法参数在超级和亚型中没有不同。

Java中收集彩票3d字谜的差异

我们要考虑的另一个方面是阵列和其他彩票3d字谜的通用集合。 Arrays in Java are covariant in their type, which means an array of Strings can be assigned to a variable of type Object [].

Object [] arr = new String [] {"hello", "world"};

但更重要的是,阵列是他们持有的彩票3d字谜的协变量。这意味着您可以将整数,字符串或任何彩票3d字谜的对象添加到对象[]。

Object [] arr = new Object [2];
arr[0] = 1;
arr[1] = "2";

Covariant arrays might appear quite handy but can cause errors at runtime. A variable of type Object [] can possibly reference an object of String []. What happens if we pass the variable to a method expecting an array of Objects? This method might want to add an Object to the array, which seems legit because the parameter is expected to be of type Object []. The problem is that the caller has no idea what the method is possibly going to put into their array which possibly causes an ArrayStoreException at runtime, which we see in this simplified example:

Object [] arr = new String [] {"hello", "world"};
arr[1] = new Object(); //throws: java.lang.ArrayStoreException: java.lang.Object 

通用收藏

As of Java 1.5, generics can be used to inform the compiler which elements are supposed to be stored in a particular collections instance (i.e. List, Map, Queue, Set, etc.). Unlike arrays, 通用集合是不变的 in their parameterized type by default. This means you can’t substitute a List<Animal> with a List<Cat>. It won’t even compile. As a result, it is not possible to run into unexpected runtime errors which makes generic collections safer than covariant arrays (有效的Java.)。作为一个缺点,我们并不是一开始于集合的亚型。幸运的是,用户可以在使用泛型时明确地指定彩票3d字谜参数的方差。我们打电话给这一点 使用现场方差.

协调集合

The following code example shows how we declare a covariant list of Animal and assign a list of Cat to it.

List<Cat> cats = new ArrayList<>();
List<? extends Animal> animals = cats;

一个像阵列仍然不同的协调列表,因为协方差在其彩票3d字谜参数中编码,这使得读者更加明显。我们只能从列表中读取,而编译器禁止添加元素。据说列表是一个 生产者 of Animals. The generic type ? extends Animal (? is the "wildcard" character) only indicates that the list contains any type with an upper bound of Animal, which could mean a list of Cat, Dog or any other animal. This approach turns the runtime error encountered with covariant arrays into the preferable compile error:

 public void processAnimals(List<? extends Animal> collection){
    Animal a = collection.get(0);
    Cat c = collection.get(0); //will not compile
    collection.add(new Dog()) //will not compile
}

Now, if your invoke processAnimals with a list of Cat, as a caller, you can be sure that the function won't add anything to it. The function itself can only retrieve Animals from the collection since that's what the wildcard syntax indicates.

逆变系列

It is also possible to work with contravariant collections, which we declare with a generic type parameter ? super Animal (lower bound of type Animal). A list like that may be of type List<Animal> itself or a list of any supertype of Animal, even Object. Like with covariant lists, we can't know which type the list really represents (again indicated by the wildcard). The difference is, we can not read from a contravariant list since it is unclear whether we will get Animals or just plain Objects. Writing to the list is permitted though since we know that the original caller expects Animals or its supertypes which makes it possible to add any subtype of Animal. The list is acting as a 消费者 of Animals:

public void addAnimals(List<? super Animal> collection) {
    collection.add(new Cat());
    collection.add(new Dog());
}

The function shown above accepts a contravariant list of Animals. As a caller of this function you could pass a List<Animal> and also List<Object> or lists of other possible supertypes of Animal. You can be sure that only animals or its subtypes will be added to your collection which means that, even after the method call, you are safe to retrieve animals from that list:

List<Animal> animals = new ArrayList<>();
addAnimals(animals);
//here we can still safely get Animals from the list, we don't care which subtype it actually has
Animal someAnimal = animals.get(0);

addAnimals(new ArrayList<Object>());

Joshua Bloch在他梦幻般的书中创造了一条经验法则 有效的Java.: "生产者 - 延伸,消费者超级(PEC)" which helps memorizing the relation between covariant producers and the extends keyword as well as between contravariant consumers and the super keyword.

与Kotlin泛型的差异

在我们看看一般途径的方差以及Java如何利用这些概念之后,我们现在想看看Kotlin如何处理它。 Kotlin与Java不同,当泛型时,也与数组结合使用,以几种方式看起来可能看起来有经验的Java开发人员。第一个也许是最舒适的差异是: Kotlin的数组是不变的. As a result, as opposed to Java, it is not possible to assign an Array<String> to a reference variable of type Array<Object>. That's great because it ensures compile-time safety and prevents runtime errors you may encounter in Java with its covariant arrays. 但仍然需要有一些其他方法可以使用亚型阵列或课程,我们将关注下一步。

声明 - 站点方差

As shown earlier, Java uses so-called "wildcard types" to make generics variant, which is said to be [the most tricky part[s] of Java’s type system] (http://kotlinlang.org/docs/reference/generics.html#type-projections). Since the user of particular generic types has to handle the variance every time a specific type is used, we refer to it as 使用现场方差。 Kotlin根本不使用通配符。相反,我们使用的kotlin 声明 - 站点方差. Let’s recall the initial problem again: Imagine, we have a class ReadableList<E> with one simple producer method get(): T. Java prohibits the assignment of an instance of ReadableList<String> to a variable of type ReadableList<Object> because generic types are invariant by default. To fix this, the user can change the variable type to ReadableList<? extends Object> and everything works fine. Kotlin approaches that issue differently. We can mark the type T as only being produced with the out keyword so that the compiler immediately understands: ReadableList is never going to consume any T, which makes it covariant in T.

abstract class ReadableList<out T> {
    abstract fun get(): T
}

fun workWithReadableList(strings: ReadableList<String>) {
    val objects: ReadableList<Any> = strings // This is OK, since T is an out-parameter
    // ...
}

As you can see, the type T is annotated as an out type via 声明 - 站点方差 - also called variance annotation. The compiler does not prohibit the use of T as a covariant type. A great example of a covariant collection in the standard library is List<T>:

val ints: List<Int> = listOf(1, 2, 3, 4)

fun takeNumbers(nums: List<Number>) {
    val number: Number = nums[0]
     nums.add(1) // add is not visible
}

takeNumbers(ints)

You can easily pass a List<Int> to a function accepting a List<Number> since List is only a producer of T and takeNumbers will never add something to the class. On the other hand, if the parameter nums was defined with type MutableList<Number>, the add would work just fine, but the caller could not pass in a List<Int> anymore and it would be evident that the method can add stuff to the list passed into it.

Of course, there is also a corresponding annotation to mark generic types as consumers, i.e., make them contravariant: in. Folks have been using the presented approach in C# successfully for some years already.

Kotlin规则记住:生产者出来,消费者

使用 - 站点方差,彩票3d字谜预测

Unfortunately, it's not always sufficient to have the opportunity of declaring a type parameter T as either out or in. Just think of arrays for example. An array offers methods for adding and receiving objects, so it cannot be either covariant or contravariant in its type parameter. As an alternative, Kotlin also allows use-site variance which we can apply using the already defined keywords in and out:

Array<in String> corresponds to Java’s Array<? super String> and Array<out String> corresponds to Java’s Array<? extends Object>

fun copy(from: Array<out Any>, to: Array<Any>) {
 // ...
}

The example shows how from is declared as a producer of its type and thus the copy method cannot do bad things like adding to the Array. This concept is called 彩票3d字谜投影 since the array is restricted in its methods: only those methods that return type parameter T may be called.

reified彩票3d字谜

与Java相同,通用彩票3d字谜仅在编译时可用 在运行时擦除。由于我们使用Generics来确保彩票3d字谜安全,这足以让这足够好。但有时,能够在运行时检索实际彩票3d字谜信息很棒。幸运的是,Kotlin附带了一个名为的功能 reified彩票3d字谜,这使得可以在运行时处理通用彩票3d字谜(例如,执行彩票3d字谜检查)。我在这方面描述了这个功能 堆栈溢出 文章并发布了一个 博客帖子 about it.

底线

In this article, we looked at the quite complex aspects of variance in the context of generics. We've used Java to demonstrate the concepts of covariance, contravariance, and invariance and compared it to Kotlin. Kotlin tries to simplify generics using different approaches such as 声明 - 站点方差 and also introduces more obvious keywords (in, out). In my opinion, Kotlin really improves and simplifies the usage of generics and additionally eliminates the problem of covariant arrays. Declaration-site variance simplifies client code a lot by liberating it from using complex declarations as known from Java's wildcard syntax. Also, even if we have to fall back to use-site variance, the syntax appears clearer and easier to understand. I know this topic is not the simplest one, but hopefully, some aspects were made a bit clearer in this article. If you still struggle with variance and generics in Kotlin, the book kotlin在行动中 是我应该阅读的推荐资源。

5 thoughts on “Kotlin泛型和方差(与Java相比)

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *