【发布时间】:2011-11-14 05:30:54
【问题描述】:
有人可以解释什么是函子并提供一个简单的例子吗?
【问题讨论】:
有人可以解释什么是函子并提供一个简单的例子吗?
【问题讨论】:
函数对象就是这样。既是对象又是函数的东西。
除此之外:将函数对象称为“函子”是对该术语的严重滥用:另一种“函子”是数学中的核心概念,并且在计算机科学中具有直接作用(参见“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++ 缺乏自动内存管理,并且不会为您保存堆栈帧。
【讨论】:
call(),它返回一些东西(但仍然不带参数)。
仿函数是一个函数对象。
Java 没有它们,因为函数不是 Java 中的一等对象。
但是您可以使用接口来近似它们,例如 Command 对象:
public interface Command {
void execute(Object [] parameters);
}
2017 年 3 月 18 日更新:
自从我第一次写这篇文章以来,JDK 8 已经添加了 lambdas。 java.util.function 包有几个有用的接口。
【讨论】:
以这个示例类为例,将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();
}
}
}
并非所有Appendables 都是Flushable 或Closeable,但那些也必须关闭和刷新。因此,必须在每次调用 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() 仅对Closeable 和Flushable 对象有用。有些Appendables 是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 {
}
}
...可以实现为匿名内部类:
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 部分:)
【讨论】:
取函数应用的概念
f.apply(x)
逆向
x.map(f)
调用x一个函子
interface Functor<T> {
Functor<R> map(Function<T, R> f);
}
【讨论】: