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

Val*_*tin 119 java java-8

我正在玩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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我想要做的是使用方法引用displayInt将方法传递给方法myForEach.编译器会产生以下错误:

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
Run Code Online (Sandbox Code Playgroud)

编译器抱怨说void cannot be converted to Void.我不知道如何myForEach在代码编译的签名中指定函数接口的类型.我知道我可以简单地改变displayIntto 的返回类型Void然后返回null.但是,在某些情况下,我无法改变我想要传递到其他地方的方法.是否有一种简单的方法可以重复使用displayInt

Edw*_*rzo 214

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

Function类型声明为

interface Function<T,R> {
  R apply(T t);
}
Run Code Online (Sandbox Code Playgroud)

但是,消费者类型与您正在寻找的兼容:

interface Consumer<T> {
   void accept(T t);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

您可以在上面看到,在这种情况下,lambda表达式接收一个参数并且没有返回值.

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

我可以使用不同类型的方法引用的,但在这种情况下,让我们趁对象方法引用的使用println该方法System.out的对象,像这样:

Consumer<String> block = System.out::println
Run Code Online (Sandbox Code Playgroud)

或者我可以做到

allJedi.forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)

println方法是合适的,因为它接收一个值并返回类型为void,就像acceptConsumer中的方法一样.

因此,在您的代码中,您需要将方法签名更改为:

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}
Run Code Online (Sandbox Code Playgroud)

然后你应该能够通过以下方式创建一个使用静态方法引用的消费者:

myForEach(theList, Test::displayInt);
Run Code Online (Sandbox Code Playgroud)

最终,你甚至可以myForEach完全摆脱你的方法,只需:

theList.forEach(Test::displayInt);
Run Code Online (Sandbox Code Playgroud)

关于作为一等公民的职能

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

  • 而'Function <Void,Void>`的正确版本是`Runnable`. (7认同)
  • 最后一段忽略了一些重要事实:Lambda表达式和方法引用不仅仅是一种语法添加.JVM本身提供了比匿名类(最重要的是`MethodHandle`)更有效地处理方法引用的新类型,并且使用它,Java编译器能够生成更高效的代码,例如实现lambdas而不作为静态方法的闭包,从而防止产生额外的课程. (4认同)
  • @OrangeDog这不完全正确.在对"Runnable"和"Callable"接口的注释中,写了它们应该与[threads]一起使用(http://docs.oracle.com/javase/7/docs/api/java/lang/ Runnable.html).令人讨厌的结果是,如果直接调用`run()`方法,一些静态分析工具(如Sonar)会抱怨. (2认同)

LOA*_*OAS 18

当你需要接受一个函数作为参数而不接受任何参数并且没有返回结果(void)时,在我看来,最好还是有类似的东西

  public interface Thunk { void apply(); }
Run Code Online (Sandbox Code Playgroud)

你代码中的某个地方.在我的函数式编程课程中,"thunk"一词用于描述这些函数.为什么它不在java.util.function中是我无法理解的.

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


Doj*_*ojo 7

将返回类型设置为Void而不是voidandreturn null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}
Run Code Online (Sandbox Code Playgroud)

或者

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});
Run Code Online (Sandbox Code Playgroud)