如何在 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<Void, Void>
的正确版本是Runnable
。
@OrangeDog 这并不完全正确。在Runnable
和Callable
接口的cmets 中写到它们应该与threads 一起使用。这样做的恼人后果是,如果您直接调用 run()
方法,一些静态分析工具(如 Sonar)会报错。
纯粹来自Java背景,我想知道在Java8之后引入函数式接口和lambdas之后,Java不能将函数视为一等公民的具体情况是什么?【参考方案2】:
当您需要接受一个不带参数且不返回结果(void)的函数作为参数时,我认为最好还是有类似的东西
public interface Thunk void apply();
在您的代码中的某处。在我的函数式编程课程中,“thunk”这个词被用来描述这些函数。为什么它不在 java.util.function 中我无法理解。
在其他情况下,我发现即使 java.util.function 确实有一些与我想要的签名匹配的东西——当接口的命名与函数在我的代码。我想这与此处其他地方关于“可运行”的观点相似——这是一个与线程类相关的术语——所以虽然它可能有我需要的签名,但它仍然可能会让读者感到困惑。
【讨论】:
特别是Runnable
不允许检查异常,这使得它对许多应用程序毫无用处。由于Callable<Void>
也没有用,您似乎需要定义自己的。丑陋。
关于检查异常问题,Java 的新函数通常都在努力解决这个问题。对于 Stream API 之类的东西来说,它是一个巨大的障碍。他们的设计非常糟糕。【参考方案3】:
将返回类型设置为Void
而不是void
和return 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<T, R>
。
消费者基本上是一个功能接口,旨在接受一个值并且不返回任何内容(即 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)方法指定函数类型?的主要内容,如果未能解决你的问题,请参考以下文章
从“void*”转换为指向非“void”的指针需要显式转换(第 17 行)