12

这种协方差在 C# 中是可能的:

IEnumerable<A> a = new List<A>();
IEnumerable<B> b = new List<B>();

a = b;

...

class A {
}

class B : A {
}

这在 Java 中是不可能的:(Iterable: Seen in this question Java Arrays & Generics : Java Equivalent to C# IEnumerable<T>)。

Iterable<A> a = new ArrayList<A>();
Iterable<B> b = new ArrayList<B>();

a = b;

...

class A {
}

class B extends A {
}

使用 Iterable,Java 看不到这两个集合是协方差的

Java中哪个可迭代/可枚举接口可以促进协变?


协方差的另一个很好的例子,给定上面相同的 A 类和 B 类,这在 Java 和 C# 上都是允许的:

   A[] x;
   B[] y = new B[10];

   x = y;

从版本 1 开始,这两种语言都具有该功能。很高兴他们正在取得进展,以使其在泛型上成为现实。尽管在语法方面,C# 的摩擦较小。

所有 OOP 语言都必须使用协变,否则 OOP 继承将是无用的练习,例如

 A x;
 B y = new B();

 x = y;

这种权力也应该扩展到泛型。



感谢大家的回答和见解。现在有了一个具有协变能力的 Java 泛型的可重用方法。这不是我们中的一些人想要的语法,但 it( <? extends classHere>) 确实符合要求:

import java.util.*;    
public class Covariance2 {  
  
        public static void testList(Iterable<? extends A> men) {                 
                for(A good : men) {
                        System.out.println("Good : " + good.name);
                }
        }    

        public static void main(String[] args) {    
                System.out.println("The A"); 
                {
                        List<A> team = new ArrayList<A>();
                        { A player = new A(); player.name = "John"; team.add(player); }
                        { A player = new A(); player.name = "Paul"; team.add(player); }
                        testList(team);
                }

                System.out.println("The B");
                {
                        List<B> bee = new ArrayList<B>();
                        { B good = new B(); good.name = "George"; bee.add(good); }
                        { B good = new B(); good.name = "Ringo"; bee.add(good); }
                        testList(bee);
                }    
        }
}

class A { String name; }    
class B extends A {}

输出:

The A
Good : John
Good : Paul
The B
Good : George
Good : Ringo

如果有人对它在 C# 中的样子感兴趣

using System.Collections.Generic;
using System.Linq;

public class Covariance2 {  

        internal static void TestList(IEnumerable<A> men) {                 
                foreach(A good in men) {
                        System.Console.WriteLine("Good : " + good.name);
                }
        }    

        public static void Main(string[] args) {    
                System.Console.WriteLine("The A"); 
                {
                        IList<A> team = new List<A>();
                        { A player = new A(); player.name = "John"; team.Add(player); }
                        { A player = new A(); player.name = "Paul"; team.Add(player); }
                        TestList(team);
                }

                System.Console.WriteLine("The A"); 
                {
                        IList<B> bee = new List<B>();
                        { B good = new B(); good.name = "George"; bee.Add(good); }
                        { B good = new B(); good.name = "Ringo"; bee.Add(good); }
                        TestList(bee);
                }    
        }
}

class A { internal string name; }    
class B : A {}
4

3 回答 3

12

Java 泛型仅在通过通配符显式声明时才允许协变,以提供更严格的类型安全。这有效:

    Iterable<? extends A> a = new ArrayList<A>();
    Iterable<B> b = new ArrayList<B>();
    a = b;

但是,请注意,您现在无法通过引用添加任何内容,a因为它被声明为包含某些特定但未知的类的实例,这些类可能是 A 或其任何子类。通配符的行为通常是违反直觉的,并且会变得非常复杂,因此应适度使用它们。

于 2012-05-19T11:50:39.687 回答
1

泛型在 Java 中不是协变的。您必须以旧方式执行此操作,就像 C# 不支持泛型中的协变时一样。

但是,在 Java 中,您可以假装泛型可迭代对象是任何事物的可迭代对象,用问号表示。任何事物的列表只包含对象。

Iterable<A> a = new ArrayList<A>();
Iterable<?> b = a;
于 2012-05-19T11:23:40.047 回答
0

泛型集合中的这种协方差是一个坏主意,这是有充分理由的:

假设你这样做:

ArrayList<A> a = new ArrayList<A>();
ArrayList<B> b = new ArrayList<B>();

a.add(new A()); 
b = a;
B item=b.get(0); 

糟糕——一个只返回 B 类型对象的函数返回了 A 类型。这显然行不通。因此 Java 编译器不允许它以确保类型安全。

不过这没什么大不了的——简单的解决方法是只使用非泛型集合类或将泛型类型参数限制为公共超类(在本例中为 A)。

于 2012-05-19T11:37:17.357 回答