【问题标题】:Can one avoid argument mismatch in Java when using "? extends" in generic declaration?在泛型声明中使用“?扩展”时,可以避免 Java 中的参数不匹配吗?
【发布时间】:2018-09-22 06:55:54
【问题描述】:

我正在尝试创建一个函数来处理(可比较的)元素的排序列表。因此,我使用通用的<T extends List<? extends Comparable>>,只要我不需要任何需要<? extends Comparable> 作为输入的特定于列表的操作,它就可以工作。但是在下面的代码 sn-p(最简单的例子:计算两个排序列表的交集)中,C.add((Comparable)(A.get(posA))); 行被编译器拒绝,声称add 需要参数? extends ComparableComparable 显然不是。

public static <T extends List<? extends Comparable>> T intersect (T A, T B) {
    T C = (T) A.getClass().newInstance();
    int posA = 0;
    int posB = 0;
    while(posA<A.size()&&posB<B.size()) {
        if (A.get(posA).compareTo(B.get(posB))>0) posB++;
        else if (A.get(posA).compareTo(B.get(posB))<0) posA++;
        else if (A.get(posA).equals(B.get(posB))) {
            C.add((Comparable)(A.get(posA)));
            posA++; posB++;
        }
    }
    return C;
}

我应该如何告诉编译器A.get(posA) 是有效类型? extends Comparable?显然强制转换不起作用,我希望例程接受并返回任意可比较对象的列表(整数、字符串、自定义对象等)

【问题讨论】:

    标签: java generics inheritance


    【解决方案1】:

    您是否注意到代码中的所有不安全类型语句,即多个不安全类型转换?
    你真的有很多。这通常意味着整体方法不正确。

    事实上,如果您了解泛型在 Java 中的工作原理,事情并没有那么复杂。
    这可以帮助你:

    根据您的实际代码,您应该考虑以下主要事项: 1) 不要使用 List&lt;? extends Comparable&gt;&gt; 等原始类型。 Comparable 是一个泛型类。
    2) 您不能在声明为List&lt;? extends Foo&gt; 的列表中添加除null 之外的任何内容,即使用上限通配符。最后一个允许使 List 协变:接受 Foo 和任何子类,但具有先前的限制。所以你不想使用它。
    3) 您可以实例化一个泛型ArrayList,而无需为ArrayList 声明泛型方法类型T。将T 用于Comparable 类型会更简单。
    4)你想尽可能地避免反射。

    通过遵循这些想法,您可以编写如下代码:

    public static <T extends Comparable<T>> List<T> intersect (List<T> A, List<T> B) {
        List<T> list = new ArrayList<>();       
        int posA = 0;
        int posB = 0;
        while(posA<A.size()&&posB<B.size()) {
            if (A.get(posA).compareTo(B.get(posB))>0) posB++;
            else if (A.get(posA).compareTo(B.get(posB))<0) posA++;
            else if (A.get(posA).equals(B.get(posB))) {
                list.add(A.get(posA));
                posA++; posB++;
            }
        }
        return list;
    }
    

    这是我最初的方法,但这里的问题是,不是每个 两个非 ArrayList 列表的交集将是一个 ArrayList 在这里。

    如果您为参数声明List,则在编译时将不知道列表的类型。因此,您将不可避免地以不安全的演员表结束。
    例如:

    @SuppressWarnings("unchecked")
    public static <T extends Comparable<T>, L extends List<T>> L intersect(L A, L B)  {
    
        if (A.getClass() != B.getClass()) {
            throw new IllegalArgumentException("not same type between ...");
        }
        List<T> list = A.getClass()
                        .newInstance(); // uncheck
    
        int posA = 0;
        int posB = 0;
        while (posA < A.size() && posB < B.size()) {
            if (A.get(posA)
                 .compareTo(B.get(posB)) > 0)
                posB++;
            else if (A.get(posA)
                      .compareTo(B.get(posB)) < 0)
                posA++;
            else if (A.get(posA)
                      .equals(B.get(posB))) {
                list.add(A.get(posA));
                posA++;
                posB++;
            }
        }
        return (L) list; // uncheck
    }
    

    【讨论】:

    • 这是我最初的方法,但这里的问题是,并非两个非 ArrayList 列表的交集在这里都是 ArrayList。理想情况下,我会让返回类型与输入相同,即如果 A 和 B 是 LinkedList,那么返回的 C 应该是 LinkedList。
    • 好的。我更新以提供一种根据您的限制解决问题的方法。
    • 太好了,谢谢!更新的版本就像一个魅力。
    【解决方案2】:

    这是一个典型的泛型问题。

    你是说你接受所有扩展 Comparable 的对象列表。

    因此,另一个方法将 List 传递给您的方法会非常好。

    当然,不应允许您在代码中向该列表添加数字,即使它会扩展 Comparable!

    您可以通过在方法签名中添加另一个通用参数而不是“?”来解决代码中的这个问题。然后您可以在代码中强制转换为该参数。

    【讨论】:

    • 如果不更改函数的语法(必须是 .intersect(List&lt;&gt;,List&lt;&gt;) 返回与两者相同的对象类型的 List&lt;&gt;输入列表)。你能给我举个例子吗/如何做到这一点?
    • 当然我可以从public static &lt;T extends Comparable&gt; List&lt;T&gt; intersect (List&lt;T&gt; A, List&lt;T&gt; B) { List&lt;T&gt; C = new ArrayList&lt;T&gt;(); 开始,但这需要我很难选择列表类型。理想情况下,我不会在这里指定列表类型(对于列表来说这不是那么重要,但对于 Sets 的类似运算符,重要的是两个 TreeSets 的交集返回一个 TreeSet 而两个HashSets 返回 HashSet)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-14
    • 1970-01-01
    • 1970-01-01
    • 2020-06-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多