【问题标题】:Why do I need a functional Interface to work with lambdas?为什么我需要一个函数式接口来使用 lambdas?
【发布时间】:2015-10-08 08:34:23
【问题描述】:

我认为这个问题已经在某个地方,但我找不到它。

我不明白,为什么必须有一个函数式接口才能使用 lambda。考虑以下示例:

public class Test {

    public static void main(String...args) {
        TestInterface i = () -> System.out.println("Hans");
//      i = (String a) -> System.out.println(a);

        i.hans();
//      i.hans("Hello");
    }
}

public interface TestInterface {
    public void hans();
//  public void hans(String a);
}

这可以正常工作,但是如果您取消注释注释行,则不会。为什么?据我了解,编译器应该能够区分这两种方法,因为它们具有不同的输入参数。为什么我需要一个功能接口并炸毁我的代码?

编辑:链接的重复项没有回答我的问题,因为我在询问不同的方法参数。但是我在这里得到了一些非常有用的答案,感谢所有帮助过的人! :)

EDIT2:抱歉,我显然不是母语人士,但准确地说是:

public interface TestInterface {
    public void hans();                 //has no input parameters</br>
    public void hans(String a);         //has 1 input parameter, type String</br>
    public void hans(String a, int b);  //has 2 input parameters, 1. type = String, 2. type = int</br>
    public void hans(int a, int b);     //has also 2 input parameters, but not the same and a different order than `hans(String a, int a);`, so you could distinguish both
}

public class Test {

    public static void main(String...args) {
        TestInterface i = () -> System.out.println("Hans");
        i = (String a) -> System.out.println(a);
        i = (String a, int b) -> System.out.println(a + b);
        i = (int a, int b) -> System.out.println(a);

        i.hans(2, 3);   //Which method would be called? Of course the one that would take 2 integer arguments. :)
    }
}

我要问的只是论点。方法名称无关紧要,但每个方法都采用不同参数的唯一顺序,因此,Oracle 可以实现此功能,而不是只为每个“Lambda 接口”提供一个方法。

【问题讨论】:

  • int i = 7; i = 5; System.out.println(i); // wouldn't it be awesome if this would print both 7 and 5?
  • 我真的不明白你的推理。如果某些东西没有实现接口,那么它怎么可能是那种类型的呢? iPod 不能算作 iPhone,因为你没有碰巧打电话。
  • @immibis:你在这里描述的和我问的不同。 :)
  • @TrudleR 你似乎在问:&lt;some type&gt; i = &lt;some value&gt;; i = &lt;some other value&gt;; // why can't i be both values at once here? 变量i 可以持有对 one 对象的引用,不管那是不是由 lambda 表达式创建的对象或您显式编写的类的实例。
  • @TrudleR 所以你想做类似TestInterface i = new TestInterface() {void hans() {System.out.println("Hans");} void hans(int a) {System.out.println(a);} /* and so on */};的事情?

标签: java lambda java-8 functional-interface


【解决方案1】:

当你写的时候:

TestInterface i = () -> System.out.println("Hans");

您为TestInterfacevoid hans() 方法提供了一个实现。

如果您可以将 lambda 表达式分配给具有多个抽象方法(即非函数式接口)的接口,则 lambda 表达式只能实现其中一种方法,而其他方法未实现。

您无法通过将具有不同签名的两个 lambda 表达式分配给同一个变量来解决此问题(就像您不能将两个对象的引用分配给单个变量并期望该变量同时引用两个对象一样)。

【讨论】:

  • @TrudleR,那么 Java 将是一种函数式语言。如果您对 JVM 上的此类实现感兴趣。你可以试试 Scala 语言。
  • @TrudleR:“如果你可以调用你实现了主体的方法”——当你有一个任意接口实现的实例时,你怎么知道实现了哪些方法?
  • TrudleR,你让我想到这里 :) 但恕我直言,如果他们允许“只调用你实现了一个主体的方法”,他们就会打开一个潘多拉盒子......:lambda 定义了 1 个方法,而您的界面是 2 的合同;偏离这个合同真的会混淆你的对象的接收。另外,如果你的接口有几个带有 String 参数的方法,编译器将不知道哪个与 lambda 表达式匹配。如果你想更激进,你可以考虑 node.js,它根本不关心合约(=接口)......
  • @TrudleR:我不明白你的意思。当然,匿名类和接口都不是过时的,没有人说不同的东西。这并不能证明您通过定义 一个 有效方法和所有其他方法都被破坏来实现多方法接口的请求是合理的。我再次问,程序员(或编译器)如何看到该接口类型的引用(可能包含任意实现)应该知道哪个方法是没有被破坏的?我说的不是你定义 n-1 损坏实现的地方,而是使用对象的地方。
  • 您的问题的问题是您一直在强调每个方法都有不同的签名这一事实。这只有在你坚持“一个 lambda 表达式与这些方法之一相关联”的想法时才有意义。如果您只是在考虑一种实现接口的紧凑方式,那么它不适用于 lambda 表达式的主要原因是,它们不应该是接口实现助手。 →Lambda 表达式是函数。函数式接口只是在不改变类型系统的情况下将它们添加到 Java 的工具,而不是它们的用途。
【解决方案2】:

