函数接口是在Java 8中引入的,用于在Java中实现函数编程。它表示一个接受一个参数并产生结果的函数。它很容易练习和阅读,但除了让它看起来很酷之外,我仍在努力理解它的好处。例如,
Function<Integer, Double> half = a -> a / 2.0;
Function<Double, Double> triple = b -> b * 3;
double result = half.andThen(triple).apply(8);
可以像一样转换为标准方法
private Double half(int a) {
return a / 2.0;
}
private Double triple (int b) {
return b * 3;
}
double result = triple(half(8));
那么使用Function有什么好处呢?正如它所指的函数式编程,Java中的函数式编程究竟是什么,它能带来什么好处?它会像这样受益吗:
- 执行将函数链接在一起(例如andThen&compose)
- Java
Stream
内部的用法 - access修饰符as函数倾向于用private而不是public来定义,而方法可以是
基本上,我很想知道,在什么情况下,我们更喜欢使用函数而不是普通方法?是否存在无法使用或难以使用的用例,或者使用普通方法转换的用例?
Function
的一个用法是在Streams中。现在每个人都使用map
方法,我相信:
此map
方法接受Function
作为参数。这允许编写一个非常优雅的代码——这在Java8:之前是无法实现的
Stream.of("a", "b", "c")
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
// List of A, B, C
现在确实有方法引用和功能接口(其中一个当然是Function
),这允许您使用方法引用将上面的示例重写为:
Stream.of("a", "b", "c")
.map(String::toUpperCase)
.collect(Collectors.toList())
但这只是一个句法糖——map
当然仍然接受Function
作为参数。
使用Java本身的Function
的另一个示例是StackWalker
:这里有一个例子:
List<StackFrame> frames = StackWalker.getInstance().walk(s ->
s.dropWhile(f -> f.getClassName().startsWith("com.foo."))
.limit(10)
.collect(Collectors.toList()));
}
注意对walk
方法的调用——它接受一个函数作为参数。
所以,归根结底,它只是另一个可以帮助程序员表达他/她的意图的工具。在适当的地方明智地使用它。
假设我想编写一个applyTwice
函数:
double applyTwice(double x, Function<Double, Double> f) {
return f.apply(f.apply(x));
}
这需要将函数表示为对象。
当您想在调用方提供的任意代码周围放置一些结构时,函数非常有用。
几天前我在工作场所不得不使用的一个例子是,当我想根据条件延迟计算消息时。例如,想象logger
的用法如下:
logger.debug("my-heavy-computed-message-here");
现在想象一下,"my-heavy-computed-message-here"
的计算实际上就是——计算起来很重;但您只想在启用DEBUG
记录器的情况下显示它。人们通常做的是:
if(logger.isDebugEnabled()) {
logger.debug("my-heavy-computed-message-here");
}
这太难看了。相反,我们有一些代码,以Function
(或Supplier
)作为输入:
logger.debug(Function<SomeObject, String> function)
在记录器实现的内部,我们只根据需要(或以"懒惰"的方式)调用function::apply
(从而计算那个昂贵的String)。
在Java中,它通常被称为"纯函数";,其定义类似:
-
函数的执行没有任何副作用。
-
函数的返回值仅取决于传递给函数的输入参数。
其他任何东西都应该是对象的方法。
传递函数-ality,而不是使用重写(即匿名实例)进行继承。
假设您创建了一个类,但必须提供一点计算。
class C {
protected abstract int f(int x);
}
class Child1of99 extends C {
@Override
protected int f(int x) { return x / 42; }
}
或
new C() {
@Override
protected int f(int x) { return x / 42; }
}
或者你可以做:
class C {
private final IntOperation f;
C(IntOperation f) {
this.f = f;
}
}
new C(x -> x / 42);
就功能接口而言,我相信它使"行为";更敏捷,我的意思是,在功能接口的帮助下,你可以轻松快速地向其他成员提供行为,而传统方法并非如此。因此,基本上,对于实例方法,您的行为会坚持对象,或者静态方法可以提供更好的范围,并且可以在所有类中访问,但它也不是动态的。
考虑以下示例,
class Math {
int sum(int a, int b) {
return a + b;
}
}
现在我的求和方法是固定的,不能更改,现在考虑下面的功能接口示例,
interface Sum {
int sum(int a, int b);
}
现在我可以有不同的行为,
Sum nocheckSum = (a, b) -> a + b;
Sum positiveNumSum = (a, b) -> {
if(a < 0 || b < 0) throw new IllegalArgumentException("Only positive numbers are allowed!");
return a + b;
};
也许这不是最好的例子,但我想你明白我的意思了。
现在,这种机制的好处是,您不需要为不同的行为声明和管理方法,您可以动态创建一个方法并将其用于特定目的。同时,值得一提的是,如果你确信行为必须对所有使用它的人来说都是常见的,那么我不建议强制执行功能接口,但如果它可以而且可能会对除特定方法消费者之外的所有方法消费者都发生变化,那么功能接口肯定会有所帮助。
底线是,方法或功能接口在语言中都有自己的意义,即使我们可以互换使用它们,更好的选择是根据您的业务需求有意识地选择其中一个。
通常,函数式编程(lambdas,函数式接口)提供最佳操作,如转换和处理。相反,OOP编程(使用方法)在必须存储数据(例如内存中的数据)、不时对其进行变异或在组件之间发送消息时效果最好。和往常一样,这取决于情况。所有的概括都有例外。
基本上,Java函数是内置的、无错误的、经过优化的、功能强大的函数,适合您的需求。开发人员使用Java函数中最好的算法来降低时间和空间的复杂性。