Java 8:java.util.function 中的 TriFunction(和 kin)在哪里?或者有啥选择?
Posted
技术标签:
【中文标题】Java 8:java.util.function 中的 TriFunction(和 kin)在哪里?或者有啥选择?【英文标题】:Java 8: Where is TriFunction (and kin) in java.util.function? Or what is the alternative?Java 8:java.util.function 中的 TriFunction(和 kin)在哪里?或者有什么选择? 【发布时间】:2013-08-26 08:24:39 【问题描述】:我看到了 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,我只是想了解不将其包含在标准库中的原因。
【问题讨论】:
带有bifunction接口,你可以轻松定义N-function类,如果你定义trifunction为单独的接口,首先某人会问为什么不是quadofunction,其次,你需要复制所有需要Bifunction的方法作为参数 这样的 API 存在收益递减点。 (我个人认为 JDK8 不久前就通过了,但这还不止于此。) 我相信基本原理是说 Function 和 BiFunction 完全使用对象和本机类型实现。包含具有所有各种变体的 TriFunction 会用类和方法破坏 JRE。 简短回答。在 Java 中,如果您没有看到它,您可以自己构建(当然,请参见 Alex P 的答案)。旁注,在 C# 中,dotnet 的实现者为您提供了预装的(最多 16 个参数),但没有前缀名称(此处为“Bi”):参见docs.microsoft.com/en-us/dotnet/api/… 只是一个简单的“Func”。所以这是我更喜欢 dotnet 而不是 java 的地方之一。请不要将此评论部分变成一场战争。并将 cmets 限制为 BiFunction。 就像其他任何东西一样。如果 API 未提供,请自行编写代码。 【参考方案1】:如果您需要 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 作为任何方法参数。所以不包括在内。
更新
为了完整起见并遵循另一个答案(与柯里化相关)中的破坏性函数解释,以下是如何在没有额外接口的情况下模拟 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 之外的函数式编程的语言来说都是很自然的,但 Java 并不是以这种方式构建的,虽然可以实现,但代码很难维护,有时也很难阅读。但是,它作为练习非常有用,有时部分函数在您的代码中占有一席之地。
【讨论】:
感谢您的解决方案。是的,BiFunction、TriFunction 肯定有用……否则人们不会搜索它。它猜测整个 lambda 东西现在对 Oracle 来说太新了,并且将在以后的 Java 版本中进行扩展。目前,它更像是一个概念证明。 你好@Alex你能定义以下行吗?这里发生了什么 defaultTriFunction<Integer,Integer,Integer,Integer> comp = (x,y,z) -> x + y + z; comp = comp.andThen(s -> s * 2); int result = comp.apply(1, 2, 3); //12
见***.com/questions/19834611/…
在答案中添加了andThen()
用法示例。
不仅currying对Java语言的适配不是很好,而且,如果我错了,请纠正我,但是BiFunction
在Stream
API中用于进行数据缩减,看起来很像我的柯里化方法:您永远不会接受两个以上的参数,并且您可以处理任意数量的元素,一次减少一个(请参阅我对已接受答案的评论,我很高兴知道我是否那样看是错误的)。【参考方案2】:
据我所知,功能只有两种,破坏性和建设性。
正如名字所暗示的那样,建设性功能会构建一些东西,而破坏性功能会破坏一些东西,但不是你现在想的那样。
例如函数
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。
如果你可以放入一个为你工作的函数,像工厂这样的模式会容易得多。
破坏性的很容易相互结合。如果类型正确,您可以随意组合它们。使用它,您可以轻松定义态射,从而使(具有不可变值的)测试变得更加容易!
你也可以用建设性的组合来做到这一点,但破坏性的组合看起来更好,更像是一个列表或装饰器,而建设性的组合看起来很像一棵树。以及诸如回溯之类的事情 具有建设性的功能并不好。您可以只保存破坏性的部分功能(动态编程),并在“回溯”上使用旧的破坏性功能。这使得代码更小,可读性更好。使用构造函数,您或多或少可以记住所有参数,这可能很多。
那么为什么需要BiFunction
应该比为什么没有TriFunction
更值得质疑?
首先,很多时候你只有几个值(小于 3)并且只需要一个结果,所以根本不需要正常的破坏性函数,建设性的就可以了。还有像单子这样的东西
真的需要一个建设性的功能。但除此之外,没有太多充分的理由说明为什么会有BiFunction
。这并不意味着它应该被删除!我为我的Monads而战,直到我死去!
所以如果你有很多参数,你不能将它们组合成一个逻辑容器类,并且如果你需要 功能要具有建设性,请使用方法参考。否则尝试使用新获得的破坏性功能,您可能会发现自己用更少的代码行做了很多事情。
【讨论】:
你回答了我的问题......我想......我不知道java语言设计者是否来自这个思路,但我并不精通函数式编程。谢谢你的解释。 我从未见过constructive和破坏性这两个词被用来指代您描述的概念。我认为 curried 和 non-curried 是更常用的术语。 第一个函数示例在语法上不正确。它应该是 BiFunction 而不是 Function,因为它需要两个输入参数。 IMOBiFunction
的创建是为了方便数据缩减,大多数Stream
s 终端操作只是数据缩减。一个很好的例子是BinaryOperator<T>
,在许多Collectors
中使用。第一个元素与第二个元素一起减少,然后可以与下一个元素一起减少,依此类推。当然,你可以创建一个Function<T, Function<T, T>
func = x -> (y -> /*reduction code here*/)。不过实话说?当您可以简单地执行BinaryOperator<T> func = (x, y) -> /*reduction code here*/
时,所有这一切。另外,这种数据缩减方法在我看来很像你的“破坏性”方法。
这是如何获得这么多支持的?这是一个可怕且令人困惑的答案,因为它基于Function<Integer,Integer> f = (x,y) -> x + y
是有效的Java 的前提,而事实并非如此。这应该是一个 BiFunction 开始!【参考方案3】:
另一种方法是,添加以下依赖项,
<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;
【讨论】:
我正要更新我的答案以提及 vavr,但你是第一个,所以我投了赞成票。如果你到了需要 TriFunction 的地步,那么使用vavr
库很有可能会让你变得更好 - 它使 Java 中的函数式编程尽可能容易接受。
答案很好。框架中还没有这个想法:GAAAAAAA。【参考方案4】:
我有几乎相同的问题和部分答案。不确定建设性/解构性答案是否是语言设计者的想法。 我认为拥有 3 个或更多 N 具有有效的用例。
我来自 .NET。在 .NET 中,您有用于 void 函数的 Func 和 Action。谓词和其他一些特殊情况也存在。见:https://msdn.microsoft.com/en-us/library/bb534960(v=vs.110).aspx
我想知道语言设计者选择 Function 的原因是什么, Bifunction 直到 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 Function 类型发生名称冲突。
因此,如果您想添加从 3 到 16 个参数的 Func,您可以做两件事。
制作 TriFunc、TesseraFunc、PendeFunc、...DecaExiFunc 等 (我应该使用希腊语还是拉丁语?) 使用包名或类使名称不同。第二种方式的例子:
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()。由于循环依赖,这会给你一个编译错误。此外,您不会对 Function 和 BiFunction 有这些重载。因此,您还应该用一个参数定义 Func,用两个参数定义 Func。在 .NET 中,循环依赖可以通过使用 Java 中不存在的扩展方法来规避。
【讨论】:
为什么你需要有 16 个参数的andThen
? Java 中函数的结果是单个值。 andThen
采用此值并对其进行处理。此外,命名没有问题。类名应该不同,并且在同名的不同文件中 - 遵循 Java 语言开发人员使用 Function 和 BiFunction 设置的逻辑。此外,如果参数类型不同,则需要所有这些不同的名称。可以为单一类型创建一个VargFunction(T, R) R apply(T.. t) ...
。【参考方案5】:
您也可以使用 3 个参数创建自己的函数
@FunctionalInterface
public interface MiddleInterface<F,T,V>
boolean isBetween(F from, T to, V middleValue);
MiddleInterface<Integer, Integer, Integer> middleInterface =
(x,y,z) -> x>=y && y<=z; // true
【讨论】:
【参考方案6】:我在这里找到了 BiFunction 的源代码:
https://github.com/JetBrains/jdk8u_jdk/blob/master/src/share/classes/java/util/function/BiFunction.java
我对其进行了修改以创建 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));
【讨论】:
【参考方案7】:您不能总是停留在 TriFunction。有时,您可能需要将 n 个参数传递给您的函数。然后支持团队将不得不创建一个 QuadFunction 来修复您的代码。 长期的解决方案是创建一个带有额外参数的对象,然后使用现成的函数或 BiFunction。
【讨论】:
【参考方案8】:简单的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
此逻辑可以扩展为使函数采用任意数量的参数。
【讨论】:
【参考方案9】: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 是另一个话题。
【讨论】:
【参考方案10】:与Spring Framework捆绑在一起的Reactor Addons库的reactor.function
包中有现成的Consumer3..Consumer8、Function3..Function8、Predicate3..Predicate8。
【讨论】:
以上是关于Java 8:java.util.function 中的 TriFunction(和 kin)在哪里?或者有啥选择?的主要内容,如果未能解决你的问题,请参考以下文章
java.util.function.Function.identity 方法的实际用途是啥?