【问题标题】:Why is Predicate<? super SomeClass> not applicable to Object?为什么谓词<? super SomeClass> 不适用于对象?
【发布时间】:2018-05-09 08:26:46
【问题描述】:

假设我们有一个声明为Predicate&lt;? super SomeClass&gt; 的谓词。我天真地期望它适用于层次结构上的任何SomeClass 超类,包括Object

但是这个谓词不适用于Object。我收到以下错误:

Predicate 类型中的方法 test(capture#3-of ? super SomeClass) 不适用于参数(Object)

Demo.

为什么Predicate&lt;? super SomeClass&gt; 不适用于Object 的实例?

代码:

import java.util.*;
import java.lang.*;
import java.io.*;
import java.net.URL;
import java.util.function.Predicate;


/* Name of the class has to be "Main" only if the class is public. */
class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        Predicate<? super URL> p = u -> u.getFile().isEmpty();
        p.test(new Object());
    }
}

【问题讨论】:

  • 你真的要使用super吗?查看演示中的代码,您似乎想要“?扩展 SomeClass”?
  • @ewramner 这不是实用/真实的代码。我只是想了解&lt;? super T&gt; 的工作原理。
  • @ewramner 我认为您应该能够使用Object - 我已经证明不是这种情况。
  • 仅供参考:PECS,这是一个很好的经验法则。
  • 您在代码中需要的是Predicate&lt;Object&gt; 类型的变量,Predicate&lt;? super X&gt; 在设计上的行为类似于Predicate&lt;X&gt;,但在选择具体实现时允许更大的灵活性(没有= new Predicate&lt;? super X&gt;() ,您必须指定具体类型)。例如,您可以采用 Predicate&lt;Object&gt; 的实现,因为该代码在 X 上也可以正常工作。这与在 test 方法中允许任何 T 无关。

标签: java generics java-8 predicate


【解决方案1】:

对于Predicate&lt;? super SomeClass&gt; 变量,您可以分配Predicate&lt;SomeClass&gt; 实例或Predicate&lt;Object&gt; 实例。

但是,您不能将Object 传递给Predicate&lt;SomeClass&gt;test() 方法。你只能传递一个SomeClass 实例。

因此,您不能将Object 传递给Predicate&lt;? super SomeClass&gt;test() 方法

考虑以下几点:

Predicate<URL> p1 = u -> u.getFile().isEmpty();
Predicate<? super URL> p2 = p1;

p2 指的是Predicate&lt;URL&gt;,因此您不能将new Object() 传递给它的test() 方法。

换句话说,为了让编译器接受p.test(new Object()),它必须对可以分配给Predicate&lt;? super URL&gt; p 变量的任何 Predicate 有效。由于Predicate&lt;URL&gt; Predicate 可以分配给该变量,并且其test() 方法不能接受Object,因此编译器无法接受p.test(new Object())

顺便说一句,在您的具体示例中,您正在创建一个Predicate&lt;URL&gt;,而URL 是一个最终类。因此,您应该简单地将其声明为:

Predicate<URL> p = u -> u.getFile().isEmpty();

? super? extends 没有任何理由。

【讨论】:

  • 我知道它不应该起作用,但我不明白为什么它不起作用。我可能在这里遗漏了一些基本的东西。
  • 代码不是真实的/实用的,我只是想了解&lt;? super T&gt;的工作原理。
  • URL 是一个最终类不是删除? super URL 的理由,这仅适用于? extends URL 的情况。
【解决方案2】:

谓词已经有一个类型。谓词接受的类型在您尝试调用它时无法确定。它有一个类型,那个类型是 X 的某个超类;只是未知。

如果我有一个Predicate&lt;Collection&gt;,则可以将其引用为Predicate&lt;? super List&gt;。但这并不意味着它会接受任何Object? extends X 泛型类型并不意味着它会接受与给定约束匹配的任何东西。这意味着它是某种与给定约束匹配的未知类型的谓词。

Predicate<? super List> predicate = Collection::isEmpty;
predicate.test(new Object()); // Should this be valid? Clearly not

