Java 8:java.util.function中的TriFunction(和亲属)在哪里?还是有什么选择?


113

我看到了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
使用双功能接口,您可以轻松定义N函数类,如果将三功能定义为单独的接口,则第一个SB将询问为什么不是四功能,其次,您需要复制所有以双功能为参数的方法
user902383 2013年

6
这样的API的收益递减。(就我个人而言,我认为JDK8早已通过了它,但这甚至超出了这个范围。)
Louis Wasserman

我相信,这样做的理由是说Function和BiFunction是使用对象和本机类型完全实现的。包括具有所有各种变体的TriFunctions会使类和方法的JRE崩溃。
托尔比约恩Ravn的安德森

1
简短的答案。在Java中,如果看不到它,则可以构建自己的(当然,请参见Alex P答案)。旁注,在C#中,dotnet的实现者为您提供了预定义的变量(最多16个参数),但没有前缀名称(此处为“ Bi”):请参阅 docs.microsoft.com/zh-cn/dotnet/api/… 只是一个简单的“功能”。所以这是我比Java更喜欢dotnet的地方之一。请不要将此评论部分变成一场激烈的战争。并将注释仅限于BiFunction。
granadaCoder

Answers:


81

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

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

例如功能

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建设性的功能相同。破坏性函数的好处是,您现在可以处理无限量的参数,这对于流来说特别方便,并且您可以只打开参数。因此,如果您再次想查看if x := 1和if的结果y := 1 , y := 2 , y := 3,您可以说h = g(1)and h(1)是for y := 1h(2)for y := 2h(3)for 的结果y := 3

所以这里您有一个固定的状态!这是非常动态的,并且在大多数情况下是我们从lambda中想要的。

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

破坏性元素很容易彼此结合。如果类型正确,则可以根据需要进行组合。使用它,您可以轻松定义使(具有不变的值)测试变得容易得多的态射!

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

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

首先,很多时候您只有几个值(小于3)并且只需要一个结果,因此根本不需要常规的破坏性函数,那么建设性的函数就可以了。诸如monad之类的东西确实需要构造功能。但是除此之外,实际上并没有很多充分的理由说明为什么会有一个BiFunction。这并不意味着应该将其删除!我为我的Monad战斗,直到我死!

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


2
您回答了我的问题...我认为...我不知道Java语言设计师是否来自这种思维方式,但是我对函数式编程并不精通。谢谢你的解释。
理查德·菲根

79
我从未见过用建设性破坏性术语来指代您所描述的概念。我认为咖喱非咖喱是更常用的术语。
Feuermurmel

17
第一个函数示例在语法上不正确。它应该是BiFunction而不是Function,因为它接受两个输入参数。
annouk '16

3
BiFunction创建IMO 是为了简化数据还原,大多数Stream终端操作只是数据缩减。一个很好的例子是BinaryOperator<T>,在很多情况下都使用过Collectors。第一个元素与第二个元素一起减少,然后可以与下一个元素一起减少,依此类推。当然,您可以创建一个Function<T, Function<T, T>func = x->(y-> / *此处的归约代码* /)。不过实话说?所有这些,只要您可以做到BinaryOperator<T> func = (x, y) -> /*reduction code here*/。另外,这种减少数据的方法似乎很像您对我的“破坏性”方法。
FBB

32
这是怎么得到这么多投票的?这是一个可怕而令人困惑的答案,因为它基于Function<Integer,Integer> f = (x,y) -> x + y有效Java 的前提,而实际上并非如此。首先应该是BiFunction!
wvdz

162

如果您需要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作为任何方法参数。因此不包括在内。

更新

为了完整起见,并在另一个答案(与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

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

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不是以这种方式构建的,尽管可以实现,但是代码却难以维护,有时甚至难以阅读。但是,它作为练习非常有帮助,有时部分函数在您的代码中应有的地位。


6
感谢您的解决方案。是的,肯定有BiFunction,TriFunction等的用途……否则人们将不会搜索它。它猜测整个lambda事情对Oracle来说现在还太陌生,并将在以后的Java版本中进行扩展。目前,它更是一种概念证明。
Stefan Endrullis 2014年

您好,@ Alex您可以定义下一行。默认情况<V> TriFunction <A,B,C,V> andThen(Function <?super R,?扩展V>之后){Objects.requireNonNull(after); return(A a,B b,C c)-> after.apply(apply(a,b,c))); }
Muneeb Nasir 2015年

@MuneebNasir -它可以让你做函数组合:TriFunction<Integer,Integer,Integer,Integer> comp = (x,y,z) -> x + y + z; comp = comp.andThen(s -> s * 2); int result = comp.apply(1, 2, 3); //12stackoverflow.com/questions/19834611/...
亚历Pakka

andThen()用法中添加了用法示例。
亚历克斯·帕卡

currying不仅不能很好地适应Java语言,而且如果我错了也可以纠正我,但是BiFunctionStreamAPI中使用它来执行数据约简,这对我来说很像currying的方法:您最多可以使用两次参数,您可以处理任意数量的元素,一次减少一个(请参阅我对已接受答案的评论,很高兴知道我是否以这种方式看错了)。
FBB

13

或者,添加以下依赖项,

<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;

2
我准备更新我的答案以提及vavr,但您是第一位的,所以我投票了。如果到了需要TriFunction的地步,那么很有可能通过使用vavr库来使自己更好—它使函数式风格的编程在Java中变得可以承受。
亚历克斯·帕卡

7

我有几乎相同的问题和部分答案。不确定建设性/解构性答案是否是语言设计师所想到的。我认为拥有3个或更多(最多N个)具有有效的用例。

我来自.NET。在.NET中,您可以使用Func和Action来实现void函数。谓词和其他一些特殊情况也存在。请参阅:https : //msdn.microsoft.com/zh-cn/library/bb534960(v=vs.110).aspx

我想知道为什么语言设计者选择功能,双功能并且直到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类型冲突。

因此,如果要将Func从3增至16,则可以做两件事。

  • 制作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中不存在的扩展方法来避免循环依赖。


2
为什么需要andThen16个参数?Java函数的结果是单个值。andThen接受此值并对其进行处理。另外,命名也没有问题。类名称应该不同,并且在不同的文件中具有相同的名称-遵循Java语言开发人员使用Function和BiFunction设置的逻辑。另外,如果参数类型不同,则需要所有这些不同的名称。可以VargFunction(T, R) { R apply(T.. t) ... }为单个类型创建一个。
亚历克斯·帕卡

2

我在这里找到了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));
    }
}

2

您还可以使用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

0

您不能总是在TriFunction停留。有时,您可能需要将n个参数传递给函数。然后,支持团队将必须创建一个QuadFunction来修复您的代码。长期解决方案是使用额外的参数创建一个对象,然后使用现成的函数或BiFunction。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.