如何在Java8中为void(不是Void)方法指定函数类型?

2022-08-31 06:50:05

我正在玩Java 8,以了解作为一等公民的功能。我有以下代码段:

package test;

import java.util.*;
import java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

我试图做的是使用方法引用将方法传递到方法。编译器将生成以下错误:displayIntmyForEach

src/test/Test.java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

编译器抱怨 .我不知道如何在签名中指定函数接口的类型,以便代码编译。我知道我可以简单地将返回类型更改为,然后返回。但是,在某些情况下,可能无法更改要传递到其他地方的方法。有没有一种简单的方法可以按原样重用?void cannot be converted to VoidmyForEachdisplayIntVoidnulldisplayInt


答案 1

您正在尝试使用错误的接口类型。在这种情况下,Function 类型不合适,因为它接收参数并具有返回值。相反,您应该使用消费者(以前称为块)

函数类型声明为

interface Function<T,R> {
  R apply(T t);
}

但是,使用者类型与您要查找的类型兼容:

interface Consumer<T> {
   void accept(T t);
}

因此,Consumer 与接收 T 且不返回任何内容(void)的方法兼容。这就是你想要的。

例如,如果我想显示列表中的所有元素,我可以简单地使用lambda表达式为其创建一个消费者:

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

您可以在上面看到,在本例中,lambda 表达式接收参数并且没有返回值。

现在,如果我想使用方法引用而不是lambda表达式来创建这种类型的使用,那么我需要一个接收字符串并返回void的方法,对吧?。

我可以使用不同类型的方法引用,但在本例中,让我们通过使用对象中的方法来利用对象方法引用,如下所示:printlnSystem.out

Consumer<String> block = System.out::println

或者我可以简单地做

allJedi.forEach(System.out::println);

该方法是合适的,因为它接收值并具有返回类型 void,就像 Consumer 中的方法一样。printlnaccept

因此,在代码中,您需要将方法签名更改为某种程度:

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

然后,您应该能够使用静态方法引用创建一个使用者,在您的情况下执行以下操作:

myForEach(theList, Test::displayInt);

最终,您甚至可以完全摆脱您的方法,只需执行以下操作:myForEach

theList.forEach(Test::displayInt);

关于一等公民的功能

总而言之,事实是Java 8不会具有作为一等公民的功能,因为结构函数类型不会添加到语言中。Java将简单地提供一种替代方法,从lambda表达式和方法引用中创建功能接口的实现。最终,lambda 表达式和方法引用将被绑定到对象引用,因此我们所拥有的只是作为一等公民的对象。重要的是功能已经存在,因为我们可以将对象作为参数传递,将它们绑定到变量引用并将它们作为来自其他方法的值返回,然后它们几乎可以用于类似的目的。


答案 2

当你需要接受一个函数作为参数,它不带参数,不返回任何结果(void),在我看来,最好还是有这样的东西

  public interface Thunk { void apply(); }

代码中的某个位置。在我的函数式编程课程中,“thunk”这个词被用来描述这样的函数。为什么它不在java.util.function中超出了我的理解范围。

在其他情况下,我发现即使java.util.function确实具有与我想要的签名匹配的东西 - 当接口的命名与代码中函数的使用不匹配时,它仍然感觉并不总是正确的。我想这是其他地方关于“Runnable”的类似观点 - 这是一个与Thread类相关的术语 - 所以虽然它可能有我需要的签名,但它仍然可能会让读者感到困惑。


推荐