【问题标题】:Lower-Bounded Wildcards Java - Access to Methods下界通配符 Java - 访问方法
【发布时间】:2017-02-05 10:48:02
【问题描述】:

我了解下界通配符存在的一个原因是,在添加新元素时集合不是不可变的。

例如

List<? extends Number> obj = new ArrayList<>();//Now this list is immutable
obj.add(new Integer(5));//Does not compile
List<? super Number> objTwo = new ArrayList<>();//This list is mutable
objTwo.add(new Integer(5));//Compiles

以下内容无法编译,因为我试图获取数字的长值。

Q1:我可以使用哪些方法?只有对象方法?:

public void testLowerBounds(List<? super Number> numbers){
        if (!numbers.isEmpty()){
           System.out.println(numbers.get(0).longValue());//Does not compile
        }    

}

我的问题是如何产生的: 我目前正在学习流,这本书指定了以下流方法:

Optional<T> min(Comparator<? super T> comparator)

并实现如下:

Stream<String> s = Stream.of("monkey", "ape", "bonobo");    
Optional<String> min = s.min((s1, s2) -> s1.length()—s2.length());

Q2:比较器在使用时如何允许使用字符串方法?

如果我必须回答 Q2:我会说可选是指定“您必须向我传递具有通用类型“String”或实现“String”的 Comparator 的实现。我这样说是否正确?

期待您的回复。

【问题讨论】:

  • 这似乎是两个不同的问题。关于第一个假设,当编译器阻止添加到列表时,它确实 not 意味着列表是不可变的。编译器只是遵守语言的静态类型检查规则。
  • 同样List 没有longValue() 方法。
  • 您好 AR.3 感谢您的反馈。我已经改变了你是对的问题,我正在检查列表方法而不是内容。它们确实是两个不同的问题,我只是想解释一下问题的由来。我明白你所说的不可变,但现在它在逻辑上是不可变的,因为编译器不允许你添加到列表中。您现在对这两个问题都有答案了吗?
  • 关于第一个问题,stackoverflow.com/questions/4343202/…已经详细回答了。
  • 首先,编译器必须推断 lambda 表达式 (s1,s2)-&gt;... 的类型,并限制它是 Comparator&lt;? super String&gt; 的子类型。推理规则选择Comparator&lt;String&gt;,这显然比Comparator&lt;CharSequence&gt;等其他选择更明智。一旦选择了 lambda 表达式的类型,我们现在就知道了 lambda 参数的类型s1,s2,它们都是String。因此,在 lambda 主体中,我们可以对它们调用 String 方法。请注意,在已知 lambda 参数类型之前,编译器无法查看 lambda 主体。

标签: java generics wildcard java-stream


【解决方案1】:

首先,您不应将通配符类型参数与可变性混淆。在List 的元素类型中使用通配符不会阻止修改,它只会对您可以对列表执行的操作施加一些实际限制。

声明一个像List&lt;? extends Number&gt; 这样的列表意味着引用的列表具有Number 的实际元素类型或Number 的子类,例如它可能是List&lt;Integer&gt;List&lt;Double&gt;。所以你不能添加任意的Number 实例,因为你不知道它是否与实际的元素类型兼容。

但您仍然可以添加null,因为已知null 引用与所有引用类型兼容。此外,您始终可以从列表中删除元素,例如拨打removeclear 没有问题。您还可以调用像Collections.swap(list, index1, index2) 这样的方法,这很有趣,因为由于通配符类型的正式规则,调用list.set(index1, list.get(index2)) 是不合法的,但是将列表传递给另一个可能使用非通配符类型变量的方法代表列表的元素类型作品。这显然是正确的,因为它只设置来自同一个列表的元素,必须兼容。

同样,如果你有Comparator&lt;Number&gt;,你可以调用Collections.sort(list, comparator),作为一个可以处理任意数字的比较器,将能够处理列表中实际存储的任何数字。

总而言之,在集合的元素类型中包含 ? extends不会阻止修改。


如前所述,您不能将任意新元素插入到实际元素类型可能是边界的未知子类的列表中,例如 List&lt;? extends Number&gt;。但是当你检索一个元素时,你肯定会得到一个Number 实例,因为Number 子类型的每个实例也是Number 的一个实例。当你声明一个List&lt;? super Number&gt; 时,它的实际元素类型可能是NumberNumber 的超类型,例如ObjectSerializable。您可以插入任意 Number 实例,因为您知道它将与列表具有的任何实际元素类型兼容,因为它是数字的超级类型。当你检索一个实例时,你只知道它是一个Object 的实例,因为它是所有实例的超类型。与? extends 的情况相比,? super 声明并不妨碍阅读,它只是施加了一些实际限制。同样,您仍然可以将其传递给 Collections.swap,因为无论我们对实际类型知之甚少,插入我们刚刚从同一个列表中检索到的内容都可以。

在您的第二个问题中,您混淆了双方。您现在查看的不是min 的实现,而是调用者min(Comparator&lt;? super T&gt; c) 的声明允许调用者传递任何使用T 或超类型T 参数化的比较器。因此,当您有Stream&lt;String&gt; 时,将Comparator&lt;String&gt; 传递给min 方法是有效的,这正是您通过(s1, s2) -&gt; s1.length()—s2.length() lambda 表达式实现的(不过,我更喜欢Comparator.comparingInt(String::length) )。

min 的实现中,确实不知道TComparator 的实际类型参数是什么。但是知道T 类型的任何流元素都可以传递给比较器的compare 方法就足够了,该方法可能期望TT 的超类型。

【讨论】:

  • 谢谢@Holger。你有一些很好的解释。我特别喜欢我的第二个问题的答案(顺便说一句,不错的 lambda,使用方法引用)。我明白你关于可变性的观点。对于我收集到的第一个问题的答案,您是说我只能访问 Object 方法,这是我所期望的,因为引用类型可能是 object,因为这是您当时可用的信息。
猜你喜欢
  • 1970-01-01
  • 2019-11-01
  • 2021-01-23
  • 1970-01-01
  • 2018-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多