如何在 Java 8 中引用 reduce() 操作的结果?
Posted
技术标签:
【中文标题】如何在 Java 8 中引用 reduce() 操作的结果?【英文标题】:How to reference the result of reduce() operation in Java 8? 【发布时间】:2019-06-27 01:20:14 【问题描述】:我试图在 Java8 中编写一个 mkString
函数,这是一个 Scala 的有用 mkString
并遇到了 2 个我可以使用一些帮助的问题:
我无法将mkString
的第一个参数设置为像Collection<Object> c
这样的通用集合引用,并让调用者调用任何类型的集合。
无法通过内联引用reduce()
返回的结果来访问结果的长度以删除多余的前导分隔符。
代码如下:
public static void main(String[] args)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(mkString(numbers, ","));
public static String mkString(Collection<Integer> c, String sep)
return c.stream()
.map(e -> String.valueOf(e))
.reduce("", (a, b) -> a + sep + b)
.substring(1, <<>>.length);
【问题讨论】:
使用 reduce 来构建字符串的效率非常低。每个元素都需要累加器字符串的完整副本。最好使用专用的连接函数,它可以使用字符串生成器之类的东西。 【参考方案1】:请注意,如果您这样做不是为了自学,而是为了在一些生产代码中实际使用它,您可能需要考虑内置的 Collectors.joining
收集器:
String result = numbers.stream()
.map(Object::toString)
// or
// .map(x -> x.toString()) // exactly the same
// or
// .map(String::valueOf) // handles nulls by turning them to the string "null"
.collect(Collectors.joining(","));
它有几个重载,类似于 Scala 的 mkString
。尽管如此,这个收集器只接受CharSequence
s,因此您需要将值显式转换为字符串作为单独的map
步骤。
此外,还有String.join
方法,它也适用于CharSequence
s 的集合。如果您特别有其中一个(例如List<String>
),使用此方法可能更方便,而不是先将集合转换为流:
List<String> strings = ...;
String result = String.join(",", strings);
// vs
String result = strings.stream().collect(Collectors.joining(","))
【讨论】:
这不会编译。Collectors::joining
期待 CharSequence
,您还需要提供 mapper
如果您正在这样做是为了自学,请考虑自己编写等效的收集器(或者不完全相同来解决尤金提到的问题)。
尽管这个解决方案需要.map
,但我仍然认为这是最好的解决方案,因为它是性能最高的,因为它在内部使用字符串生成器,而不是连接字符串
确实,我错过了它想要CharSequence
s,所以它实际上与String.join
非常相似。我已经修复了我的答案,包括map
电话。
即使是出于教育目的,也值得指出collect(Collectors.joining(","))
是正确 解决方案,而reduce("", (a, b) -> a + "," + b)
的功能违反了关联性约束。【参考方案2】:
如果我没记错我的 java,您可以将参数类型声明为 Collection<?>
以便能够传递任何对象的集合。
至于咬掉分隔符,我想,.substring(1)
会做你想做的事。
【讨论】:
与 Collection> 方法相比,docs.oracle.com/javase/tutorial/extra/generics/methods.html 似乎更倾向于泛型方法。T
和 ?
在这种情况下是完全一样的。所以,这是一个品味问题。我个人更喜欢?
,因为它清楚地表明实现不使用或不需要类型信息,并且集合旨在进行变异。它也是呼叫站点差异的常见指标。
@212:我认为您一定是误读了您链接到的页面。在“[t]返回类型不依赖于类型参数,也不依赖于方法的任何其他参数”的情况下,“应该使用通配符”非常明确,并且将多个段落专门用于该指导。所以迪玛说的很对。
@Dima 用更简单的话来说(IIRC 也存在于有效的 java 中):如果类型参数只使用一次,则可以用通配符替换。
如果分隔符由多个字符组成,.substring(1)
将不起作用【参考方案3】:
你可以这样做:
public static <T> String mkString(Collection<T> c, String sep) // generic impl
return c.stream()
.map(String::valueOf)
.reduce("", (a, b) -> a + sep + b)
.substring(1); // substring implementation to strip leading character
【讨论】:
感谢您解决这两个问题。但是,这是否意味着没有语法可以访问reduce()的结果而不进行赋值呢?T
不是必需的。 Collection<?>
也可以。
@nullpointer 这不是关联的,因此违反了规范...例如,您希望从String s = Stream.of("a", "b", "c", "d") .parallel() .reduce("", (a, b) -> a + "-" + b); System.out.println(s);
打印什么?
@212 好吧,这取决于您主要要执行的操作。
@nullpointer 即使您不确定是否需要:1)文档要求它 2)尝试我上面给出的代码并查看结果 - 但首先尝试看看您是否可以预测它【参考方案4】:
任何类型 java中的collection表示Collection<?>
,语义上与Collection<T>
相同(在你的情况下),据说如果类型参数只使用一次)它可以安全地用通配符替换。但是,由于您希望能够连接任何集合,您还应该要求调用者提供一个Function
,它将从该类型转换为字符串表示,因此您的方法将变为:
public static <T> String mkString(Collection<T> c,
Function<T, ? extends CharSequence> mapper,
String sep)
return c.stream()
.map(mapper)
.collect(Collectors.joining(sep));
【讨论】:
【参考方案5】:您可以将String.join
与泛型类型一起使用:
public static <T> String mkString(Collection<T> c, String sep)
return String.join(sep, c.stream()
.map(e -> String.valueOf(e))
.collect(Collectors.toList()));
Here it is 作用于字符串和其他对象。
【讨论】:
您正在收集到List
,只是为了能够致电String::join
?为此目的有Collectors.joining
。并且您假设String::valueOf
将提供对象的任何有意义的字符串表示,这可能不成立。更好的方法是也传递一个映射器。
@Eugene 忘记了collectors.joining,但现在它已经是一个答案,所以我会保持原样。传递映射器虽然有用,但超出了 imo 的范围。感谢您的反馈。以上是关于如何在 Java 8 中引用 reduce() 操作的结果?的主要内容,如果未能解决你的问题,请参考以下文章
执行 map-reduce 操作的通用方法。 (Java-8)
为啥在java 8中转换类型的reduce方法需要一个组合器
java 使用reduce和Integer.max查找最大值java 8