【问题标题】:Why does negate() require an explicit cast to Predicate?为什么 negate() 需要显式强制转换为 Predicate?
【发布时间】:2020-02-22 01:40:01
【问题描述】:

我有一个名字列表。在第 3 行,我必须将 lambda 表达式的结果转换为 Predicate<String>。我正在阅读的书解释说,为了帮助编译器确定匹配的功能接口是什么,强制转换是必要的。

但是,我不需要在下一行进行这样的转换,因为我不调用 negate()。这有什么区别?我知道negate()这里返回Predicate<String>,但是前面的lambda表达式不一样吗?

List<String> names = new ArrayList<>();
//add various names here
names.removeIf(((Predicate<String>) str -> str.length() <= 5).negate()); //cast required
names.removeIf(((str -> str.length() <= 5))); //compiles without cast

【问题讨论】:

  • ...为了帮助编译器确定匹配的功能接口是什么,强制转换是必要的。 无论如何都要回答它。下一行不需要它的原因是推断要简单得多,并且不会在推断接口上进行方法调用。我已经学会了使用涉及泛型的 lambda 函数,它主要是关于在这种情况下的推理,并且显式声明非常安全,例如 Predicate&lt;String&gt; predicate = str -&gt; str.length() &lt;= 5; names.removeIf(predicate.negate()); names.removeIf(predicate);
  • @Nathan 对 negate() 的调用难道不应该提供更多的上下文,从而使显式转换的必要性变得更少吗?毕竟,lambda 表达式本来可以引用一个函数而不是一个谓词,但是随后对 negate() 的调用消除了任何歧义的空间。
  • 但是,如果 negate() 无法推断出作为表达式一部分的 lambda 属性的类型,从而进一步无法确定该表达式表示 @987654328,它会调用什么? @.
  • 我并不是说 negate() 是必要的。我的最后一行显然没有它。但是,我看不出调用 negate() 会如何突然让编译器混淆函数接口的类型。

标签: java lambda predicate


【解决方案1】:

不完全是因为你打电话给negate()。看看这个版本,它与你的非常接近,但可以编译:

Predicate<String> predicate = str -> str.length() <= 5;
names.removeIf(predicate.negate());

这个和你的版本有什么区别?这是关于 lambda 表达式如何获取它们的类型(“目标类型”)。

你认为这有什么作用?

(str -> str.length() <= 5).negate()?

您当前的答案是“它在表达式 str -&gt; str.length() &lt;= 5 给出的 Predicate&lt;String&gt; 上调用 negate()”。正确的?但这只是因为这是您的本意。 编译器不知道这一点。为什么?因为它可以是任何东西。我自己对上述问题的回答可能是“它在我的其他功能接口类型上调用 negate...(是的,这个例子会有点奇怪)

interface SentenceExpression {
    boolean checkGrammar();
    default SentenceExpression negate() {
        return ArtificialIntelligence.contradict(explainSentence());
    };
}

我可以使用相同的 lambda 表达式 names.removeIf((str -&gt; str.length() &lt;= 5).negate());,但意味着 str -&gt; str.length() &lt;= 5SentenceExpression 而不是 Predicate&lt;String&gt;

解释:(str -&gt; str.length() &lt;= 5).negate() 不会使str -&gt; str.length() &lt;= 5 成为Predicate&lt;String&gt;。这就是为什么我说它可以是任何东西,包括我上面的功能界面。

回到 Java...这就是为什么 lambda 表达式具有“目标类型”概念的原因,它定义了编译器将 lambda 表达式理解为给定功能接口类型的机制(即,您如何帮助编译器知道表达式是Predicate&lt;String&gt; 而不是SentenceExpression 或其他任何可能的值)。阅读What is meant by lambda target type and target type context in Java?Java 8: Target typing 会很有用

推断目标类型的上下文之一(如果您阅读了这些帖子上的答案)是调用上下文,您在其中传递 lambda 表达式作为函数接口类型参数的参数,这就是适用于names.removeIf(((str -&gt; str.length() &lt;= 5)));:它只是作为参数提供给采用Predicate&lt;String&gt; 的方法的lambda 表达式。这不适用于未编译的语句。

所以,换句话说……

names.removeIf(str -&gt; str.length() &lt;= 5); 在参数类型明确定义预期 lambda 表达式类型的地方使用 lambda 表达式(即,str -&gt; str.length() &lt;= 5 的目标类型显然是 Predicate&lt;String&gt;)。

但是,(str -&gt; str.length() &lt;= 5).negate() 不是 lambda 表达式,它只是一个恰好使用 lambda 表达式的表达式。也就是说,在这种情况下,str -&gt; str.length() &lt;= 5 不在确定 lambda 表达式目标类型的调用上下文中(就像您最后一条语句的情况一样)。是的,编译器知道removeIf 需要Predicate&lt;String&gt;,并且它肯定知道传递给方法的整个表达式必须是Predicate&lt;String&gt;,但它不会假设参数表达式中的任何lambda 表达式将是 Predicate&lt;String&gt;(即使您通过在其上调用 negate() 将其视为谓词;它可能是与 lambda 表达式兼容的任何东西)。

这就是为什么需要使用显式强制类型转换(或其他方式,如我给出的第一个反例)键入您的 lambda。

