帮助理解Java中的函数对象或函子

有人可以解释一个仿函数是什么,并提供一个简单的例子吗?

函数对象就是这样。 既是对象又是函数的东西。

除此之外:将函数对象称为“仿函数”是对术语的严重滥用:不同类型的“仿函数”是数学中的核心概念,并且在计算机科学中具有直接作用(参见“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")); ... } 

这真的很烦人,因为我们希望能够执行诸如图形用户界面库之类的操作,您可以将“回调”function与单击按钮相关联。

那么我们该怎么办?

那么,一般解决方案(由其他海报讨论)是使用我们可以调用的单个方法定义接口。 例如,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等)支持某种forms的“Lambda Literal”。 即使C ++现在也有它们(从C ++ 11开始),虽然在这种情况下它们有点复杂,因为C ++缺乏自动内存管理,并且不会为你保存堆栈帧。

仿函数是一个函数的对象。

Java没有它们,因为函数不是Java中的第一类对象。

但您可以使用接口来近似它们,例如Command对象:

 public interface Command { void execute(Object [] parameters); } 

2017年3月18日更新:

自从我第一次写这篇文章以来,JDK 8就添加了lambdas。 java.util.function包有几个有用的接口。

从每次检查,到Functors,再到Java 8 Lambdas(有点)

问题

以这个示例类为例,它将Appendable 调整为Writer :

  import java.io.Closeable; import java.io.Flushable; import java.io.IOException; import java.io.Writer; import java.util.Objects; /** 

{@code java WriterForAppendableWChecksInFunc}

**/ 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都可以冲洗或Closeable ,但是那些也必须关闭和冲洗。 因此,必须在每次调用flush()close()时检查Appendable对象的实际类型,并且当它确实是该类型时,它将被转换并调用该函数。

不可否认,这不是最好的例子,因为close()仅在每个实例中调用一次,而flush()也不一定被调用。 此外,虽然这个特定的示例用法,但reflection性的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()进行布尔检查。 虽然肯定是一种改进,但如何完全消除这些function检查?

如果只有你能以某种方式定义一个可以由类存储然后由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! } } 

但是传递函数是不可能的 ……至少在Java 8 Lambdas之前是这样 。 那么你如何在8版之前的Java版本中做到这一点?

函子

用Functor 。 Functor基本上是一个Lambda,但它包含在一个对象中。 虽然函数不能作为参数传递给其他函数,但对象可以 。 从本质上讲,Functors和Lambdas是一种传递函数的方法

那么我们如何在我们的编写器适配器中实现Functor呢? 我们所知道的是close()flush()仅对CloseableFlushable对象有用。 而且有些Appendable是可Closeable ,有些是Closeable ,有些是两种,有些都是。

因此,我们可以在类的顶部存储一个FlushableCloseable对象:

 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 ,应该存储什么?

什么都不做Functors

一个无所事事的Functor ……

 class CloseableDoesNothing implements Closeable { public void close() throws IOException { } } class FlushableDoesNothing implements Flushable { public void flush() 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 : 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... } 

为了最有效,这些无操作仿函数应该作为静态最终对象实现。 有了它,这是我们class级的最终版本:

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

这个特定的例子来自stackoverflow上的这个问题 。 该示例的完整工作和完整文档版本(包括测试function)可以在该问题的底部找到(在答案之上)。

使用枚举实现Functors

离开我们的Writer – 可Appendable示例,让我们看一下实现Functors的另一种方法:使用Enum。

例如,此枚举对每个基本方向都有一个move函数:

 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对象(它是一个接口,但也可以是一个抽象类):

  interface MoveInDirection {
    void move(int steps);
 } 

这个界面自然有四个具体实现,每个方向一个。 以下是北方的一个简单实现:

 类MoveNorth实现MoveInDirection {
    public void move(int steps){
       System.out.println(“Moved”+ steps +“steps north。”);
    }
 } 

通过这个简单的调用就可以使用这个Functor:

  CardinalDirection.WEST.move(3); 

在我们的示例中,将其输出到控制台:

 向西移3步。 

这是一个完整的工作示例:

 /** 

Demonstrates a Functor implemented as an Enum.

{@code java EnumFunctorXmpl}

**/ 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."); } }

输出:

  [C:\ java_code] java EnumFunctorXmpl
向西移3步。
向北移动了两步。
向东移动了15步。 

我还没有开始使用Java 8,所以我还不能编写Lambdas部分:)

采取function应用的概念

 f.apply(x) 

 x.map(f) 

x仿函数

 interface Functor { Functor map(Function f); }