为什么negate()需要显式转换为谓词?

问题描述 投票:7回答:3

我有一个名字列表。在第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
java lambda predicate
3个回答
6
投票

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

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

此版本与您的版本之间的区别?关于lambda表达式如何获取其类型(“目标类型”)。

您认为这是什么?

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

您当前的答案是“它在由表达式negate()给出的Predicate<String>上调用str -> str.length() <= 5”。对?但这仅仅是因为这就是您的意图。 编译器不知道。为什么?因为它可以是任何东西。我对上述问题的回答可能是“它在我的其他功能接口类型上调用negate ...(是的,示例将有些奇怪)

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

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

说明:(str -> str.length() <= 5).negate()不会使str -> str.length() <= 5成为Predicate<String>。这就是为什么我说可能是什么,包括上面的功能接口。

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

非常有用

推断目标类型的上下文之一(如果您阅读了这些文章的答案)是调用上下文,在该上下文中,您传递了一个lambda表达式作为功能接口类型的参数的参数,这就是适用于names.removeIf(((str -> str.length() <= 5)));:这只是作为采用Predicate<String>的方法的参数而给出的lambda表达式。这不适用于未编译的语句。

所以,换句话说...

[names.removeIf(str -> str.length() <= 5);在参数类型清楚地定义了lambda表达式的类型的地方使用了lambda表达式(即str -> str.length() <= 5的目标类型显然是Predicate<String>)。

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

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


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表达式据说它们取决于使用它们的context。 Lambda表达式相同。让我们在isolation中使用lambda表达式:

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

当您查看它时,这是什么?是Predicate<String>吗?但是可能是A Function<String, Boolean>?甚至可能是MyTransformer<String, Boolean>,其中:

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

选择无尽。

  • 理论上,.negate()直接称为可以是一个选择。

从10_000英里以上开始,您是正确的:您将str -> str.length() <= 5设置为仅接受removeIfPredicate方法。没有removeIf方法,因此在提供(str -> str.length() <= 5).negate()时,编译器应该能够“做正确的事”。

所以这怎么不起作用?让我们从您的评论开始:

对negate()的调用是否应该提供更多上下文,从而使显式强制转换的必要性降低?

似乎这是主要问题开始的地方,这根本不是javac的工作方式。它不能取整个str -> str.length() <= 5).negate(),告诉自己这是一个Predicate<String>(因为您将其用作removeIf的参数),然后进一步分解了没有.negate()的那一部分,看是否是Predicate<String>也。 javac的作用相反,它需要知道target才能知道调用negate是否合法。

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

[在某些语言中,可能会以不同的方式完成任务,并且在可能的情况下,javac根本不是那种类型。


1
投票

在以下示例中

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

表达式返回true。如果在下面的示例中没有强制转换,则true对方法negate()不了解

另一方面,

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

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

   !test(s) where s is the string argument

请注意,如果不进行强制转换,则可以实现相同的结果,如下所示:

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

© www.soinside.com 2019 - 2024. All rights reserved.