Java 8消费者/功能Lambda歧义 [英] Java 8 Consumer/Function Lambda Ambiguity

查看:242
本文介绍了Java 8消费者/功能Lambda歧义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个重载方法,它分别接受一个Consumer和一个Function对象,并返回一个与相应的Consumer / Function匹配的泛型类型。我认为这样会很好,但是当我尝试使用lambda表达式调用任一方法时,我得到一个错误,指示对该方法的引用是不明确的。

I have an overloaded method that takes a Consumer and a Function object respectively and returns a generic type that matches the corresponding Consumer/Function. I thought this would be fine, but when I try to call either method with a lambda expression I get an error indicating the reference to the method is ambiguous.

基于我的阅读JLS§15.12。 2.1。识别可能适用的方法:似乎编译器应该知道我的带有void块的lambda与Consumer方法匹配,而我的lambda与返回类型匹配Function方法。

Based on my reading of JLS §15.12.2.1. Identify Potentially Applicable Methods: it seems like the compiler should know that my lambda with a void block matches the Consumer method and my lambda with a return type matches the Function method.

我把以下无法编译的示例代码放在一起:

I put together the following sample code that fails to compile:

import java.util.function.Consumer;
import java.util.function.Function;

public class AmbiguityBug {
  public static void main(String[] args) {
    doStuff(getPattern(x -> System.out.println(x)));
    doStuff(getPattern(x -> String.valueOf(x)));
  }

  static Pattern<String, String> getPattern(Function<String, String> function) {
    return new Pattern<>(function);
  }

  static ConsumablePattern<String> getPattern(Consumer<String> consumer) {
    return new ConsumablePattern<>(consumer);
  }

  static void doStuff(Pattern<String, String> pattern) {
    String result = pattern.apply("Hello World");
    System.out.println(result);
  }

  static void doStuff(ConsumablePattern<String> consumablePattern) {
    consumablePattern.consume("Hello World");
  }

  public static class Pattern<T, R> {
    private final Function<T, R> function;

    public Pattern(Function<T, R> function) {
      this.function = function;
    }

    public R apply(T value) {
      return function.apply(value);
    }
  }

  public static class ConsumablePattern<T> {
    private final Consumer<T> consumer;

    public ConsumablePattern(Consumer<T> consumer) {
      this.consumer = consumer;
    }

    public void consume(T value) {
      consumer.accept(value);
    }
  }
}

我还找到了类似的 stackoverflow帖子,结果证明是编译器错误。我的情况非常相似,虽然有点复杂。对我来说,这仍然看起来像一个bug,但我想确保我不会误解lambdas的语言规范。我正在使用Java 8u45,它应该有所有最新的修复。

I also found a similar stackoverflow post that turned out to be a compiler bug. My case is very similar, though a bit more complicated. To me this still looks like a bug, but I wanted to make sure I am not misunderstanding the language spec for lambdas. I'm using Java 8u45 which should have all of the latest fixes.

如果我改变我的方法调用被包装在一个块中,一切似乎都在编译,但这增加了额外的详细程度和许多自动格式化程序会将其重新格式化为多行。

If I change my method calls to be wrapped in a block everything seems to compile, but this adds additional verbosity and many auto-formatters will reformat it into multiple lines.

doStuff(getPattern(x -> { System.out.println(x); }));
doStuff(getPattern(x -> { return String.valueOf(x); }));


推荐答案

这一行绝对含糊不清:

doStuff(getPattern(x -> String.valueOf(x)));

从链接的JLS章节重读:

Reread this from the linked JLS chapter:

如果满足以下所有条件,则lambda表达式(第15.27节)可能与功能接口类型(第9.8节)兼容:

A lambda expression (§15.27) is potentially compatible with a functional interface type (§9.8) if all of the following are true:


  • 目标类型的函数类型的arity与lambda表达式的arity相同。

  • The arity of the target type's function type is the same as the arity of the lambda expression.

如果目标类型的函数类型具有void返回,然后lambda主体是语句表达式(§14.8)或void兼容块(§15.27.2)。

If the target type's function type has a void return, then the lambda body is either a statement expression (§14.8) or a void-compatible block (§15.27.2).

