Java 8 中使用的功能接口是啥?

Posted

技术标签:

【中文标题】Java 8 中使用的功能接口是啥?【英文标题】:What are functional interfaces used for in Java 8?Java 8 中使用的功能接口是什么? 【发布时间】:2016-08-21 06:42:00 【问题描述】:

我在 Java 8 中遇到了一个新术语:“函数式接口”。在使用 lambda 表达式时,我只能找到它的一种用途。

Java 8 提供了一些内置的函数式接口,如果我们想定义任何函数式接口,我们可以使用@FunctionalInterface 注解。它将允许我们在接口中只声明一个方法。

例如:

@FunctionalInterface
interface MathOperation 
    int operation(int a, int b);

除了使用 lambda 表达式之外,它在 Java 8 中还有多大用处?

(here 的问题与我问的不同。它问的是为什么我们在使用 lambda 表达式时需要函数式接口。我的问题是:除了使用 lambda 表达式之外,函数式接口的其他用途是什么?)

【问题讨论】:

它看起来与此链接重复。他们还讨论了为什么在功能接口中应该只有一种方法。 ***.com/questions/33010594/… @KulbhushanSingh 我在发帖前看到了这个问题......这两个问题都感觉不同...... 【参考方案1】:

@FunctionalInterface 注释对于代码的编译时间检查很有用。除了staticdefault 和覆盖Object 中的方法的抽象方法之外,您的@FunctionalInterface 或任何其他用作功能接口的接口中的方法不能超过一种。

但是您可以使用不带此注释的 lambda,也可以覆盖不带 @Override 注释的方法。

来自文档

一个函数式接口只有一个抽象方法。自默认 方法有一个实现,它们不是抽象的。如果一个接口 声明了一个抽象方法,它覆盖了以下公共方法之一 java.lang.Object,这也不计入接口的 抽象方法计数,因为接口的任何实现都会 有来自 java.lang.Object 或其他地方的实现

这个可以在 lambda 表达式中使用

public interface Foo 
  public void doSomething();

这个不能在 lambda 表达式中使用

public interface Foo 
  public void doSomething();
  public void doSomethingElse();

但这会产生编译错误

@FunctionalInterface
public interface Foo 
  public void doSomething();
  public void doSomethingElse();

'@FunctionalInterface' 注释无效; Foo 不是函数式 界面

【讨论】:

更准确地说,你必须有一个确切的抽象方法,它不会覆盖函数接口中java.lang.Object中的方法。 ……这与“除了staticdefault 之外没有多个public 方法”略有不同…… 仍然不明白拥有它的任何意义。为什么地球上有人会费心检查他/她的界面有多少方法。标记接口仍然有一个要点和一个特定的目的。文档和答案只解释了它的作用,而不是它有什么用处。而“使用”正是 OP 所要求的。所以我不会推荐这个答案。 @VNT 编译错误获取此接口的客户端,但接口本身不能更改。有了这个注解,编译错误就在接口上,所以你要确保没有人会破坏你接口的客户端。 这显示了如何使用它们,但没有解释为什么我们需要它们。【参考方案2】:

documentation 确实起到了作用

一种信息性注释类型,用于指示接口类型声明旨在成为 Java 语言规范定义的功能接口

和用例

请注意,函数式接口的实例可以使用 lambda 表达式、方法引用或构造函数引用来创建。

其措辞一般不排除其他用例。由于主要目的是指示 功能接口,您的实际问题归结为 “除了 lambda 表达式和方法之外, 功能接口是否还有其他用例/构造函数引用?”

由于 功能接口 是由 Java 语言规范定义的 Java 语言结构,因此只有该规范才能回答这个问题:

JLS §9.8. Functional Interfaces:

除了通过声明和实例化类(第 15.9 节)来创建接口实例的常规过程之外,还可以使用方法引用表达式和 lambda 表达式(第 15.13 节、第 15.27 节)来创建功能接口实例。

所以 Java 语言规范没有另外说明,该部分中提到的唯一用例是使用方法引用表达式和 lambda 表达式创建接口实例。 (这包括构造函数引用,因为它们在规范中被称为方法引用表达式的一种形式)。

一句话,不,Java 8 中没有其他用例。

【讨论】:

