Java8: 在 Java.util.function 中 TriFunction (和 kin)在哪里? 或者有什么替代方案?

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

BiFunction<Integer, Integer, Integer> f = (x, y) -> { return 0; };

如果这还不够好,我需要三角函数怎么办? 它根本不存在!

TriFunction<Integer, Integer, Integer, Integer> f = (x, y, z) -> { return 0; };

我想我应该补充一点,我知道我可以定义我自己的 TriFunction,我只是试图理解不把它包含在标准库中背后的基本原理。

122115 次浏览

如果你需要 TriFunctional,只需要这样做:

@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 作为任何方法参数。所以不包括在内。

更新

为了完整起见,并遵循另一个答案中的破坏性函数解释(与局部套用相关) ,下面是如何在没有额外接口的情况下模拟 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

对于任何支持 lambdas 之外的函数式编程的语言来说,局部套用都是很自然的事情,但是 Java 并不是这样构建的,而且尽管可以实现,但是代码很难维护,有时候很难阅读。但是,作为练习,它非常有帮助,有时部分函数在代码中有合适的位置。

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

虽然建设性功能,顾名思义,建设性的东西,一个破坏性的摧毁一些东西,但不是以你现在可能认为的方式。

例如,函数

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

有建设性型的。 当你需要构造一些东西的时候,在例子中 你构造了元组 (x,y)。构造函数有问题, 无法处理无穷无尽的争论。但最糟糕的是,你 你不能只是说“好吧,让 x: = 1”然后尝试 每次你可能想尝试。你必须构造整个元组 所以如果你想知道函数返回什么 必须写 f(1,1) , f(1,2) , f(1,3)

在 Java8中,构造性函数(大多数情况下)应该通过使用方法引用来处理,因为使用构造性 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 := 1的结果,h(2)y := 2的结果,h(3)y := 3的结果。

所以这里你有一个固定的状态!这是相当动态的,这是大多数时候,我们想从一个 lambda。

像 Factory 这样的模式要容易得多,如果你只需要放入一个函数来为你工作。

破坏性的物质很容易混合在一起。如果类型是正确的,你可以随心所欲地创作它们。使用它,您可以很容易地定义使(具有不可变值)测试更容易的态变量!

你也可以用一个有建设性的组合来做到这一点,但是有破坏性的组合看起来更好,更像一个列表或者一个装饰品,而有建设性的组合看起来更像一棵树。还有回溯之类的 有构造性的函数就是不好。你可以只保存破坏性函数的部分函数(动态编程) ,在“回溯”中只使用旧的破坏性函数。这使得代码更小,可读性更好。有了构造性函数,你或多或少可以记住所有的论点,这可能是很多。

那么,为什么对 BiFunction的需求比为什么没有 TriFunction的问题更多呢?

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

因此,如果有很多参数,不能将其组合成逻辑容器类,并且需要 函数是构造性的,使用方法参考。否则,尝试使用新获得的破坏性函数的能力,您可能会发现自己用更少的代码行做了很多事情。

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

我来自。NET.进去。NET 中,对于 void 函数有 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 函数类型的名称冲突。

所以如果你想把 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 中不存在的扩展方法来规避。

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

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

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

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

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

简单的 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

这个逻辑可以扩展,使函数接受任意数量的参数。

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

为了简化,假设您需要一个函数来添加两个字符串

方法

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 是另一个话题。

已经准备好使用 Consumer3了。.消费者8,功能3。.函数8,谓词3。.与 Spring 框架捆绑在一起的 反应堆 Addons 库的 reactor.function包中的 PRECTate8。

我认为处理这个问题最直接的方法是简单地创建一个类来定义所有要处理的参数,并使用一个简单的 Function 或 Consumer。有没有一个真正的原因,你会想要 TriFunction 而不是,例如:

public class MyDataObject {
public String arg0;
public int arg1;
public Date argWhatever;
}


//...


Function<Integer, MyDataObject> fn = (data) -> {
// do stuff with data.arg0, data.arg1, etc
return 0; // or whatever
};

如果它变得比两个参数更复杂,那么这是一种更简洁的方法。