有人可以解释一下什么是函数对象,并且提供一个简单的例子吗?
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());
...
}
显然,这个例子是人为制造的。我们可以使用匿名内部类使代码更清晰一些,但这只是一个大致的想法。一个函数对象是一个可以作为函数使用的对象。
Java没有函数对象,因为在Java中函数不是一等公民对象。
但是你可以用接口来近似它们,例如Command对象:
public interface Command {
void execute(Object [] parameters);
}
更新于2017年3月18日:
自我最初撰写此文以来,JDK 8已经添加了lambda表达式。 java.util.function 包中有几个有用的接口。
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();
}
}
}
诚然,这不是最好的示例,因为每个实例只调用一次“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!
}
}
With a Functor. A Functor is basically a Lambda, but one that is wrapped in an object. While functions cannot be passed into other functions as parameters, objects can. So essentially, Functors and Lambdas are a ways to pass around functions.
So how can we implement a Functor into our writer-adapter? What we know is that close()
and flush()
are only useful with Closeable
and Flushable
objects. And that some Appendable
s are Flushable
, some Closeable
, some neither, some both.
Therefore, we can store a Flushable
and Closeable
object at the top of the class:
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();
}
}
The "every-time" checks have now been eliminated. But when the Appendable
is not a Flushable
or not a Closeable
, what should be stored?
A do nothing Functor...
class CloseableDoesNothing implements Closeable {
public void close() throws IOException {
}
}
class FlushableDoesNothing implements Flushable {
public void flush() throws IOException {
}
}
...which can be implemented as an anonymous inner class:
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...
}
To be most efficient, these do-nothing functors should be implemented as static final objects. And with that, here is the final version of our 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();
}
}
This particular example comes from this question on stackoverflow. A fully working, and fully-documented version of this example (including a testing function) can be found at the bottom of that question-post (above the answer).
Leaving our Writer
-Appendable
example, let's take a look at another way to implement Functors: with an Enum.
As an example, this enum has a move
function for each cardinal direction:
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);
}
}
Its constructor requires a MoveInDirection
object (which is an interface, but could also be an abstract class):
interface MoveInDirection { void move(int steps); }
There are naturally four concrete implementations of this interface, one per direction. Here is a trivial implementation for north:
class MoveNorth implements MoveInDirection { public void move(int steps) { System.out.println("Moved " + steps + " steps north."); } }
Using this Functor is done with this simple call:
CardinalDirection.WEST.move(3);
Which, in our example, outputs this to the console:
Moved 3 steps west.
And here is a full working example:
/**
<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.");
}
}
Output:
[C:\java_code]java EnumFunctorXmpl Moved 3 steps west. Moved 2 steps north. Moved 15 steps east.
I haven't started with Java 8 yet, so I can't write the Lambdas section yet :)
理解函数应用的概念
f.apply(x)
反转
x.map(f)
将x
称为函数对象
interface Functor<T> {
Functor<R> map(Function<T, R> f);
}
call()
,它会返回某些内容(但仍不需要参数)。 - Paul Bellora