我看到java.util.function.BiFunction,所以我可以这样做:
BiFunction<Integer, Integer, Integer> f = (x, y) -> { return 0; };
如果那还不够好我需要TriFunction怎么办?它不存在!
TriFunction<Integer, Integer, Integer, Integer> f = (x, y, z) -> { return 0; };
我想我应该补充一点,我知道我可以定义自己的TriFunction,我只是想了解不将它包含在标准库中的理由。
答案 0 :(得分:122)
如果您需要TriFunction,请执行以下操作:
@FunctionalInterface
interface TriFunction<A,B,C,R> {
R apply(A a, B b, C c);
default <V> TriFunction<A, B, C, V> andThen(
Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (A a, B b, C c) -> after.apply(apply(a, b, c));
}
}
以下小程序显示了如何使用它。请记住,结果类型被指定为最后一个泛型类型参数。
public class Main {
public static void main(String[] args) {
BiFunction<Integer, Long, String> bi = (x,y) -> ""+x+","+y;
TriFunction<Boolean, Integer, Long, String> tri = (x,y,z) -> ""+x+","+y+","+z;
System.out.println(bi.apply(1, 2L)); //1,2
System.out.println(tri.apply(false, 1, 2L)); //false,1,2
tri = tri.andThen(s -> "["+s+"]");
System.out.println(tri.apply(true,2,3L)); //[true,2,3]
}
}
我想如果java.util.*
或java.lang.*
中的TriFunction有实际用途,那么它就会被定义。我永远不会超过22个参数,但是;-)我的意思是,所有允许流集合的新代码都不需要TriFunction作为任何方法参数。所以它没有被包括在内。
更新
为了完整性并遵循另一个答案中的破坏性功能解释(与currying相关),以下是如何在没有附加接口的情况下模拟TriFunction:
Function<Integer, Function<Integer, UnaryOperator<Integer>>> tri1 = a -> b -> c -> a + b + c;
System.out.println(tri1.apply(1).apply(2).apply(3)); //prints 6
当然,可以通过其他方式组合功能,例如:
BiFunction<Integer, Integer, UnaryOperator<Integer>> tri2 = (a, b) -> c -> a + b + c;
System.out.println(tri2.apply(1, 2).apply(3)); //prints 6
//partial function can be, of course, extracted this way
UnaryOperator partial = tri2.apply(1,2); //this is partial, eq to c -> 1 + 2 + c;
System.out.println(partial.apply(4)); //prints 7
System.out.println(partial.apply(5)); //prints 8
虽然对于支持lambda之外的函数式编程的语言来说,currying是很自然的,但Java不是以这种方式构建的,虽然可以实现,但代码很难维护,有时甚至可以阅读。但是,它作为练习非常有用,有时部分功能在您的代码中占有一席之地。
答案 1 :(得分:70)
据我所知,只有两种功能,破坏性和建设性。
虽然建设性的功能,顾名思义,构建一些东西,一种破坏性的功能会破坏某些东西,但不会像你现在想象的那样。
例如,函数
Function<Integer,Integer> f = (x,y) -> x + y
是一个建设性的。
因为你需要构建一些东西。在示例中
你构造了元组(x,y)。建设性功能有问题,
无法处理无限参数。但最糟糕的是,你
不能只是打开一个论点。你不能只说“好吧,让x:= 1”然后尝试一下
你可能想尝试一下。每次整个元组都需要构造
x := 1
。所以如果你想看看y := 1, y := 2, y := 3
你的函数返回什么
必须写f(1,1) , f(1,2) , f(1,3)
。
在Java 8中,应该使用方法引用来处理(大多数情况下)构造函数,因为使用构造型lambda函数没有多大优势。它们有点像静态方法。 你可以使用它们,但它们没有真正的状态。
另一种类型是破坏性的,它需要一些东西并根据需要将其拆除。 例如,破坏性功能
Function<Integer, Function<Integer, Integer>> g = x -> (y -> x + y)
与具有建设性的函数f
相同。破坏性功能的好处是,你
现在可以处理无限的参数,这对于流来说特别方便,你可以让参数保持打开状态。
因此,如果您想再次查看x := 1
和y := 1 , y := 2 , y := 3
的结果,您可以说h = g(1)
和
h(1)
的结果为y := 1
,h(2)
为y := 2
,h(3)
为y := 3
。
所以在这里你有一个固定的状态!这是非常有活力的,而且大多数时候我们想要的是lambda。
如果您可以为您提供适合您的功能,那么像Factory这样的模式就会轻松得多。
破坏性的很容易相互结合。如果类型合适,您可以根据自己的喜好进行组合。使用它,您可以轻松定义使(使用不可变值)测试更容易的态射!
你也可以用建设性的方式做到这一点,但破坏性的构图看起来更好,更像是一个列表或装饰者,而建设性的构图看起来很像一棵树。还有像回溯一样的东西 具有建设性的功能并不好。你可以只保存破坏性的部分功能(动态编程),而在“回溯”中只使用旧的破坏性功能。这使得代码更小,更易读。使用建设性功能,您或多或少地记住所有参数,这可能很多。
那为什么BiFunction
需要更多的问题而不是为什么没有TriFunction
?
首先,很多时候你只有一些值(少于3个)并且只需要一个结果,所以根本不需要正常的破坏性功能,建设性的就可以了。还有像monad这样的东西
真的需要一个建设性的功能。但除此之外,实际上有BiFunction
的原因并不是很多。这并不意味着它应该被删除!我为Monads而战直到我死!
因此,如果你有很多参数,你不能把它们组合成一个逻辑容器类,如果你需要的话 函数是建设性的,使用方法引用。否则尝试使用新获得的破坏性函数的能力,你可能会发现自己用很少的代码行做很多事情。
答案 2 :(得分:7)
我有几乎相同的问题和部分答案。不确定建设性/解构性的答案是否是语言设计者的想法。 我认为有3个以上的N有有效用例。
我来自.NET。在.NET中,你有虚函数的Func和Action。谓词和其他一些特殊情况也存在。请参阅:https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx
我想知道语言设计师选择功能的原因是什么, 双功能并没有继续直到DecaExiFunction?
第二部分的答案是类型擦除。编译后,Func和Func之间没有区别。 因此,以下内容无法编译:
package eu.hanskruse.trackhacks.joepie;
public class Functions{
@FunctionalInterface
public interface Func<T1,T2,T3,R>{
public R apply(T1 t1,T2 t2,T3 t3);
}
@FunctionalInterface
public interface Func<T1,T2,T3,T4,R>{
public R apply(T1 t1,T2 t2,T3 t3, T4 t4);
}
}
内部功能被用来规避另一个小问题。 Eclipse坚持在同一目录中将两个类都放在名为Function的文件中......不确定这是否是一个编译器问题。但我无法改变Eclipse中的错误。
Func用于防止与java函数类型的名称冲突。
因此,如果你想从3到16的参数中添加Func,你可以做两件事。
第二种方式的例子:
package eu.hanskruse.trackhacks.joepie.functions.tri;
@FunctionalInterface
public interface Func<T1,T2,T3,R>{
public R apply(T1 t1,T2 t2,T3 t3);
}
和
package eu.trackhacks.joepie.functions.tessera;
@FunctionalInterface
public interface Func<T1,T2,T3,T4,R>{
public R apply(T1 t1,T2 t2,T3 t3, T4 t4);
}
最好的方法是什么?
在上面的例子中,我没有包含andThen()和compose()方法的实现。如果你添加这些,你必须添加16个重载:TriFunc应该有一个带有16个参数的andthen()。由于循环依赖性,这会给你一个编译错误。你也不会有函数和BiFunction的这些重载。因此,您还应该使用一个参数定义Func,使用两个参数定义Func。在.NET中,循环依赖性将通过使用Java中不存在的扩展方法来规避。
答案 3 :(得分:5)
替代方法是,添加以下依赖项,
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.9.0</version>
</dependency>
现在,您可以使用Vavr函数,例如下面最多8个参数,
3个参数:
Function3<Integer, Integer, Integer, Integer> f =
(a, b, c) -> a + b + c;
5个参数:
Function5<Integer, Integer, Integer, Integer, Integer, Integer> f =
(a, b, c, d, e) -> a + b + c + d + e;
答案 4 :(得分:2)
我在这里找到了BiFunction的源代码:
我对其进行了修改以创建TriFunction。与BiFunction一样,它使用andThen()而不是compose(),因此对于某些需要compose()的应用程序,它可能不合适。对于正常的对象应该很好。关于andThen()和compose()的一篇不错的文章可以在这里找到:
http://www.deadcoderising.com/2015-09-07-java-8-functional-composition-using-compose-and-andthen/
import java.util.Objects;
import java.util.function.Function;
/**
* Represents a function that accepts two arguments and produces a result.
* This is the three-arity specialization of {@link Function}.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #apply(Object, Object)}.
*
* @param <S> the type of the first argument to the function
* @param <T> the type of the second argument to the function
* @param <U> the type of the third argument to the function
* @param <R> the type of the result of the function
*
* @see Function
* @since 1.8
*/
@FunctionalInterface
public interface TriFunction<S, T, U, R> {
/**
* Applies this function to the given arguments.
*
* @param s the first function argument
* @param t the second function argument
* @param u the third function argument
* @return the function result
*/
R apply(S s, T t, U u);
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*/
default <V> TriFunction<S, T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (S s, T t, U u) -> after.apply(apply(s, t, u));
}
}
答案 5 :(得分:1)
您不能总是停在TriFunction。有时,您可能需要将n个参数传递给函数。然后,支持团队将必须创建一个QuadFunction来修复您的代码。 长期的解决方案是使用额外的参数创建一个Object,然后使用现成的Function或BiFunction。
答案 6 :(得分:0)
您还可以使用3个参数创建自己的函数
{{1}}
答案 7 :(得分:0)
可以在嵌套形式中使用简单的 Function<T, R>
来模拟 TriFunction
下面是一个简单的例子-
final Function<Integer, Function<Integer, Function<Integer, Double>>> function = num1 -> {
System.out.println("Taking first parameter");
return num2 -> {
System.out.println("Taking second parameter");
return num3 -> {
System.out.println("Taking third parameter");
return (double)(num1 + num2 + num3);
};
};
};
final Double result = function.apply(2).apply(3).apply(4);
System.out.println("Result -> " + result);
输出 -
Taking first parameter
Taking second parameter
Taking third parameter
Result -> 9.0
可以扩展此逻辑,使函数采用任意数量的参数。
答案 8 :(得分:0)
selected answer 是最有用的,虽然我觉得解释有点复杂。
为了简化,假设您想要一个将两个字符串相加的函数
方法
String add(String s, String t) {
return s + t;
}
会有一个具有相同行为的函数:
Function<String,Function<String,String>> add = s -> t -> s + t;
并调用它:
var result = add.apply("hello").apply(" world");
这是否是惯用的 Java,这是一个不同的话题。
答案 9 :(得分:0)
与 Spring Framework 捆绑在一起的 reactor.function
Addons 库的 Reactor 包中有准备使用 Consumer3..Consumer8、Function3..Function8、Predicate3..Predicate8。