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你能定义以下行吗?这里发生了什么 default TriFunction andThen( Function super R, ? extends V> after) Objects.requireNonNull(after); return (A a, B b, C c) -> after.apply(apply(a, b, c)); @MuneebNasir - 它允许你做函数组合:TriFunction&lt;Integer,Integer,Integer,Integer&gt; comp = (x,y,z) -&gt; x + y + z; comp = comp.andThen(s -&gt; s * 2); int result = comp.apply(1, 2, 3); //12 见***.com/questions/19834611/… 在答案中添加了andThen() 用法示例。 不仅currying对Java语言的适配不是很好,而且,如果我错了,请纠正我,但是BiFunctionStream 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 := 1y := 1 , y := 2 , y := 3 的结果会怎样,您可以说 h = g(1)h(1)y := 1 的结果,h(2)y := 2h(3)y := 3 的结果。

所以这里你有一个固定的状态!这是非常动态的,这也是我们大多数时候想要的 lambda。

如果你可以放入一个为你工作的函数,像工厂这样的模式会容易得多。

破坏性的很容易相互结合。如果类型正确,您可以随意组合它们。使用它,您可以轻松定义态射,从而使(具有不可变值的)测试变得更加容易!

你也可以用建设性的组合来做到这一点,但破坏性的组合看起来更好,更像是一个列表或装饰器,而建设性的组合看起来很像一棵树。以及诸如回溯之类的事情 具有建设性的功能并不好。您可以只保存破坏性的部分功能(动态编程),并在“回溯”上使用旧的破坏性功能。这使得代码更小,可读性更好。使用构造函数,您或多或少可以记住所有参数,这可能很多。

那么为什么需要BiFunction 应该比为什么没有TriFunction 更值得质疑?

首先,很多时候你只有几个值(小于 3)并且只需要一个结果,所以根本不需要正常的破坏性函数,建设性的就可以了。还有像单子这样的东西 真的需要一个建设性的功能。但除此之外,没有太多充分的理由说明为什么会有BiFunction。这并不意味着它应该被删除!我为我的Monads而战,直到我死去!

所以如果你有很多参数,你不能将它们组合成一个逻辑容器类,并且如果你需要 功能要具有建设性,请使用方法参考。否则尝试使用新获得的破坏性功能,您可能会发现自己用更少的代码行做了很多事情。

【讨论】:

你回答了我的问题......我想......我不知道java语言设计者是否来自这个思路,但我并不精通函数式编程。谢谢你的解释。 我从未见过constructive破坏性这两个词被用来指代您描述的概念。我认为 curriednon-curried 是更常用的术语。 第一个函数示例在语法上不正确。它应该是 BiFunction 而不是 Function,因为它需要两个输入参数。 IMO BiFunction 的创建是为了方便数据缩减,大多数Streams 终端操作只是数据缩减。一个很好的例子是BinaryOperator&lt;T&gt;,在许多Collectors 中使用。第一个元素与第二个元素一起减少,然后可以与下一个元素一起减少,依此类推。当然,你可以创建一个Function&lt;T, Function&lt;T, T&gt; func = x -> (y -> /*reduction code here*/)。不过实话说?当您可以简单地执行BinaryOperator&lt;T&gt; func = (x, y) -&gt; /*reduction code here*/ 时,所有这一切。另外,这种数据缩减方法在我看来很像你的“破坏性”方法。 这是如何获得这么多支持的?这是一个可怕且令人困惑的答案,因为它基于Function&lt;Integer,Integer&gt; f = (x,y) -&gt; 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&lt;T, R&gt; 可用于嵌套形式来模拟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 8 Function 函数接口

Java 8 Function 函数接口

java.util.function.Function.identity 方法的实际用途是啥?

Java系列教程一起爪哇Java 8——函数式接口

java.util.function 中的 FunctionPredicateConsumer

JDK8的新特性-java.util.function--Function接口