【讨论】:

  • names.removeIf(((str -> str.length()
  • @KMan 我认为您需要有关如何键入 lambda 表达式的更多详细信息。我将编辑并添加一个链接。
  • 我已经阅读了很多关于 lambda 表达式的内容。整个表达式是否是 lambda 表达式无关紧要,不是吗? negate() 在我上面的代码中不返回 Predicate 吗?我可能弄错了,但我不这么认为。
  • @KMan 在我上面的代码中 negate() 不返回 Predicate 吗? .. 不,它不会 直到它被 @ 调用987654352@ 这是编译器由于使用negate 本身而未能首先推断的内容,如this answer中所述。
  • @KMan 我已编辑 - 我想您需要重新阅读答案。简而言之,正如 Naman 所说,它归结为理解 (str -&gt; str.length() &lt;= 5).negate() 不会使 str -&gt; str.length() &lt;= 5 成为 Predicate&lt;String&gt;,即使那是想要的。
【解决方案2】:

我不知道为什么会如此混乱。这可以用两个原因来解释,IMO。

  • Lambda 表达式是 poly 表达式。

我会让你弄清楚这意味着什么以及它周围的 JLS 词是什么。但本质上,这些就像泛型一样:

static class Me<T> {
    T t...
}

T 这里的类型是什么?这得看情况。如果你这样做:

Me<Integer> me = new Me<>(); // it's Integer
Me<String>  m2 = new Me<>(); // it's String

poly 表达式是说它们依赖于使用它们的上下文。 Lambda 表达式是相同的。让我们在这里使用 isolation 中的 lambda 表达式:

(String str) -> str.length() <= 5

当你看它时,这是什么?那么它是Predicate&lt;String&gt;?但可能是 AFunction&lt;String, Boolean&gt;?或者甚至可能是MyTransformer&lt;String, Boolean&gt;,其中:

 interface MyTransformer<String, Boolean> {
     Boolean transform(String in){
         // do something here with "in"
     } 
 } 

选择是无穷无尽的。

  • 理论上,直接调用.negate()可能是一种选择。

从 10_000 英里以上,您是正确的:您将 str -&gt; str.length() &lt;= 5 提供给仅接受 PredicateremoveIf 方法。没有更多的removeIf 方法,因此当您提供(str -&gt; str.length() &lt;= 5).negate() 时,编译器应该能够“做正确的事情”。

那么为什么这不起作用?让我们从您的评论开始:

对 negate() 的调用不应该提供更多上下文,从而使显式转换的必要性变得更少吗?

看来这是主要问题的开始,这根本不是javac 的工作原理。它不能占用整个str -&gt; str.length() &lt;= 5).negate(),告诉自己这是一个Predicate&lt;String&gt;(因为您将它用作removeIf 的参数),然后进一步分解没有.negate() 的部分,看看这是否是一个Predicate&lt;String&gt; 也。 javac 反向操作,它需要知道 目标 以便能够判断调用 negate 是否合法。

通常,您还需要明确区分 poly 表达式表达式str -&gt; str.length() &lt;= 5).negate() 是一个表达式,str -&gt; str.length() &lt;= 5 是一个多边形表达式。

可能有一些语言以不同的方式完成事情并且这是可能的,javac 根本不是那种类型。

【讨论】:

    【解决方案3】:

    在下面的例子中

          names.removeIf(str -> str.length() <= 5); //compiles without cast
    

    表达式返回真。如果在以下示例中没有强制转换,truenegate() 方法一无所知

    另一方面,

       names.removeIf(((Predicate<String>) str -> str.length() <= 5).negate()); //cast required
    

    表达式被转换为Predicate&lt;String&gt;,以告诉编译器在哪里可以找到方法negate。然后是 negate() 方法实际上通过以下方式进行评估:

       (s)->!test(s) where s is the string argument
    
    

    请注意,您可以在没有强制转换的情况下获得相同的结果,如下所示:

        names.removeIf(str->!str.length <= 5) 
          // or
        name.removeIf(str->str.length > 5)
    
    

    【讨论】:

      【解决方案4】:

      如果不深入研究,底线是 lambda str -&gt; str.length() &lt;= 5 不一定是 Predicate&lt;String&gt;,正如 Eugene 解释的那样。

      也就是说,negate()Predicate&lt;T&gt; 的成员函数,编译器无法使用对negate() 的调用来帮助推断应将lambda 解释为的类型。即使尝试过,也可能会出现问题,因为如果多个可能的类具有 negate() 函数,它就不知道该选择哪个。

      假设你是编译器。

      • 你看str -&gt; str.length() &lt;= 5。你知道它是 lambda,它接受一个字符串并返回一个布尔值,但不知道它具体代表哪种类型。
      • 接下来您会看到成员引用.negate(),但因为您不知道“。”左侧的表达式类型。您需要报告错误,因为您不能使用 negate 调用来帮助推断类型。

      如果将 negate 实现为静态函数,那么一切都会起作用。例如:

      public class PredicateUtils {
          public static <T> Predicate<T> negate(Predicate<T> p) {
              return p.negate();
      }
      

      会让你写作

      names.removeIf(PredicateUtils.negate(str -> str.length() <= 5)); //compiles without cast
      

      因为 negate 函数的选择是明确的,编译器知道它需要将 str -&gt; str.length() &lt;= 5 解释为 Predicate&lt;T&gt; 并且可以正确地强制类型。

      我希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-01-06
        • 2014-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-08
        • 2021-05-08
        相关资源
        最近更新 更多