【问题标题】:Practical use of Collections.max() signatureCollections.max() 签名的实际使用
【发布时间】:2015-05-03 10:17:29
【问题描述】:

这是Explanation of Collections.max() signature 的后续问题,接受的答案并未深入探讨此通配符的实际原因。

max 方法采用 Collection<? extends T>,我想不出这个通配符有帮助的实际案例。

我什至提到了Oracle More fun with wildcards,它指出

一般来说,如果您的 API 仅使用类型参数 T 作为 论点,它的使用应该利用下界通配符 (? 超级T)。相反,如果 API 只返回 T,您将给出您的 通过使用上限通配符 (? extends T)。

但我还是没听懂。甚至Java Generics and Collections book 也没有过多地谈论这个通配符背后的原因。

这个有实际用途吗?一个真实的用例会很棒。

【问题讨论】:

  • 也许根本就没有实际原因?只是遵循一般的 PECS 原则?至少我想不出来。

标签: java generics wildcard


【解决方案1】:

想象一下这两个签名:

static <T extends Object & Comparable<? super T>> T max1(Collection<T> coll);
static <T extends Object & Comparable<? super T>> T max2(Collection<? extends T> coll);

现在,让我们尝试构造函数类型。

Function<Collection<java.sql.Timestamp>, java.util.Date>

// This doesn't work:
Function<Collection<Timestamp>, Date> f0 = 
    (Collection<Timestamp> col) -> Test.<Date>max1(col);

// This works:
Function<Collection<Timestamp>, Date> f1 = 
    (Collection<Timestamp> col) -> Test.<Date>max2(col);

如您所见,使用显式类型,其中一种方法不再起作用。

类型推断在“现实世界”中“隐藏”了这个问题:

当然,你可以省略泛型类型参数Test.&lt;Date&gt;maxN()的显式绑定:

Function<Collection<Timestamp>, Date> f0 = 
    (Collection<Timestamp> col) -> Test.max1(col);
Function<Collection<Timestamp>, Date> f1 = 
    (Collection<Timestamp> col) -> Test.max2(col);

甚至:

Function<Collection<Timestamp>, Date> f2 = Test::max1;
Function<Collection<Timestamp>, Date> f3 = Test::max2;

并且类型推断会发挥它的魔力,因为上述所有内容都可以编译。

现在何必呢?

我们应该总是关心 API 的一致性,就像pbabcdefp has said in his answer。想象一下有一个额外的optionalMax() 方法(只是为了处理空参数集合的情况):

static <T extends ...> Optional<T> optionalMax1(Collection<T> coll);
static <T extends ...> Optional<T> optionalMax2(Collection<? extends T> coll);

现在,您可以清楚地看到只有第二个变体有用:

// Does not work:
Function<Collection<Timestamp>, Optional<Date>> f0 = 
    (Collection<Timestamp> col) -> Test.optionalMax1(col);
Function<Collection<Timestamp>, Optional<Date>> f2 = Test::max1;

// Works:
Function<Collection<Timestamp>, Optional<Date>> f1 = 
    (Collection<Timestamp> col) -> Test.optionalMax2(col);
Function<Collection<Timestamp>, Optional<Date>> f3 = Test::optionalMax2;

考虑到这一点,下面的 API 会感觉非常不一致,因此是错误的:

static <T extends ...> T max(Collection<T> coll);
static <T extends ...> Optional<T> optionalMax(Collection<? extends T> coll);

因此,为了保持一致(无论是否需要输入),方法应该使用Collection&lt;? extends T&gt; 签名。总是。

【讨论】:

    【解决方案2】:

    一个有用的实验是这样的:

    static <T extends Object & Comparable<? super T>> T max1(Collection<T> coll) {
        return max2(coll);
    }
    
    static <T extends Object & Comparable<? super T>> T max2(Collection<? extends T> coll) {
        return max1(coll);
    }
    

    这个编译的事实表明这两个签名接受完全相同的参数集。

    事实上,Collections 类的签名中有几个冗余的通配符。

    例如

    中不需要通配符
    static <T> void fill(List<? super T> list, T t)
    

    static <T> void copy(List<? super T> dst, List<? extends T> src)
    

    如果一个或另一个(但不是两个)通配符不存在,也会同样有效。

    那么为什么要包括它们呢?

    copy 的例子很好。 super 的使用强调dst 以只读方式使用,extends 的使用强调src 以只读方式使用。同样在fill 中,super 虽然不需要,但强调list 正在写入,但未读取。在max 中,extends 强调List 正在被读取但未被写入。对于 Collection 既可读取又可写入的方法,例如

    static <T extends Comparable<? super T>> void sort(List<T> list)
    

    没有使用通配符。

    所以总而言之,我不认为通配符有任何实际用途,但它是指示该方法使用Collection的方式的好方法。

    【讨论】:

    • 很好的实验。我试图找到与extends 一起编译但没有它就无法编译的东西,但没想到这样的测试表明没有这种情况。因此,除非手动使用 Collections.&lt;TheType&gt;max(...) 修复类型,否则确实没有区别。
    【解决方案3】:

    我相信它的实际用途更多的是 API 设计人员。

    Collections.addAll(Collection&lt;? extends E&gt;) 为例。如果Student 类和Teacher 类都从Person 类扩展,则您不能将List&lt;Student&gt;List&lt;Teacher&gt; 添加到您想要向其发送一些邀请的List&lt;Person&gt;

    另一个实际用途可能是防止 modifications 错误地添加非空元素(假设您无法通过 getNumberList() 控制 List 的类型)。

    在您的代码中,您获得了对 List&lt;Number&gt; 的引用,并且您希望防止由于错误而将某些内容添加到此列表中。

    // this would compile
    List<Number> workList = getNumberList();
    workList.add(new Integer(42));
    
    // this would not compile as ? might be List<Double>
    List<? extends Number> workList = getNumberList();
    workList.add(new Integer(42));
    

    【讨论】:

    • 正如另一个问题的回答中提到的,它作为一种防止修改的技术并不能很好地发挥作用,因为您仍然可以添加空值和删除元素。在这种情况下应该使用Collections.unmodifiableList()
    • @SergeyTachenov 我同意。我更改了答案以明确表示只会在编译时阻止添加非空元素。这不是一个防弹解决方案。另一边的Collections.unmodifiableList() 会在运行时失败。
    猜你喜欢
    • 2021-06-14
    • 1970-01-01
    • 2011-01-15
    • 2013-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-16
    相关资源
    最近更新 更多