145

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

4

10 回答 10

194

如果您需要 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 不是以这种方式构建的,虽然可以实现,但代码很难维护,有时也很难阅读。但是,它作为练习非常有用,有时部分函数在您的代码中占有一席之地。

于 2013-10-29T04:07:17.713 回答
92

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

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

例如,函数

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与具有建设性的功能相同。破坏性函数的好处是,您现在可以处理无限参数,这对于流特别方便,并且您可以让参数保持打开状态。x := 1因此,如果您再次想查看如果and的结果会是什么样子y := 1 , y := 2 , y := 3,您可以说h = g(1)and h(1)是 for y := 1h(2)fory := 2h(3)for的结果y := 3

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

如果你可以放入一个为你工作的函数,像工厂这样的模式会容易得多。

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

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

那么为什么需要BiFunction应该比为什么没有更多的问题TriFunction呢?

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

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

于 2014-02-15T13:07:59.937 回答
20

另一种方法是,添加以下依赖项,

<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;
于 2019-01-21T07:23:06.180 回答
7

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

我来自.NET。在 .NET 中,您有用于 void 函数的 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);
    }
}

内部函数被用来规避另一个小问题。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);
        }

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 中不存在的扩展方法来规避。

于 2016-10-17T17:28:04.430 回答
4

您还可以使用 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
于 2019-05-19T11:26:37.783 回答
3

我在这里找到了 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));
    }
}
于 2018-11-28T20:17:26.350 回答
1

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

于 2020-02-14T08:19:45.740 回答
0

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

可以扩展此逻辑以使函数采用任何所需数量的参数。

于 2021-02-15T17:48:21.823 回答
0

在与 Spring Framework 捆绑reactor.functionReactor Addons 库中,有现成的 Consumer3..Consumer8、Function3..Function8、Predicate3..Predicate8 可供使用。

于 2021-07-29T07:40:52.533 回答
0

选择的答案是最有帮助的,尽管我觉得解释有点复杂。

为简化起见,假设您想要一个添加两个字符串的函数

方法

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

于 2021-03-03T00:18:08.710 回答