Java 8中的函数式接口有什么用途?

189

我在Java 8中发现了一个新术语:“函数接口”。在使用lambda表达式时,我只找到了一个用法。

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

例如:

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

除了使用lambda表达式,在Java 8中它还有多少实用价值?

(这个问题与我所问的不同。那个问题是在问我们在使用lambda表达式时为什么需要函数式接口。我的问题是:除了与lambda表达式一起使用外,函数式接口还有哪些其他用途?)


1
它看起来与此链接重复。他们还讨论了为什么函数式接口中应该只有一个方法。https://dev59.com/HVwY5IYBdhLWcg3wF0le - Kulbhushan Singh
1
@KulbhushanSingh 在发布之前我看到了这个问题... 两个问题的意义不同... - Madhusudan
11个回答

144

@FunctionalInterface注解在编译时检查代码非常有用。在@FunctionalInterface或任何其他用作函数接口的接口中,除了staticdefault和抽象方法覆盖Object中的方法之外,您不能拥有多个方法。

但是,您可以不使用此注解使用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 不是一个函数式接口


47
更加精确地说,在函数式接口中,您必须恰好有一个不覆盖java.lang.Object中方法的抽象方法。 - Holger
9
这句话的意思是:“除了 staticdefault 方法之外,不要有多个 public 方法”,与“不要有超过一个 public 方法”略有不同。 - Holger
5
仍然不理解有它存在的任何意义。为什么会有人在乎他/她的接口有多少方法。标记接口仍然具有一定的意义和特定的目的。 文档和答案只解释了它的作用,而没有解释它到底有什么用处。而“用处”恰恰是问题提出者所询问的。因此,我不建议使用这个答案。 - saran3h
1
@VNT 编译错误会获取此接口的客户端,而不是更改接口本身。使用此注释,编译错误会出现在接口上,因此您可以确保没有人会破坏您的接口客户端。 - Sergii Bishyr
3
这展示了如何使用它们,但没有解释为什么我们需要它们。 - sheikh
显示剩余7条评论

25

文档确实区分了用途和用例。

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

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

由于函数接口是由Java语言规范定义的Java语言构造,因此只有该规范可以回答这个问题:

JLS §9.8. 函数式接口:

...

除了声明和实例化类(§15.9)的通常过程外,函数接口的实例还可以通过方法引用表达式和lambda表达式(§15.13、§15.27)来创建。

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

所以简而言之,在Java 8中没有其他用途。


可能有点过分或不相关(您可以选择不回答),但是当有人创建一个实用程序public static String generateTaskId()而另外一些人选择将其编写为public class TaskIdSupplier implements Supplier<String>以使其更“功能化”时,您会建议什么?get方法使用现有的生成实现。这是对功能接口的误用,特别是重用JDK内置的Supplier吗?附言:我找不到更好的位置/问答来提问。如果您能提供建议,我很乐意迁移。 - Naman
1
@Naman,当你创建一个命名类TaskIdSupplier时,你并没有使实用方法更加功能化。现在的问题是为什么要创建这个命名类。有些情况下需要这样的命名类型,例如当您想通过ServiceLoader支持查找实现时。让它实现Supplier也没有错。但是当你不需要它时,就不要创建它。当你只需要一个Supplier<String>时,使用DeclaringClass::generateTaskId已经足够了,消除了显式类的需求,这也是这种语言特性的重点。 - Holger
说实话,我在寻找一个推荐的理由。在工作中,我并没有真正感觉到 TaskIdSupplier 实现值得付出努力,但是 ServiceLoader 的概念却完全跳过了我的脑海。在这些讨论中遇到了一些问题,比如“当一个人可以继续开发自己的接口时,Supplierpublic 存在有什么用处?”和“为什么不把 public static Supplier<String> TASK_ID_SUPPLIER = () ->... 作为全局常量?”(1/2) - Naman
所有这些似乎很简单和基础,但在实施过程中做出决策或设计服务时,似乎很难对这些问题提供合理的解释,这肯定需要亲身体验各种语言特性。 - Naman
1
@Naman,Java中表示函数的惯用方式是方法,并且评估这些函数与调用它们是相同的。开发人员永远不应该被强制执行variable.genericMethodName(args)而不是meaningfulMethodName(args)。使用类类型来表示函数,无论是通过lambda表达式/方法引用还是手动创建的类,都只是传递函数的一种方式(在Java中没有真正的函数类型时)。只有在需要时才应这样做。 - Holger
1
当您有一个仅被传递的小代码片段时,可以创建一个封装它的lambda表达式。每当需要像方法一样调用它时(包括需要测试的情况,当代码片段不是微不足道的时),创建一个可调用的命名方法,并使用方法引用或lambda表达式/显式类封装调用,以在需要时传递它。常量只有在您不信任嵌入在代码中的lambda表达式或方法引用的效率时才有用,换句话说,它们几乎永远不需要。 - Holger

