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

Ric*_*gan 98 java lambda java-8

我看到java.util.function.BiFunction,所以我可以这样做:

BiFunction<Integer, Integer, Integer> f = (x, y) -> { return 0; };
Run Code Online (Sandbox Code Playgroud)

如果那还不够好我需要TriFunction怎么办?它不存在!

TriFunction<Integer, Integer, Integer, Integer> f = (x, y, z) -> { return 0; };
Run Code Online (Sandbox Code Playgroud)

我想我应该补充一点,我知道我可以定义自己的TriFunction,我只是想了解不在标准库中包含它的原理.

Ale*_*kka 144

如果您需要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));
    }
}
Run Code Online (Sandbox Code Playgroud)

以下小程序显示了如何使用它.请记住,结果类型被指定为最后一个泛型类型参数.

  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]
    }
  }
Run Code Online (Sandbox Code Playgroud)

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

UPDATE

为了完整性并遵循另一个答案(与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
Run Code Online (Sandbox Code Playgroud)

当然,可以通过其他方式组合功能,例如:

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
Run Code Online (Sandbox Code Playgroud)

虽然curry对于支持lambda之外的函数式编程的任何语言都是很自然的,但Java不是以这种方式构建的,并且虽然可以实现,但代码很难维护,有时甚至可以阅读.但是,它作为练习非常有用,有时部分功能在您的代码中具有合适的位置.

  • 谢谢你的解决方案.是的,肯定有用于BiFunction,TriFunction,......否则人们不会搜索它.它猜测整个lambda现在对于Oracle来说太新了,并且将在以后的Java版本中进行扩展.目前它更像是一个概念证明. (5认同)

小智 77

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

虽然建设性的功能,顾名思义,构建一些东西,一个破坏性的东西会摧毁一些东西,但不会像你现在想象的那样.

例如,功能

Function<Integer,Integer> f = (x,y) -> x + y  
Run Code Online (Sandbox Code Playgroud)

是一个建设性的.因为你需要构建一些东西.在示例中,您构造了元组(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) 
Run Code Online (Sandbox Code Playgroud)

与具有f建设性的功能相同.破坏性函数的好处是,你现在可以处理无限的参数,这对于流特别方便,你可以让参数保持打开状态.所以,如果你想再次看看会的结果是什么样的,如果x := 1y := 1 , y := 2 , y := 3,可以说h = g(1)h(1)是结果为y := 1,h(2)y := 2h(3)y := 3.

所以在这里你有一个固定的状态!这是非常有活力的,而且大多数时候我们想要的是lambda.

像Factory这样的模式更容易,如果你可以放入一个为你工作的功能.

破坏性的很容易相互结合.如果类型合适,您可以根据自己的喜好进行组合.使用它,您可以轻松定义使(使用不可变值)测试更容易的态射!

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

那么为什么需要BiFunction更多的问题而不是为什么没有TriFunction呢?

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

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

  • 我从未见过使用_constructive_和_destructive_这两个术语来指代您描述的概念.我认为_curried_和_non-curried_是更常见的术语. (69认同)
  • 这怎么会得到这么多的赞成?这是一个可怕且令人困惑的答案,因为它基于这样的前提:"Function <Integer,Integer> f =(x,y) - > x + y`是有效的Java,它不是.这应该是一个BiFunction开始! (23认同)
  • 第一个函数示例在语法上不正确.它应该是BiFunction而不是Function,因为它需要两个输入参数. (14认同)
  • IMO`BiFunction`的创建是为了简化数据,大多数`Stream的终端操作只是数据减少.一个很好的例子是`BinaryOperator <T>`,在许多`Collectors`中使用.第一个元素用第二个元素减少,然后用下一个元素减少,依此类推.当然,你可以创建一个`Function <T,Function <T,T>`func = x - >(y - >/*简化代码在这里*/).不过实话说?所有这一切,你只需要在这里做`BinaryOperator <T> func =(x,y) - >/*简化代码*/`.此外,这种数据缩减方法看起来很像你对我的"破坏性"方法. (3认同)
  • 您回答了我的问题...我认为...我不知道Java语言设计者是否来自这种思维方式,但是我对函数式编程并不精通。谢谢你的解释。 (2认同)

Han*_*ans 7

我有几乎相同的问题和部分答案.不确定建设性/解构性的答案是否是语言设计者的想法.我认为有3个以上的N有有效用例.

我来自.NET.在.NET中,你有虚函数的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);
    }
}
Run Code Online (Sandbox Code Playgroud)

内部功能被用来规避另一个小问题.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);
        }
Run Code Online (Sandbox Code Playgroud)

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);
    }
Run Code Online (Sandbox Code Playgroud)

什么是最好的方法?

在上面的例子中,我没有包含andThen()和compose()方法的实现.如果你添加这些,你必须添加16个重载:TriFunc应该有一个带有16个参数的andthen().由于循环依赖性,这会给你一个编译错误.你也不会有函数和BiFunction的这些重载.因此,您还应该使用一个参数定义Func,使用两个参数定义Func.在.NET中,循环依赖性将通过使用Java中不存在的扩展方法来规避.

  • 为什么你需要16个参数的`andThen`?Java中函数的结果是单个值.`andThen`取这个值并用它做点什么.此外,命名也没有问题.类名应该不同,并且位于不同的文件中 - 遵循Java语言开发人员使用Function和BiFunction设置的逻辑.此外,如果参数类型不同,则需要所有这些不同的名称.可以为单一类型创建一个`VargFunction(T,R){R apply(T ... t)...}`. (2认同)

小智 7

或者,添加以下依赖项,

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.9.0</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用Vavr函数,例如以下最多8个参数,

3个参数:

Function3<Integer, Integer, Integer, Integer> f = 
      (a, b, c) -> a + b + c;
Run Code Online (Sandbox Code Playgroud)

5个参数:

Function5<Integer, Integer, Integer, Integer, Integer, Integer> f = 
      (a, b, c, d, e) -> a + b + c + d + e;
Run Code Online (Sandbox Code Playgroud)

  • 答案是好的。这个想法还没有在框架中:GAAAAAAA。 (3认同)
  • 我打算更新我的答案以提及vavr,但您是第一位的,所以我投票了。如果到了需要TriFunction的地步,那么很有可能通过使用`vavr`库来获得更好的收益-它使函数式风格的编程在Java中成为可能。 (2认同)