Java 8:java.util.function 中的 TriFunction(和 kin)在哪里?或者有什么选择?

新手上路,请多包涵

我看到了 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,我只是想了解不将其包含在标准库中的基本原理。

原文由 Richard Finegan 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 837
2 个回答

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

顾名思义,建设性功能会构造某些东西,而破坏性功能会破坏某些东西,但不是您现在所想的那样。

例如,函数

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

建设性 的。因为你需要构建一些东西。在示例中,您构造了元组 (x,y) 。构造函数存在无法处理无限参数的问题。但最糟糕的是,你不能就此置之不理。你不能只说“好吧,让 x := 1”然后尝试每一个你可能想尝试的 y。每次都必须使用 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 相同。破坏性函数的好处是,你现在可以处理无限参数,这对流来说特别方便,而且你可以让参数保持打开状态。 So if you again want to see what would the result be like if x := 1 and y := 1 , y := 2 , y := 3 , you can say h = g(1) and h(1) is the result for y := 1 , h(2) for y := 2 and h(3) for y := 3 .

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

如果您可以放入一个为您完成工作的函数,那么像 Factory 这样的模式会容易得多。

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

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

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

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

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

原文由 user3003859 发布,翻译遵循 CC BY-SA 3.0 许可协议

如果您需要 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 在 java.util.*java.lang.* 中有实际用途,它就会被定义。不过,我永远不会超过 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 并不是以这种方式构建的,虽然可以实现,但代码难以维护,有时甚至难以阅读。但是,它作为练习非常有帮助,有时偏函数在您的代码中有其应有的位置。

原文由 Alex Pakka 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