Java 8:TriFunction(和kin)在java.util.function中的位置在哪里?或者有什么替代方案?

2022-08-31 07:28:55

我看到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,我只是试图理解不将其包含在标准库中的理由。


答案 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]
    }
  }

我猜如果TriFunction有实际用途,或者它本来会被定义。不过,我永远不会超过22个论点;-)我的意思是,所有允许流式传输集合的新代码都不需要TriFunction作为任何方法参数。所以它不包括在内。java.util.*java.lang.*

更新

为了完整并遵循另一个答案(与咖喱相关的)中的破坏性函数解释,以下是如何在没有附加接口的情况下模拟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 并不是以这种方式构建的,虽然可以实现,但代码很难维护,有时甚至难以阅读。但是,它作为练习非常有用,有时部分函数在代码中具有应有的位置。


答案 2

据我所知,只有两种功能,破坏性和建设性。

虽然建设性功能,顾名思义,构建了某种东西,但破坏性的功能破坏了某些东西,但不是你现在可能认为的方式。

例如,函数

Function<Integer,Integer> f = (x,y) -> x + y  

是一个建设性的。因为你需要构建一些东西。在此示例中,您构造了元组 (x,y)。建构函数有一个问题,即无法处理无限的参数。但最糟糕的是,你不能只是让争论不休。你不能只是说“好吧,让x := 1”,然后尝试你可能想尝试的每一个y。每次使用 构造整个元组时,都必须使用 .所以如果你想看看函数返回什么,你必须写.x := 1y := 1, y := 2, y := 3f(1,1) , f(1,2) , f(1,3)

在Java 8中,建设性函数应该(大多数时候)通过使用方法引用来处理,因为使用建设性的lambda函数没有太多优势。它们有点像静态方法。您可以使用它们,但它们没有真实状态。

另一种类型是破坏性的,它拿走一些东西,并根据需要将其拆除。例如,破坏性功能

Function<Integer, Function<Integer, Integer>> g = x -> (y -> x + y) 

与建设性的功能相同。破坏性函数的好处是,您现在可以处理无限的参数,这对于流特别方便,并且您可以只保留参数。因此,如果您再次想查看如果 和 的结果会是什么样子,则可以说 和 是 、 for 和 的结果。fx := 1y := 1 , y := 2 , y := 3h = g(1)h(1)y := 1h(2)y := 2h(3)y := 3

所以在这里你有一个固定的状态!这是非常动态的,大多数时候,我们想要从lambda中得到什么。

像 Factory 这样的模式要容易得多,如果你能把一个函数放在一个为你工作的功能中。

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

你也可以用建设性的构图来做到这一点,但是破坏性的构图看起来更好,更像是一个列表或装饰器,而建设性的构图看起来很像一棵树。像用建设性函数回溯这样的事情并不好。您可以只保存破坏性函数(动态规划)的部分函数,而在“回溯”上只需使用旧的破坏性函数即可。这使得代码更小,可读性更好。对于建设性函数,您或多或少需要记住所有参数,这可能很多。

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

首先,很多时候你只有几个值(小于3),只需要一个结果,所以根本不需要一个正常的破坏性函数,一个建设性的函数会做得很好。还有一些东西,比如monads,确实需要一个建设性的功能。但除此之外,实际上并没有很多很好的理由来解释为什么有一个。这并不意味着它应该被删除!我为我的Monads而战,直到我死去!BiFunction

因此,如果您有很多参数,无法将其组合成逻辑容器类,并且如果您需要函数具有建设性,请使用方法引用。否则,尝试使用新获得的破坏性函数的能力,您可能会发现自己用更少的代码行做了很多事情。


推荐