可能只是要求太多或无关紧要(您可以选择不回答),但是当有人创建实用程序public static String generateTaskId() 而不是使其更具“功能性”时,您会提出什么建议?否则选择使用现有的生成实现将其写为public class TaskIdSupplier implements Supplier<String>get 方法。这是对功能接口的滥用,尤其是重用JDK内置的Supplier吗? PS:我找不到更好的地方/问答来问这个问题。如果您能提出建议,我们很高兴迁移。 @Naman 当您创建命名类TaskIdSupplier 时,您并没有使实用程序方法更具功能性。现在,问题是您为什么创建命名类。在某些情况下需要这种命名类型,例如当您想通过ServiceLoader 支持查找实现时。那么让它实现Supplier 并没有错。但是当你不需要它时,不要创建它。当您只需要 Supplier<String> 时,使用 DeclaringClass::generateTaskId 就已经足够了,并且无需显式类是此语言功能的重点。 老实说,我一直在寻找我正在传递的建议的理由。由于工作中的某些原因,我并不觉得TaskIdSupplier 的实现值得付出努力,但后来ServiceLoader 的概念完全让我忘记了。在我们进行的这些讨论中遇到了一些问题,例如 当一个人可以继续开发自己的界面时,Supplierpublic 存在有什么用处?为什么不呢?将public static Supplier<String> TASK_ID_SUPPLIER = () ->... 设为全局常量?。 (1/2) @Naman 在 Java 中表示函数的惯用方式是方法,评估这些函数与调用它们相同。绝不应该强迫开发人员使用variable.genericMethodName(args) 而不是meaningfulMethodName(args)。使用类类型来表示函数,无论是通过 lambda 表达式/方法引用还是手动创建的类,都只是传递函数的工具(在 Java 中没有真正的函数类型的情况下)。这应该只在需要时进行。 当你有一个小代码片段只被传递时,你可以创建一个封装它的 lambda 表达式。每当还需要像方法一样调用它时(这包括需要测试的场景,当代码片段不是微不足道时),创建一个可以调用的命名方法并使用方法引用或 lambda 表达式/显式类封装一个调用,在需要时传递它。仅当您不信任嵌入在代码中的 lambda 表达式或方法引用的效率时,常量才有用,换句话说,几乎从不需要它们。【参考方案3】:

正如其他人所说,功能接口是公开一种方法的接口。它可能有不止一种方法,但所有其他方法都必须有一个默认实现。它被称为“功能接口”的原因是因为它有效地充当了一个函数。由于您可以将接口作为参数传递,这意味着函数现在是“一等公民”,就像在函数式编程语言中一样。这有很多好处,您会在使用 Stream API 时看到很多。当然,lambda 表达式是它们的主要用途。

【讨论】:

【参考方案4】:

一点也不。 Lambda 表达式是该注释的唯一点。

【讨论】:

好吧,lamdbas 也可以在没有注释的情况下工作。这是一个类似于@Override 的断言,让编译器知道你打算编写一些“功能性”的东西(如果你滑倒了会得到一个错误)。 直截了当和正确答案,虽然有点短。我花时间添加了一个more elaborated answer,用更多的话说同样的话……【参考方案5】:

可以将 lambda 表达式分配给函数式接口类型,但方法引用和匿名类也可以。

java.util.function 中的特定功能接口的一个好处是它们可以组合以创建新功能(如Function.andThenFunction.composePredicate.and 等),因为它们包含方便的默认方法.

【讨论】:

您应该详细说明此评论。方法引用和新功能呢?【参考方案6】:

只有一个抽象方法的接口称为功能接口。 使用@FunctionalInterface 不是强制性的,但最好将它与函数式接口一起使用,以避免意外添加额外的方法。如果接口使用@FunctionalInterface 注解,并且我们尝试使用多个抽象方法,则会引发编译器错误。

package com.akhi;
    @FunctionalInterface
    public interface FucnctionalDemo 

      void letsDoSomething();
      //void letsGo();      //invalid because another abstract method does not allow
      public String toString();    // valid because toString from Object 
      public boolean equals(Object o); //valid

      public static int sum(int a,int b)   // valid because method static
           
            return a+b;
        
        public default int sub(int a,int b)   //valid because method default
        
            return a-b;
        
    

【讨论】:

【参考方案7】:

功能界面:

在 Java 8 中引入 包含“单一抽象”方法的接口。

示例 1:

   interface CalcArea    // --functional interface
        double calcArea(double rad);
               

示例 2:

interface CalcGeometry  // --functional interface
    double calcArea(double rad);
    default double calcPeri(double rad) 
        return 0.0;
    
       