在您的消费者的情况下,您有一个语句表达式,因为任何方法调用都可以用作语句expressio即使该方法是无效的。例如,您可以简单地写这个:

In your case for Consumer you have a statement expression as any method invocation can be used as statement expression even if the method is non-void. For example, you can simply write this:

public void test(Object x) {
    String.valueOf(x);
}

这没有任何意义,但编译完美。您的方法可能有副作用,编译器不知道它。例如,它是 List.add 总是返回 true 并且没有人关心它的返回值。

It makes no sense, but compiles perfectly. Your method may have a side-effect, compiler doesn't know about it. For example, were it List.add which always returns true and nobody cares about its return value.

当然这个lambda也有资格获得 Function ,因为它是一个表达式。这就是它的暧昧。如果你有一个表达式而不是语句表达式,那么调用将被映射到 Function 而没有任何问题:

Of course this lambda also qualifies for Function as it's an expression. Thus it's ambigue. If you have something which is an expression, but not a statement expression, then the call will be mapped to Function without any problem:

doStuff(getPattern(x -> x == null ? "" : String.valueOf(x)));

当您将其更改为 {return String.valueOf(x); } ,您创建了一个与值兼容的块,因此它与函数匹配,但它不符合 void-compatible块的条件。但是你也可能遇到块问题:

When you change it to { return String.valueOf(x); }, you create a value-compatible block, so it matches the Function, but it does not qualify as a void-compatible block. However you may have problems with blocks as well:

doStuff(getPattern(x -> {throw new UnsupportedOperationException();}));

此块既可以兼容值又可以兼容,因此您再次有歧义。另一个ambigue块示例是无限循环:

This block qualifies both as a value-compatible and a void-compatible, thus you have an ambiguity again. Another ambigue block example is an endless loop:

doStuff(getPattern(x -> {while(true) System.out.println(x);}));

至于 System.out.println(x)案件有点棘手。它肯定符合语句表达式,因此可以与 Consumer 匹配,但似乎它与表达式匹配,并且规范说方法调用是一个表达式。然而,它是有限使用的表达像15.12.3 说:

As for System.out.println(x) case it's a little bit tricky. It surely qualifies as statement expression, so can be matched to Consumer, but seems that it matches to expression as well as spec says that method invocation is an expression. However it's an expression of limited use like 15.12.3 says:


如果编译时声明为void,则方法调用必须是顶级表达式(即,表达式语句或for语句的ForInit或ForUpdate部分中的表达式,或发生编译时错误。这样的方法调用不会产生任何值,因此只能在不需要值的情况下使用。

If the compile-time declaration is void, then the method invocation must be a top level expression (that is, the Expression in an expression statement or in the ForInit or ForUpdate part of a for statement), or a compile-time error occurs. Such a method invocation produces no value and so must be used only in a situation where a value is not needed.

因此编译器完全遵循规范。首先,它确定你的lambda主体既被限定为表达式(即使它的返回类型为void:15.12.2.1对于这种情况也不例外)和语句表达式,所以它也被认为是歧义。

So compiler perfectly follows the specification. First it determines that your lambda body is qualified both as an expression (even though its return type is void: 15.12.2.1 makes no exception for this case) and a statement expression, so it's considered an ambiguity as well.

因此对我来说,这两个语句都是按照规范编译的。 ECJ编译器在此代码上生成相同的错误消息。

Thus for me both statements compile according to the specification. ECJ compiler produces the same error messages on this code.

一般情况下,我建议您在重载具有相同数量的参数并且具有相同数量的参数时避免重载方法差异仅在接受的功能界面。即使这些功能接口具有不同的arity(例如, Consumer BiConsumer ):lambda也没有问题,但可能有方法引用的问题。在这种情况下,只需为您的方法选择不同的名称(例如, processStuff consumeStuff )。

In general I'd suggest you to avoid overloading your methods when your overloads has the same number of parameters and has the difference only in accepted functional interface. Even if these functional interfaces have different arity (for example, Consumer and BiConsumer): you will have no problems with lambda, but may have problems with method references. Just select different names for your methods in this case (for example, processStuff and consumeStuff).

这篇关于Java 8消费者/功能Lambda歧义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