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
注释对于代码的编译时间检查很有用。除了static
、default
和覆盖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
中的方法。
……这与“除了static
和default
之外没有多个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
的概念完全让我忘记了。在我们进行的这些讨论中遇到了一些问题,例如 当一个人可以继续开发自己的界面时,Supplier
的 public
存在有什么用处? 和 为什么不呢?将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.andThen
和Function.compose
、Predicate.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
函数式接口的应用:
方法参考 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
包包含各种通用功能接口,例如Predicate
、Consumer
、Function
和Supplier
。
另外请注意,您可以在没有此注释的情况下使用 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 表达式。 Runnable
、Callable
、Comparable
、Comparator
是Functional
接口的几个例子。
重点说明:
使用注解@FunctionalInterface
(可选)。
它应该只有 1 个抽象方法(与默认和静态的数量无关
方法)。
两个抽象方法给出编译错误(提供者@FunctionalInterface
注解是
使用)。
这个thread 更详细地讨论了函数式接口对匿名类的好处以及如何使用它们。
【讨论】:
以上是关于Java 8 中使用的功能接口是啥?的主要内容,如果未能解决你的问题,请参考以下文章