【讨论】:

  • “这意味着它是某种类型匹配给定约束的谓词。” - 但Object 是匹配给定约束的类型。
  • 但是Predicate&lt;? super X&gt; 不是匹配给定约束的任何类型 的谓词。它是 some unknown type 匹配给定约束的谓词。
  • 对不起,我不明白这个。 Object不能是这个“未知类型”吗?
  • @lexicore Predicate 已经一个类型。谓词接受的类型在您尝试调用它时无法确定。它有一个类型,该类型是X 的某个超类;只是未知。
  • 因为类型未知但必须是 URL 的超类。这就是super URL 的用途。因此可以传递一个 URL。
【解决方案3】:

Java 教程页面提供了一些关于使用泛型时upper / lower 有界通配符的有用信息。

它还提供了一些guidelines 用于使用通配符,这可以帮助您决定应该使用哪个通配符:

一个“入”变量 “in”变量为代码提供数据。想象一个带有两个参数的复制方法:copy(src, dest)。 src 参数提供 要复制的数据,所以它是“in”参数。 “出”变量 “out”变量保存在其他地方使用的数据。在复制示例 copy(src, dest) 中,dest 参数接受数据,因此它是 "out" 参数。

当然,有些变量同时用于“输入”和“输出”目的—— 指南中也提到了这种情况。

在决定是否使用时,可以使用“in”和“out”原则 通配符以及合适的通配符类型。下列 列表提供了要遵循的准则: 通配符准则:

An "in" variable is defined with an upper bounded wildcard, using the extends keyword.
An "out" variable is defined with a lower bounded wildcard, using the super keyword.
In the case where the "in" variable can be accessed using methods defined in the Object class, use an unbounded wildcard.
In the case where the code needs to access the variable as both an "in" and an "out" variable, do not use a wildcard.

这些准则不适用于方法的返回类型。用一个 应避免使用通配符作为返回类型,因为它强制 程序员使用代码来处理通配符。

【讨论】:

    【解决方案4】:

    考虑更广泛的例子:

    Predicate<? super Integer> p;
    

    在这种情况下,以下任何赋值都是有效的,对吧?

    p = (Predicate<Integer>) i -> true; // but, only Integer can be applied
    p = (Predicate<Number>)  n -> true; // Number and its sub-classes (Integer, Double...)
    p = (Predicate<Object>)  o -> true; // any type
    

    所以,如果最后是:

    Predicate<? super Integer> p  = (Predicate<Integer>) i -> true;
    

    那么,当然,NumberObject 都不能应用于谓词。

    为了保证类型安全,编译器只允许那些对Predicate&lt;? super Integer&gt; 的任何可能赋值有效的类型 - 因此,在这个特定示例中,只允许Integer

    将下界从Integer 更改为Number 分别扩展边界:

    Predicate<? super Number> p  = (Predicate<Number>) n -> true;
    

    现在,谓词可以应用于Number 及其任何子类:IntegerDouble 等。

    总结

    唯一可以应用于Predicate&lt;? super SomeClass&gt;而不破坏类型安全保证的类型:下限本身及其子类。

    【讨论】:

      【解决方案5】:

      这是一个相当复杂的主题。这些? extends T? super T 声明应该在类之间创建“匹配”。

      如果一个类定义了一个接受Consumer的方法,比如Iterable&lt;T&gt;.foreach(),它定义这个消费者接受所有可以接受T的东西。因此,它defines it as forEach(Consumer&lt;? super T&gt; action)。为什么?因为Iterator&lt;Integer&gt;foreach() 可以用Consumer&lt;Number&gt; 甚至Consumer&lt;Object&gt; 调用。如果没有? super T,这是不可能实现的。

      OTOH,如果一个类定义了一个采用Supplier 的方法,比如addFrom,它定义了这个供应商来提供所有T。因此,它将其定义为addFrom(Supplier&lt;? extends T&gt; supplier)。为什么?因为ThisClass&lt;Number&gt;addFrom() 可以用Supplier&lt;Integer&gt;Supplier&lt;Double&gt; 调用。如果没有? extends T,这是不可能的。

      对于后者来说可能是一个更好的例子:List&lt;E&gt;.addAll() 接受 Collection&lt;? extends E&gt;。这使得将List&lt;Integer&gt; 的元素添加到List&lt;Number&gt; 成为可能,但反之则不行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-02-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多