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

Posted

技术标签:

【中文标题】如何在 Java8 中为 void(非 Void)方法指定函数类型?【英文标题】:How to specify function types for void (not Void) methods in Java8? 【发布时间】:2012-12-28 11:48:42 【问题描述】:

我正在使用 Java 8 来了解如何充当一等公民。我有以下sn-p:

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

我要做的是使用方法引用将方法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

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

【问题讨论】:

【参考方案1】:

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

函数类型声明为

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

但是,Consumer 类型与您正在寻找的类型兼容:

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 的方法,对吗?

我可以使用不同类型的方法引用,但在这种情况下,让我们通过在System.out 对象中使用println 方法来利用对象方法引用,如下所示:

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

或者我可以这样做

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

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

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

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 表达式和方法引用将绑定到对象引用,因此我们所拥有的只是作为一等公民的对象。重要的是功能已经存在,因为我们可以将对象作为参数传递,将它们绑定到变量引用并将它们作为来自其他方法的值返回,然后它们几乎可以达到类似的目的。

【讨论】:

顺便说一下,在 JDK 8 API 的最新版本中,Block 已更改为 Consumer 最后一段掩盖了一些重要的事实:Lambda 表达式和方法引用不仅仅是句法上的补充。 JVM 本身提供了新的类型来比匿名类(最重要的是MethodHandle)更有效地处理方法引用,并且使用它,Java 编译器能够生成更高效的代码,例如将不带闭包的 lambda 实现为静态方法,从而防止生成额外的类。 Function&lt;Void, Void&gt;的正确版本是Runnable @OrangeDog 这并不完全正确。在RunnableCallable 接口的cmets 中写到它们应该与threads 一起使用。这样做的恼人后果是,如果您直接调用 run() 方法,一些静态分析工具(如 Sonar)会报错。 纯粹来自Java背景,我想知道在Java8之后引入函数式接口和lambdas之后,Java不能将函数视为一等公民的具体情况是什么?【参考方案2】:

当您需要接受一个不带参数且不返回结果(void)的函数作为参数时,我认为最好还是有类似的东西

  public interface Thunk  void apply(); 

在您的代码中的某处。在我的函数式编程课程中,“thunk”这个词被用来描述这些函数。为什么它不在 java.util.function 中我无法理解。

在其他情况下,我发现即使 java.util.function 确实有一些与我想要的签名匹配的东西——当接口的命名与函数在我的代码。我想这与此处其他地方关于“可运行”的观点相似——这是一个与线程类相关的术语——所以虽然它可能有我需要的签名,但它仍然可能会让读者感到困惑。

【讨论】:

特别是Runnable 不允许检查异常,这使得它对许多应用程序毫无用处。由于Callable&lt;Void&gt; 也没有用,您似乎需要定义自己的。丑陋。 关于检查异常问题,Java 的新函数通常都在努力解决这个问题。对于 Stream API 之类的东西来说,它是一个巨大的障碍。他们的设计非常糟糕。【参考方案3】:

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

// Modify existing method
public static Void displayInt(Integer i) 
    System.out.println(i);
    return null;

// Or use Lambda
myForEach(theList, i -> System.out.println(i);return null;);

【讨论】:

【参考方案4】:

我觉得你应该使用消费者界面而不是Function&lt;T, R&gt;

消费者基本上是一个功能接口,旨在接受一个值并且不返回任何内容(即 void)

在您的情况下,您可以像这样在代码的其他地方创建消费者:

Consumer<Integer> myFunction = x -> 
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...

然后你可以用下面的sn-p替换你的myForEach代码:

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 

  list.forEach(x->myFunction.accept(x));

您将 myFunction 视为一等对象。

【讨论】:

除了现有的答案之外,这增加了什么?

以上是关于如何在 Java8 中为 void(非 Void)方法指定函数类型?的主要内容,如果未能解决你的问题,请参考以下文章

Visual C++ 如何使用线程启动非 void 函数

GWT RPC 机制如何使用非 void 返回类型

void 函数中的意外非 void 返回值(swift)

从“void*”转换为指向非“void”的指针需要显式转换(第 17 行)

如何调用从 Kotlin 获取非空 Void 参数的 Java 方法?

在phonegap中为ios压缩图像插件