【问题标题】:What decides which functional interface to create from a lambda?是什么决定了从 lambda 创建哪个功能接口?
【发布时间】:2017-02-01 14:02:56
【问题描述】:

请考虑这个例子:

import java.util.function.Consumer;

public class Example {
    public static void main(String[] args) {
        Example example = new Example();

        example.setConsumer(test -> System.out.println("passed string is " + test)); //uses MyConsumer, why ?
        example.getConsumer().accept("Test 1");

        example.setConsumer((MyConsumer<String>)test -> System.out.println("passed string is " + test)); //uses MyConsumer
        example.getConsumer().accept("Test 2");

        example.setConsumer((Consumer<String>)test -> System.out.println("passed string is " + test)); //uses Consumer
        example.getConsumer().accept("Test 3");
    }

    private Consumer<String> consumer;

    public Consumer<String> getConsumer() {
        return consumer;
    }

    public void setConsumer(Consumer<String> consumer) {
        this.consumer = consumer;
    }

    public void setConsumer(MyConsumer<String> consumer) {
        this.consumer = consumer;
    }

    @FunctionalInterface
    public interface MyConsumer<T> extends Consumer<T> {
        @Override
        default void accept(T value) {
            System.out.println("In consumer string: " + value); //example thing to do
            receive(value);
        }

        void receive(T value);
    }
}

我感兴趣的是第一个测试。为什么使用 MyConsumer 而不是 Consumer ?如果我有更多不同的可能消费者具有相同的 lambda 结构,谁有优先权?另外,我在测试 2 中所做的演员表被我的 IDE 标记为Redundant。这意味着 lamdba 首先是作为 MyConsumer 创建的。为什么会这样?

我正在使用带有 Javac 的 IntelliJ Idea。

【问题讨论】:

  • 不知道你用的是哪个IDE。我认为是 JVM 使用对象的运行时类型来“决定”。
  • 我认为 Java 选择了最具体的类型,在这种情况下是 MyConsumer。如果MyConsumer 不是Consumer 的子接口,我想你会得到一个错误,说这是一个模棱两可的调用。
  • 它选择最具体的方法。例如,如果您有 MyConsumer2&lt;T&gt; extends Consumer&lt;T&gt;setConsumer(MyConsumer2&lt;String&gt; consumer),那么第一次调用将是模棱两可的,并且您会遇到编译时错误。另请参阅JLS 15.12.2.5
  • 这个问题本身导致了建议:不要使用不同的功能接口作为方法重载的标准。重载本身可能很难理解。从一开始就针对不同目标类型对 lambda 进行类型推断的重载是一个坏主意。在某些情况下,甚至编译器编写者也必须调试他们自己的编译器才能找出重载决议的正确答案。普通程序员在这里没有机会。

标签: java lambda java-8 functional-interface


【解决方案1】:

按照语言规范定义的choosing the most specific method的程序:

如果多个成员方法既可访问又适用于方法调用,则有必要选择一个为运行时方法分派提供描述符。 Java 编程语言使用选择最具体方法的规则。

...

如果 T 不是 S 的子类型并且以下条件之一为真(其中 U1 ... Uk其中R1是S的捕获函数类型的参数类型和返回类型,V1...Vk和R2是T的函数类型的参数类型和返回类型):

  • 如果 e 是显式类型的 lambda 表达式(第 15.27.1 节),则以下情况之一为真:
  • R2 无效。

  • R1 <: r2.>

  • R1和R2是函数式接口类型,至少有一个结果表达式,对于e的每个结果表达式,R1都比R2更具体。

    (带有块体的 lambda 表达式的结果表达式在 §15.27.2 中定义;带有表达式体的 lambda 表达式的结果表达式就是块体本身。)

  • R1是原始类型,R2是引用类型,并且至少有一个结果表达式,并且e的每个结果表达式都是原始类型的独立表达式(第15.2节)。

  • R1为引用类型,R2为基本类型,且至少有一个结果表达式,e的每个结果表达式要么是引用类型的独立表达式,要么是多边形表达式。

  • 如果 e 是精确方法参考表达式 (§15.13.1),则 i) 对于所有 i (1 ≤ i ≤ k),Ui 与 Vi 相同,并且 ii) 以下其中一项为真:
  • R2 无效。

  • R1 <: r2.>

  • R1 是原始类型,R2 是引用类型,方法引用的编译时声明具有原始类型的返回类型。

  • R1 是引用类型,R2 是原始类型,方法引用的编译时声明有一个返回类型,即引用类型。

  • 如果 e 是带括号的表达式,则这些条件之一递归地应用于包含的表达式。

  • 如果 e 是条件表达式,则对于第二个和第三个操作数中的每一个,这些条件之一将递归应用。

因此,MyConsumerConsumer 更具体,因为Consumer(规范中为T)不是子类型,并且两者的返回值都是void

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-09
    • 2021-01-25
    • 1970-01-01
    • 2020-02-03
    • 1970-01-01
    • 1970-01-01
    • 2021-12-24
    • 2016-08-21
    相关资源
    最近更新 更多