【问题标题】:Identity for BinaryOperatorBinaryOperator 的身份
【发布时间】:2018-09-12 13:12:10
【问题描述】:

我在 Java8 中的 UnaryOperator 接口中看到了一段代码,它对参数不执行任何操作并返回相同的值。

static <T> UnaryOperator<T> identity() {
    return t -> t;
}

BinaryOperator 有什么东西可以接受两个相同类型的参数并返回一个值

static <T> BinaryOperator<T> identity() {
    return (t,t) -> t;
}

为什么我问这个问题是为了满足以下要求,

List<String> list = Arrays.asList("Abcd","Abcd");
Map<String,Integer> map = list.stream().collect(Collectors.toMap(str->str, 
str->(Integer)str.length(),(t1,t2)->t1));
System.out.println(map.size());

在上面的代码中,我不想为同一个键的两个值做任何事情,我只想返回一个值,因为在我的情况下,值肯定是相同的。 由于我没有使用 t2 值声纳抛出错误,所以我发现在 java8 中是否有类似 UnaryOperator.identity()BinaryOperator 的东西

【问题讨论】:

  • 一个身份,根据定义,返回其输入而不做任何修改。如果输入是两个值,那么它的输出也必须是两个值。您想要的不是身份,而是在处理值之前进行的某种不同或组操作。 (我不会说现代 Java,但在 C# 中,这实际上是 .Distinct.GroupBy。)
  • 问题不在于您的代码,而在于声纳警报。把它关掉就行了。
  • 一些函数式语言只有一元函数。在这些语言中,接受元组的函数等效于 Java 中的二进制函数。当您以这种方式考虑时,这样一个函数的标识很容易:如果一个元组是输入,它也必须是输出。

标签: java lambda java-8 functional-programming


【解决方案1】:

你的问题真的没有意义。如果您将建议的 BinaryOperator.identity 方法粘贴到 IDE 中,您会立即看到它会抱怨标识符 t 被声明了两次。

要解决这个问题,我们需要为每个参数使用不同的标识符:

return (t, u) -> t;

现在我们可以清楚地看到这不是一个identity函数。这是一个接受两个参数并返回第一个参数的方法。因此,最好的名称是getFirst

回答您关于 JDK 中是否有类似内容的问题:没有。使用标识函数是一个常见的用例,因此为此定义一个方法很有用。任意返回 2 的第一个参数不是一个常见的用例,并且有一个方法来做到这一点也没有用。

【讨论】:

  • 嗯,我同意你在这里所说的,除了最后一部分:Arbitrarily returning the first argument of two is not a common use case, and it's not useful to have a method to do that。事实上,它非常有用,正如 OP 在这里展示的那样。另一个用例是在实现收集器时,您需要提供一个您知道永远不会使用的组合器功能(因为即流是顺序的)。此外,BinaryOperator 确实提供了maxBy(Comparator) 静态方法(还有minBy),所以想知道为什么没有firstsecond 方法是有道理的......
  • @FedericoPeraltaSchaffner “常见”有点主观。我从来不需要它,我每天都使用 JDK。相比之下,我需要 Function.identity 可能有 100 多次。 JDK 应该是广泛有用的工具的集合。它并非旨在包含开发人员可能需要的所有内容。如果添加getFirst,人们会想知道getSecond 在哪里。如果你为BinaryOperator添加了这两个,为什么不为其他42个功能接口添加类似的呢?繁荣 - 你现在有大约 100 种基本无用的方法使 API 混乱。
  • 我完全同意你的观点,我有点扮演魔鬼代言人的角色......问题的问题不是不存在选择第一个或第二个的方法元素,但声纳在不应该发出警报的时候...
【解决方案2】:

T 表示它们具有相同的类型,而不是相同的,这本身不是一个身份。

这只是意味着BinaryOperator 将用于相同的类型,但为不同的值提供identity...这听起来有点像foldLeftfoldRightfoldLeftIdentity/foldRightIdentity,java 没有有。

【讨论】:

  • 您对 implementation of any 在合并中返回任何值有何看法?
  • @nullpointer :) 这就是我能说的。可能Math.random 在这里太贵了,我会选择像long nt = System.nanoTime(); ((nt &gt;&gt;&gt; 32) ^ nt) &gt; 0 ? ... : ... 这样更便宜的东西,但我已经在其他地方看到过这个:)(当然必须在这里采取最便宜和最快的随机数)
【解决方案3】:

您的代码似乎可以改进为

List<String> list = Arrays.asList("Abcd", "Abcd");
Map<String, Integer> map = list.stream()
            .collect(Collectors.toMap(Function.identity(), String::length, (a, b) -> a));
System.out.println(map.size());

或者可能对于您的用例我不想为同一个键的两个值做任何事情,我只想返回一个值,您可以选择使用如下实现随机返回任意值:

private static <T> BinaryOperator<T> any() {
    return Math.random() < 0.5 ? ((x, y) -> x) : ((x, y) -> y);
}

然后在你的代码中使用它

Map<String, Integer> map = list.stream()
            .collect(Collectors.toMap(Function.identity(), String::length, any()));

感谢 Holger、Eugene 和 Federico 的建议,any 方法的其他有效实现实际上可以涉及使用:

private static <T> BinaryOperator<T> any() {
    // suggested by Holger
    return ThreadLocalRandom.current().nextBoolean() ? ((x, y) -> x) : ((x, y) -> y);
    // suggested by Eugene
    long nt = System.nanoTime(); 
    ((nt >>> 32) ^ nt) > 0 ? ((x, y) -> x) : ((x, y) -> y);
}

【讨论】:

  • 顺便说一句,这是一次非常有趣的合并:)
  • @Eugene 我想我必须根据问题提到... 我不想为同一个键的两个值做任何事情,我只想返回一个值
  • 你知道你的随机实现不会每次都选择一个随机参数,而是会在调用时选择一个实现,然后总是返回相同的参数。顺便说一句,我没有投反对票
  • 我记得我在这里问过一些问题,Stuart Marks 友好地回答了...:D
  • 我会使用ThreadLocalRandom.current().nextBoolean() 而不是Math.random() &lt; 0.5。另一种选择是(a,b) -&gt; { assert Objects.equals(a, b); return a; }
猜你喜欢
  • 1970-01-01
  • 2018-09-16
  • 2011-08-23
  • 1970-01-01
  • 2020-08-06
  • 2011-10-07
  • 2018-01-01
  • 2015-10-12
  • 1970-01-01
相关资源
最近更新 更多