它们必须只包含一种方法的最重要原因是,否则很容易混淆。如果接口中允许有多个方法,如果参数列表相同,则 lambda 应该选择哪个方法?

interface TestInterface {
    void first();
    void second(); // this is only distinguished from first() by method name
    String third(); // maybe you could say in this instance "well the return type is different"
    Object fourth(); // but a String is an Object, too !
}

void test() {
    // which method are you implementing, first or second ?
    TestInterface a = () -> System.out.println("Ido mein ado mein");
    // which method are you implementing, third or fourth ?
    TestInterface b = () -> "Ido mein ado mein";
}

【讨论】:

  • 是的,我知道,如果你想运行这样的代码,它应该只会导致异常。但我认为编译器应该清楚不同的论点。如果声明了2个同名同参数的方法,也会报异常,那Oracle为什么要在这里阻止呢?
  • 很好的例子。谢谢
  • 按顺序定义的第一个方法怎么样? :) 一个接口上可以有 5 个方法,当使用接口实例作为 lambda 参数时,将使用 5 中的第一个方法。
【解决方案3】:

您似乎在寻找匿名课程。以下代码有效:

public class Test {

    public static void main(String...args) {
        TestInterface i = new TestInterface() {
            public void hans() {
                System.out.println("Hans");
            }
            public void hans(String a) {
                System.out.println(a);
            }
        };

        i.hans();
        i.hans("Hello");
    }
}

public interface TestInterface {
    public void hans();
    public void hans(String a);
}

Lambda 表达式(大多数情况下)是一种只用一种方法编写匿名类的更短的方法。 (同样,匿名类是只在一个地方使用的内部类的简写)

【讨论】:

  • 嘿,我不是一个新手。 :) 当然,我知道如何将其用于工作。我只是想问一下,为什么 lambdas 只适用于函数式接口。很明显,我需要为我的案例编写匿名类(或实现一个类本身)。我只想说,我认为 Oracle 可以扩展 lambdas 的使用。但是在这里的讨论之后,很清楚为什么他们强迫你使用函数式接口。不过还是谢谢。
  • @TrudleR 如果他们以您想的方式扩展 lambda,那么它们将与匿名类相同,那么有什么意义呢?
【解决方案4】:

您不必为了创建 lambda 函数而创建函数式接口。该接口允许您为将来的函数调用创建实例。

在你的情况下,你可以使用已经存在的接口 Runable

Runnable r = () -&gt; System.out.println("Hans");

然后调用

r.run();

你可以认为 lambda -&gt; 只是以下的简写:

Runnable r = new Runnable() {
     void run() {
          System.out.println("Hans");`
     }
}

使用 lambda,您不需要在上面示例中创建的匿名类。

但这有一些限制,为了弄清楚什么方法应该被调用,与 lambdas 一起使用的接口必须是 SAM(单一抽象方法)。那么我们只有一种方法。

更详细的解释请阅读:

Introduction to Functional Interfaces – A Concept Recreated in Java 8

【讨论】:

    【解决方案5】:
    • 在java中实现接口时,需要实现其所有抽象方法(否则实现类必须是接口)。

    • Java 编译器在内部扩展 lambda 表达式,其中包含方法的类定义和实例化此类的语句。目前 java 不支持/提供一种方法让超过 1 个 lambda 与 1 个接口相关联。

        public class Test {
            public static void main(String...args) {
                TestInterface i = () -> System.out.println("Hans"); // this will not compile as the implementation for public void hans(String a); can not be provided/is not found
                //i = (String a) -> System.out.println(a); //this can not add an implementation for 2nd method to i after compilation of 1st lambda
             }
        }
        public interface TestInterface {
            public void hans();
            public void hans(String a);
        }
      

    这就是 java 中的 lambda 仅适用于具有一种方法或功能接口的接口的原因。

    【讨论】:

      【解决方案6】:

      根据java specs,函数接口只能包含一个抽象方法。

      当然 lambda 表达式可以像注释代码那样一次性使用,但是在将 lambda 表达式作为参数传递以模拟函数回调时,函数式接口是必须的,因为在这种情况下,变量数据类型是函数式接口.

      例如Runnable是一个内置的功能接口:

      public interface Runnable() {
          public void run();
      }
      

      用法如下:

      public class LambdaTest {
          // data type of parameter 'task' is functional interface 'Runnable'
          static void doSeveralTimes(Runnable task, int repeat) {
              for (int i = 0; i < repeat; i++) {
                  task.run();
              }
          }
      
          public static void main(String[] args) {
              // one-time lambda
              doSeveralTimes(() -> {
                  System.out.println("one-time lambda");
              }, 3);
      
              // lambda as variable
              Runnable test;
              test = () -> {
                  System.out.println("lambda as variable");
              };
              doSeveralTimes(test, 3);
          }
      }
      

      结果是:

      one-time lambda
      one-time lambda
      one-time lambda
      lambda as variable
      lambda as variable
      lambda as variable
      

      【讨论】:

        【解决方案7】:

        lambda 表达式只不过是定义函数式接口实现的快捷方式。 它相当于一个功能接口实现的实例。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-08-25
          • 2020-02-22
          • 2018-05-17
          • 1970-01-01
          • 2021-01-23
          • 1970-01-01
          • 2012-03-16
          • 1970-01-01
          相关资源
          最近更新 更多