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

时间:2011-09-10 03:40:39

标签: java functor

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

4 个答案:

答案 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)

从每次检查,到Functors,再到Java 8 Lambdas(排序)

问题

将此示例类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都是FlushableCloseable,但必须同时关闭和刷新。因此,必须在每次调用Appendableflush()时检查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()仅适用于CloseableFlushable个对象。有些AppendableFlushable,有些是Closeable,有些Flushable,有些都不是。

因此,我们可以在课程顶部存储Closeablepublic 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 { } } 时,应该存储什么?

不做任何Functors

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。该示例的完整工作和完整文档版本(包括测试功能)可以在该问题的底部找到(在答案之上)。

使用枚举实现Functors

离开我们的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);
}