有人可以解释一下仿函数是什么并提供一个简单的例子吗?
答案 0 :(得分:38)
一个功能对象就是这样。既是对象又是函数的东西。
除此之外:将函数对象称为“仿函数”是对术语的严重滥用:不同类型的“仿函数”是数学中的核心概念,并且在计算机科学中具有直接作用(参见“Haskell Functors”) “)。这个术语在ML中的使用方式略有不同,所以除非你用Java实现其中一个概念(你可以!),请停止使用这个术语。它使简单的事情变得复杂。
回到答案: Java没有“第一类函数”,也就是说,你不能将函数作为参数传递给函数。这在语法代码表示的多个层次上都是正确的,并且类型系统缺少“函数构造函数”
换句话说,你不能这样写:
public static void tentimes(Function f){
for(int i = 0; i < 10; i++)
f();
}
...
public static void main{
...
tentimes(System.out.println("hello"));
...
}
这真的很烦人,因为我们希望能够做像图形用户界面库这样的事情,你可以将“回调”功能与点击按钮相关联。
那我们该怎么做?
嗯,一般解决方案(由其他海报讨论)是使用我们可以调用的单个方法定义接口。例如,Java一直使用名为Runnable
的接口来处理这类事情,它看起来像:
public interface Runnable{
public void run();
}
现在,我们可以从上面重写我的例子:
public static void tentimes(Runnable r){
for(int i = 0; i < 10; i++)
r.run();
}
...
public class PrintHello implements Runnable{
public void run{
System.out.println("hello")
}
}
---
public static void main{
...
tentimes(new PrintHello());
...
}
显然,这个例子是人为的。我们可以使用匿名内部类使这段代码更好一些,但这可以得到一般的想法。
以下是故障发生的地方:Runnable
仅适用于不带任何参数的函数,并且不返回任何有用的函数,因此最终为每个作业定义一个新接口。例如,Mohammad Faisal答案中的界面Comparator
。提供更通用的方法,以及采用语法的方法,是Java 8(Java的下一个版本)的主要目标,并且被大量推送到Java 7中。这在函数抽象机制之后被称为“lambda”。在Lambda微积分中。 Lambda Calculus(也许)是最古老的编程语言,也是计算机科学的理论基础。当Alonzo Church(计算机科学的主要创始人之一)发明它时,他使用希腊字母lambda作为函数,因此得名。
其他语言,包括函数式语言(Lisp,ML,Haskell,Erlang等),大多数主要动态语言(Python,Ruby,JavaScript等)和其他应用程序语言(C#,Scala,Go,D)等)支持某种形式的“Lambda Literal”。即使C ++现在也有它们(从C ++ 11开始),虽然在这种情况下它们有点复杂,因为C ++缺乏自动内存管理,并且不会为你保存堆栈帧。
答案 1 :(得分:14)
仿函数是一个函数对象。
Java没有它们,因为函数不是Java中的第一类对象。
但你可以用接口来近似它们,比如Command对象:
public interface Command {
void execute(Object [] parameters);
}
2017年3月18日更新:
自从我第一次写这篇文章以来,JDK 8已添加了lambdas。 java.util.function包有几个有用的接口。
答案 2 :(得分:6)
将此示例类adapts Appendable加入Writer:
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
/**
<P>{@code java WriterForAppendableWChecksInFunc}</P>
**/
public class WriterForAppendableWChecksInFunc extends Writer {
private final Appendable apbl;
public WriterForAppendableWChecksInFunc(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
}
//Required functions, but not relevant to this post...START
public void write(char[] a_c, int i_ndexStart, int i_ndexEndX) throws IOException {
public Writer append(char c_c) throws IOException {
public Writer append(CharSequence text) throws IOException {
public Writer append(CharSequence text, int i_ndexStart, int i_ndexEndX) throws IOException {
//Required functions, but not relevant to this post...END
public void flush() throws IOException {
if(apbl instanceof Flushable) {
((Flushable)apbl).flush();
}
}
public void close() throws IOException {
flush();
if(apbl instanceof Closeable) {
((Closeable)apbl).close();
}
}
}
并非所有Appendable
都是Flushable
或Closeable
,但必须同时关闭和刷新。因此,必须在每次调用Appendable
和flush()
时检查close()
对象的实际类型,并且当它确实是该类型时,它将被转换并调用该函数。
不可否认,这不是最好的例子,因为close()
每个实例只调用一次,flush()
也不一定被调用。此外,instanceof
虽然具有反思性,但考虑到这个特定的示例用法,并不算太糟糕。尽管如此,每次你需要做其他事情时都需要检查的概念是真实的,并且当它真正重要时,避免这些“每次”检查会带来显着的好处。
那你从哪里开始呢?如何在不影响代码的情况下避免这些检查?
在我们的示例中,最简单的步骤是将所有instanceof
项检查移至构造函数。
public class WriterForAppendableWChecksInCnstr extends Writer {
private final Appendable apbl;
private final boolean isFlshbl;
private final boolean isClsbl;
public WriterForAppendableWChecksInCnstr(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
isFlshbl = (apbl instanceof Flushable);
isClsbl = (apbl instanceof Closeable);
}
//write and append functions go here...
public void flush() throws IOException {
if(isFlshbl) {
((Flushable)apbl).flush();
}
}
public void close() throws IOException {
flush();
if(isClsbl) {
((Closeable)apbl).close();
}
}
}
现在这些“重型”检查只进行一次,只需要flush()
和close()
进行布尔检查。虽然肯定是一种改进,但如何完全消除这些功能检查?
如果只有你能以某种方式定义一个函数,它可以由存储由类,然后使用 by flush()
和close()
...
public class WriterForAppendableWChecksInCnstr extends Writer {
private final Appendable apbl;
private final FlushableFunction flshblFunc; //If only!
private final CloseableFunction clsblFunc; //If only!
public WriterForAppendableWChecksInCnstr(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
if(apbl instanceof Flushable) {
flshblFunc = //The flushable function
} else {
flshblFunc = //A do-nothing function
}
if(apbl instanceof Closeable) {
clsblFunc = //The closeable function
} else {
clsblFunc = //A do-nothing function
}
}
//write and append functions go here...
public void flush() throws IOException {
flshblFunc(); //If only!
}
public void close() throws IOException {
flush();
clsblFunc(); //If only!
}
}
但是passing functions is not possible ......至少在Java 8 Lambdas之前。那么你如何在8版之前的Java版本中做到这一点?
使用Functor。 Functor基本上是一个Lambda,但它包含在一个对象中。虽然函数不能作为参数传递给其他函数,但对象可以。从本质上讲,Functors和Lambdas是一种传递函数的方法。
那么我们如何在编写器适配器中实现Functor?我们知道close()
和flush()
仅适用于Closeable
和Flushable
个对象。有些Appendable
是Flushable
,有些是Closeable
,有些Flushable
,有些都不是。
因此,我们可以在课程顶部存储Closeable
和public class WriterForAppendable extends Writer {
private final Appendable apbl;
private final Flushable flshbl;
private final Closeable clsbl;
public WriterForAppendable(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
//Avoids instanceof at every call to flush() and close()
if(apbl instanceof Flushable) {
flshbl = apbl; //This Appendable *is* a Flushable
} else {
flshbl = //?????? //But what goes here????
}
if(apbl instanceof Closeable) {
clsbl = apbl; //This Appendable *is* a Closeable
} else {
clsbl = //?????? //And here????
}
this.apbl = apbl;
}
//write and append functions go here...
public void flush() throws IOException {
flshbl.flush();
}
public void close() throws IOException {
flush();
clsbl.close();
}
}
个对象:
Appendable
现在已经取消了“每次”检查。但是当Flushable
不一个Closeable
或不一个class CloseableDoesNothing implements Closeable {
public void close() throws IOException {
}
}
class FlushableDoesNothing implements Flushable {
public void flush() throws IOException {
}
}
时,应该存储什么?
A do nothing Functor ...
public WriterForAppendable(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
//Avoids instanceof at every call to flush() and close()
flshbl = ((apbl instanceof Flushable)
? (Flushable)apbl
: new Flushable() {
public void flush() throws IOException {
}
});
clsbl = ((apbl instanceof Closeable)
? (Closeable)apbl
: new Closeable() {
public void close() throws IOException {
}
});
}
//the rest of the class goes here...
}
...可以作为匿名内部类实现:
package xbn.z.xmpl.lang.functor;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;
public class WriterForAppendable extends Writer {
private final Appendable apbl;
private final Flushable flshbl;
private final Closeable clsbl;
//Do-nothing functors
private static final Flushable FLUSHABLE_DO_NOTHING = new Flushable() {
public void flush() throws IOException {
}
};
private static final Closeable CLOSEABLE_DO_NOTHING = new Closeable() {
public void close() throws IOException {
}
};
public WriterForAppendable(Appendable apbl) {
if(apbl == null) {
throw new NullPointerException("apbl");
}
this.apbl = apbl;
//Avoids instanceof at every call to flush() and close()
flshbl = ((apbl instanceof Flushable)
? (Flushable)apbl
: FLUSHABLE_DO_NOTHING);
clsbl = ((apbl instanceof Closeable)
? (Closeable)apbl
: CLOSEABLE_DO_NOTHING);
}
public void write(char[] a_c, int i_ndexStart, int i_ndexEndX) throws IOException {
apbl.append(String.valueOf(a_c), i_ndexStart, i_ndexEndX);
}
public Writer append(char c_c) throws IOException {
apbl.append(c_c);
return this;
}
public Writer append(CharSequence c_q) throws IOException {
apbl.append(c_q);
return this;
}
public Writer append(CharSequence c_q, int i_ndexStart, int i_ndexEndX) throws IOException {
apbl.append(c_q, i_ndexStart, i_ndexEndX);
return this;
}
public void flush() throws IOException {
flshbl.flush();
}
public void close() throws IOException {
flush();
clsbl.close();
}
}
为了最有效率,这些无操作仿函数应该实现为静态最终对象。有了它,这是我们班级的最终版本:
Writer
此特定示例来自this question上的stackoverflow。该示例的完整工作和完整文档版本(包括测试功能)可以在该问题的底部找到(在答案之上)。
离开我们的Appendable
- move
示例,让我们看一下实现Functors的另一种方法:使用Enum。
例如,此枚举对每个主要方向都有public enum CardinalDirection {
NORTH(new MoveNorth()),
SOUTH(new MoveSouth()),
EAST(new MoveEast()),
WEST(new MoveWest());
private final MoveInDirection dirFunc;
CardinalDirection(MoveInDirection dirFunc) {
if(dirFunc == null) {
throw new NullPointerException("dirFunc");
}
this.dirFunc = dirFunc;
}
public void move(int steps) {
dirFunc.move(steps);
}
}
函数:
MoveInDirection
它的构造函数需要一个/**
<P>Demonstrates a Functor implemented as an Enum.</P>
<P>{@code java EnumFunctorXmpl}</P>
**/
public class EnumFunctorXmpl {
public static final void main(String[] ignored) {
CardinalDirection.WEST.move(3);
CardinalDirection.NORTH.move(2);
CardinalDirection.EAST.move(15);
}
}
enum CardinalDirection {
NORTH(new MoveNorth()),
SOUTH(new MoveSouth()),
EAST(new MoveEast()),
WEST(new MoveWest());
private final MoveInDirection dirFunc;
CardinalDirection(MoveInDirection dirFunc) {
if(dirFunc == null) {
throw new NullPointerException("dirFunc");
}
this.dirFunc = dirFunc;
}
public void move(int steps) {
dirFunc.move(steps);
}
}
interface MoveInDirection {
void move(int steps);
}
class MoveNorth implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps north.");
}
}
class MoveSouth implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps south.");
}
}
class MoveEast implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps east.");
}
}
class MoveWest implements MoveInDirection {
public void move(int steps) {
System.out.println("Moved " + steps + " steps west.");
}
}
对象(它是一个接口,但也可以是一个抽象类):
interface MoveInDirection { void move(int steps); }
此接口自然有四种具体实现,每个方向一个。以下是北方的一个简单实现:
class MoveNorth implements MoveInDirection { public void move(int steps) { System.out.println("Moved " + steps + " steps north."); } }
使用此Functor完成了这个简单的调用:
CardinalDirection.WEST.move(3);
在我们的示例中,将其输出到控制台:
Moved 3 steps west.
这是一个完整的工作示例:
{{1}}
输出:
[C:\java_code]java EnumFunctorXmpl Moved 3 steps west. Moved 2 steps north. Moved 15 steps east.
我还没有开始使用Java 8,所以我还不能编写Lambdas部分:)
答案 3 :(得分:1)
采用功能应用的概念
f.apply(x)
反
x.map(f)
致电x
仿函数
interface Functor<T> {
Functor<R> map(Function<T, R> f);
}