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

时间:2013-08-23 10:11:39

标签: java lambda java-8

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

10 个答案:

答案 0 :(得分:122)

如果您需要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作为任何方法参数。所以它没有被包括在内。

更新

为了完整性并遵循另一个答案中的破坏性功能解释(与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不是以这种方式构建的,虽然可以实现,但代码很难维护,有时甚至可以阅读。但是,它作为练习非常有用,有时部分功能在您的代码中占有一席之地。

答案 1 :(得分:70)

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

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

例如,函数

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 := 1h(2)y := 2h(3)y := 3

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

如果您可以为您提供适合您的功能,那么像Factory这样的模式就会轻松得多。

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

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

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

首先,很多时候你只有一些值(少于3个)并且只需要一个结果,所以根本不需要正常的破坏性功能,建设性的就可以了。还有像monad这样的东西 真的需要一个建设性的功能。但除此之外,实际上有BiFunction的原因并不是很多。这并不意味着它应该被删除!我为Monads而战直到我死!

因此,如果你有很多参数,你不能把它们组合成一个逻辑容器类,如果你需要的话 函数是建设性的,使用方法引用。否则尝试使用新获得的破坏性函数的能力,你可能会发现自己用很少的代码行做很多事情。

答案 2 :(得分:7)

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

我来自.NET。在.NET中,你有虚函数的Func和Action。谓词和其他一些特殊情况也存在。请参阅:https://msdn.microsoft.com/en-us/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函数类型的名称冲突。

因此,如果你想从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()。由于循环依赖性,这会给你一个编译错误。你也不会有函数和BiFunction的这些重载。因此,您还应该使用一个参数定义Func,使用两个参数定义Func。在.NET中,循环依赖性将通过使用Java中不存在的扩展方法来规避。

答案 3 :(得分:5)

替代方法是,添加以下依赖项,

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

答案 4 :(得分: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));
    }
}

答案 5 :(得分:1)

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

答案 6 :(得分:0)

您还可以使用3个参数创建自己的函数

{{1}}

答案 7 :(得分:0)

可以在嵌套形式中使用简单的 Function<T, R> 来模拟 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

可以扩展此逻辑,使函数采用任意数量的参数。

答案 8 :(得分:0)

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,这是一个不同的话题。

答案 9 :(得分:0)

与 Spring Framework 捆绑在一起的 reactor.function Addons 库的 Reactor 包中有准备使用 Consumer3..Consumer8、Function3..Function8、Predicate3..Predicate8。