15

正如其他人所说,函数式接口是只暴露一个方法的接口。虽然它可能有多个方法,但所有其他方法必须有默认实现。它被称为“函数式接口”的原因是因为它实际上像一个函数一样。由于您可以将接口作为参数传递,这意味着函数现在像函数式编程语言中的“一等公民”一样。这具有许多好处,在使用流API时您会经常看到它们的应用。当然,Lambda表达式是它们的主要明显用途。


14

完全不是。Lambda表达式是该注释唯一的要点。


7
Lambda表达式同样可以不使用注解。这就像@Override一样是一种断言,让编译器知道你打算编写的代码是“函数式的”(如果你犯了错误会得到一个错误提示)。我的翻译是否符合您的要求? - Thilo
2
直截了当地给出正确答案,虽然有点简短。我花时间添加了一个更详细的答案,用更多的话说了同样的事情... - Holger

8

一个Lambda表达式可以赋值给一个函数接口类型,但方法引用和匿名类也可以。

java.util.function中特定的函数接口之一的好处是它们可以组合创建新的函数(例如Function.andThenFunction.compose, Predicate.and等),因为它们包含了方便的默认方法。


你应该更详细地阐述这个评论。方法引用和新函数怎么样? - K.Nicholas

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;
        }
    }

5

功能接口:

  • 在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注解,它仍然是函数式接口(如果只有一个抽象方法)。该注解可以帮助避免错误。
  • 函数式接口可能有额外的静态和默认方法。
  • 例如:Iterable<>、Comparable<>和Comparator<>。

函数式接口的应用:

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

要学习函数式接口,首先要学习接口中的默认方法,然后在学习函数式接口之后,理解方法引用和Lambda表达式将变得容易。


你的前两个例子是否应该加上“abstract”关键字? - sofs1
1
@sofs1 接口中声明的方法默认为公共和抽象。在抽象类中,如果有方法需要声明为抽象方法,则必须使用 abstract 关键字。然而,在接口中使用 abstract 关键字也是可以的。这是为了兼容旧版本的 Java 而被允许的,但并不鼓励这样做。 - Ketan

2
你可以在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 LambdasFunctionalInterfaces的更多信息,请参见。


1

@FunctionalInterface 是 Java 8 中发布的新注释,为 lambda 表达式提供目标类型,并在编译时检查您的代码。

当您想要使用它时:

1- 您的接口不能有多个抽象方法,否则将给出编译错误。

2- 您的接口应该是纯净的,这意味着函数接口旨在由无状态类实现,例如 Comparator 接口,因为它不依赖于实现者的状态,在这种情况下不会出现编译错误,但在许多情况下,您将无法使用此类接口的 lambda 表达式。

java.util.function 包包含各种通用的函数接口,例如 PredicateConsumerFunctionSupplier

请注意,您可以在不使用此注释的情况下使用 lambda 表达式。


1

功能接口:如果一个接口只有一个抽象方法,不考虑默认方法或静态方法的数量,则称其为功能接口。功能接口用于lambda表达式。RunnableCallableComparableComparator是一些Functional Interface的例子。

关键点:

  • 使用注释@FunctionalInterface(可选)。
  • 它应该只有1个抽象方法(无论默认和静态方法的数量如何)。
  • 两个抽象方法会导致编译错误(提供者使用@FunctionalInterface注释)。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接