【问题标题】:Help with understanding a function object or functor in Java帮助理解 Java 中的函数对象或函子
【发布时间】:2011-11-14 05:30:54
【问题描述】:

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

【问题讨论】:

    标签: java functor


    【解决方案1】:

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

    除此之外:将函数对象称为“函子”是对该术语的严重滥用:另一种“函子”是数学中的核心概念,并且在计算机科学中具有直接作用(参见“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 演算既是(也许)最古老的编程语言,也是大部分计算机科学的理论基础。当 Alonzo Church(计算机科学的主要创始人之一)发明它时,他使用希腊字母 lambda 表示函数,因此得名。

    其他语言,包括函数式语言(Lisp、ML、Haskell、Erlang 等)、大多数主要的动态语言(Python、Ruby、JavaScript 等)和其他应用程序语言(C#、Scala、Go、D等)支持某种形式的“Lambda Literal”。甚至 C++ 现在也有它们(从 C++11 开始),尽管在这种情况下它们会稍微复杂一些,因为 C++ 缺乏自动内存管理,并且不会为您保存堆栈帧。

    【讨论】:

    • +1 另外值得一提的是Callable 及其方法call(),它返回一些东西(但仍然不带参数)。
    • 这需要尽快更新。 Java 8 将支持第一类函数。语法看起来很像 C#。
    【解决方案2】:

    仿函数是一个函数对象。

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

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

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

    2017 年 3 月 18 日更新:

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

    【讨论】:

    • +1 而 Guava 的 Function 是另一个近似值。
    【解决方案3】:

    从每次检查,到 Functor,再到 Java 8 Lambda(有点)

    问题

    以这个示例类为例,将adaptsAppendable 转换为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();
          }
       }
    }
    

    并非所有Appendables 都是FlushableCloseable,但那些也必须关闭和刷新。因此,必须在每次调用 flush()close() 时检查 Appendable 对象的实际类型,并且当它确实是该类型时,它会被强制转换并调用函数。

    诚然,这不是最好的例子,因为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() 只需要进行布尔检查。虽然肯定是一种改进,但如何才能完全消除这些功能检查?

    如果你能以某种方式定义一个函数,它可以由类存储然后由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。那么如何在 Java 8 之前的版本中做到这一点呢?

    函子

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

    那么,我们如何在编写器适配器中实现 Functor?我们知道close()flush() 仅对CloseableFlushable 对象有用。有些Appendables 是Flushable,有些是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 时,应该存储什么?

    什么也不做仿函数

    一个什么都不做的函子...

    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...
    
    }
    

    为了最有效,这些无所事事的仿函数应该被实现为静态最终对象。有了这个,这是我们课程的最终版本:

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

    这个特殊的例子来自this question 上的stackoverflow。可以在该问题帖的底部(答案上方)找到此示例的完整工作且完整记录的版本(包括测试功能)。

    用枚举实现函子

    离开我们的 Writer-Appendable 示例,让我们看看另一种实现 Functor 的方法:使用 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 对象(它是一个接口,但也可以是一个抽象类):

    界面 MoveInDirection {
       无效移动(整数步);
    }

    这个接口自然有四个具体的实现,每个方向一个。这是一个简单的北方实现:

    类 MoveNorth 实现 MoveInDirection {
       公共无效移动(整数步){
          System.out.println("向北移动 " + 步数 + " 步数。");
       }
    }

    通过这个简单的调用来使用这个 Functor:

    CardinalDirection.WEST.move(3);

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

    向西移动了 3 步。

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

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

    输出:

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

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

    【讨论】:

    【解决方案4】:

    取函数应用的概念

    f.apply(x)
    

    逆向

    x.map(f)
    

    调用x一个函子

    interface Functor<T> {
        Functor<R> map(Function<T, R> f);
    }
    

    【讨论】:

      猜你喜欢
      • 2012-01-18
      • 2017-01-04
      • 1970-01-01
      • 1970-01-01
      • 2021-01-13
      • 1970-01-01
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多