示例 3:

interface CalcGeometry   // -- not functional interface
    double calcArea(double rad);
    double calcPeri(double rad);
   

Java8注解——@FunctionalInterface

注解检查接口只包含一个抽象方法。如果不是,则引发错误。 即使缺少@FunctionalInterface,它仍然是函数式接口(如果有单个抽象方法)。注释有助于避免错误。 功能接口可能有额外的静态和默认方法。 例如可迭代、可比较、比较器。

函数式接口的应用:

方法参考 Lambda 表达式 构造函数引用

要学习函数式接口,首先要学习接口中的默认方法,学习函数式接口后,你会很容易理解方法引用和lambda表达式

【讨论】:

你的前两个例子应该有“抽象”关键字吗? @sofs1 接口中声明的方法默认是公共的和抽象的。对于抽象类中的方法,您必须使用抽象关键字。但是,也可以对接口中的方法使用抽象关键字。他们允许它与旧的 java 版本兼容,但不鼓励这样做。【参考方案8】:

您可以在 Java 8 中使用 lambda

public static void main(String[] args) 
    tentimes(inputPrm - > System.out.println(inputPrm));
    //tentimes(System.out::println);  // You can also replace lambda with static method reference


public static void tentimes(Consumer myFunction) 
    for (int i = 0; i < 10; i++)
        myFunction.accept("hello");

更多关于Java Lambdas和FunctionalInterfaces的信息

【讨论】:

【参考方案9】:

@FunctionalInterface 是随 Java 8 发布的一个新注解,它为 lambda 表达式提供目标类型,并用于代码的编译时检查。

当你想使用它时:

1- 你的接口不能有多个抽象方法,否则会报编译错误。

1- 你的接口应该是纯的,这意味着功能接口旨在由无状态类实现,纯的例子是Comparator接口,因为它不依赖于实现者的状态,在这个case No 会出现编译错误,但在很多情况下你将无法在这种接口上使用 lambda

java.util.function 包包含各种通用功能接口,例如PredicateConsumerFunctionSupplier

另外请注意,您可以在没有此注释的情况下使用 lambda。

【讨论】:

【参考方案10】:

除了其他答案之外,我认为“为什么使用函数式接口而不是直接使用 lambda 表达式”的主要原因可能与 Java 语言的面向对象的性质有关。

Lambda 表达式的主要属性有: 1. 可以左右传递 2. 可以在未来特定时间(多次)执行。现在为了在语言中支持这个特性,其他一些语言只是简单地处理这个问题。

例如,在 Java Script 中,函数(匿名函数或函数字面量)可以作为对象来寻址。因此,您可以简单地创建它们,也可以将它们分配给变量等等。例如:

var myFunction = function (...) 
    ...;

alert(myFunction(...));

或者通过 ES6,你可以使用箭头函数。

const myFunction = ... => ...

到目前为止,Java 语言设计者还没有接受通过这些方式处理提到的特性(函数式编程技术)。他们认为 Java 语言是面向对象的,因此他们应该通过面向对象的技术来解决这个问题。他们不想错过 Java 语言的简单性和一致性。

因此,他们使用接口,因为当需要只有一种方法的接口对象(我的意思是功能接口)时,您可以将其替换为 lambda 表达式。如:

ActionListener listener = event -> ...;

【讨论】:

【参考方案11】:

Functional Interfaces:如果一个接口有一个抽象方法,不管默认或静态方法的数量如何,它就被称为功能接口。函数接口用于 lamda 表达式。 RunnableCallableComparableComparatorFunctional 接口的几个例子。

重点说明:

使用注解@FunctionalInterface(可选)。 它应该只有 1 个抽象方法(与默认和静态的数量无关 方法)。 两个抽象方法给出编译错误(提供者@FunctionalInterface注解是 使用)。

这个thread 更详细地讨论了函数式接口对匿名类的好处以及如何使用它们。

【讨论】:

以上是关于Java 8 中使用的功能接口是啥?的主要内容,如果未能解决你的问题,请参考以下文章

java中接口的定义是啥?

在java中,啥是接口,接口的特点是啥

java开发的项目中模块、包、类、接口的关系是啥?

java开发的项目中模块、包、类、接口的关系是啥?

Java 学习总结(189)—— Java 8 功能接口使用总结

Java 学习总结(189)—— Java 8 功能接口使用总结