为什么在Java 8中String.chars()是一个整数流?


194

在Java 8中,有一个新方法String.chars()可返回代表字符代码的ints(IntStream)流。我想很多人会期待char这里有s 流。这样设计API的动机是什么?


4
@RohitJain我的意思不是任何特定的流。如果CharStream不存在,添加它会有什么问题?
亚当·迪加

5
@AdamDyga:设计师明确地选择通过将原始流限制为3种类型来避免类和方法的爆炸式增长,因为其他类型(char,short,float)可以由其较大的等价物(int,double)表示,而没有任何显着性性能损失。
JB Nizet 2014年

3
@JBNizet我明白了。但是,仅仅为了节省几个新类,它仍然感觉像是一个肮脏的解决方案。
亚当·迪加

9
@JB Nizet:在我看来,考虑到所有流重载以及所有函数接口,我们已经有了爆炸的接口 ……
Holger 2014年

5
是的,即使只有三个原始流专业化,也已经在爆炸。如果所有八个原语都具有流专业化,那会是什么?大灾变?:-)
斯图尔特(Stuart)

Answers:


214

正如其他人已经提到的那样,其背后的设计决策是防止方法和类的爆炸式增长。

尽管如此,我个人还是认为这是一个非常糟糕的决定,并且鉴于他们不想做出CharStream合理的替代方法chars(),我应该考虑:

  • Stream<Character> chars(),它会提供一系列字符,这会降低性能。
  • IntStream unboxedChars(),该代码将用于性能代码。

但是,我认为,这个答案应该集中在展示一种使用Java 8中获得的API来完成此操作的方法,而不是着眼于目前为什么这样做的原因

在Java 7中,我应该这样做:

for (int i = 0; i < hello.length(); i++) {
    System.out.println(hello.charAt(i));
}

而且我认为在Java 8中执行此操作的合理方法如下:

hello.chars()
        .mapToObj(i -> (char)i)
        .forEach(System.out::println);

在这里,我获得一个,IntStream并通过lambda i -> (char)i将其映射到一个对象,这将自动将其装箱成一个Stream<Character>,然后我们可以做我们想做的事情,并且仍然使用方法引用作为加号。

请注意,尽管您必须这样做mapToObj,但是如果忘记并使用它map,那么什么也不会抱怨,但是最终还是会出现一个IntStream,并且您可能会怀疑为什么它会打印整数值而不是表示字符的字符串。

Java 8的其他丑陋替代品:

通过保留在IntStream并希望最终打印它们,您将无法再使用方法引用进行打印:

hello.chars()
        .forEach(i -> System.out.println((char)i));

而且,使用对您自己方法的方法引用不再起作用!考虑以下:

private void print(char c) {
    System.out.println(c);
}

然后

hello.chars()
        .forEach(this::print);

这可能会导致编译错误,因为可能会有损耗的转换。

结论:

API的这种设计方式是因为不想添加CharStream,我个人认为该方法应返回Stream<Character>,并且当前的解决方法是在mapToObj(i -> (char)i)上使用IntStream以便能够与之正常工作。


7
我的结论是:API的这一部分被设计破坏了。但感谢广泛的答案
亚当Dyga

26
+1,但是我的建议是使用,codePoints()而不是,chars()您会发现许多库函数已经接受了intfor代码点char,例如java.lang.Character,以及的所有方法StringBuilder.appendCodePoint,等等jdk1.5。自从开始存在这种支持。
霍尔格

6
关于代码点的要点。使用它们将处理补充字符,这些补充字符在String或中表示为代理对char[]。我敢打赌大多数char处理代码会错误处理代理对。
斯图尔特(Stuart Marks)2014年

2
@skiwi,定义void print(int ch) { System.out.println((char)ch); },然后可以使用方法引用。
斯图尔特(Stuart Marks)

2
请参阅我的回答以了解为什么Stream<Character>被拒绝。
斯图尔特(Stuart Marks)2014年

90

Skiwi答案已经涵盖了许多要点。我会再填一些背景。

任何API的设计都是一系列折衷。在Java中,难题之一是处理很久以前做出的设计决策。

从1.0开始,基元就已经在Java中使用。它们使Java成为“不纯的”面向对象的语言,因为基元不是对象。我相信添加原语是一个务实的决定,它以牺牲面向对象的纯度为代价来提高性能。

这是我们在二十年后的今天仍然需要权衡的问题。Java 5中添加的自动装箱功能主要消除了使用装箱和拆箱方法调用使源代码混乱的问题,但开销仍然存在。在许多情况下,它并不明显。但是,如果要在一个内部循环中执行装箱或拆箱,您会发现它会带来大量的CPU和垃圾回收开销。

在设计Streams API时,很明显,我们必须支持原语。装箱/拆箱的开销将扼杀并行性带来的任何性能优势。但是,我们并不想支持所有的原语,因为那样会给API增加很多麻烦。(您真的可以看到ShortStream“?” 的用途吗?)“全部”或“无”是设计的理想位置,但都不可接受。因此,我们必须找到“ some”的合理值。我们结束了与原始的专长intlongdouble。(就我个人而言,我会被排除在外,int但那只是我。)

因为CharSequence.chars()我们考虑过退货Stream<Character>(早期的原型可能已经实现了),但由于装箱费用而被拒绝。考虑到字符串具有char作为原始值的字符串,当调用者可能只是对该​​值进行一些处理并将其重新装箱回到字符串中时,无条件地施加装箱操作似乎是一个错误。

我们还考虑了CharStream原始的专业化,但是与将其添加到API的大量数量相比,它的使用似乎非常狭窄。似乎没有必要添加它。

这给调用者带来的代价是,他们必须知道IntStream包含char表示为的值,ints并且必须在适当的位置进行转换。这令人倍加困惑,因为存在诸如PrintStream.print(char)和的API调用重载PrintStream.print(int),其行为明显不同。可能codePoints()还会引起混乱,因为该调用还会返回一个,IntStream但其中包含的值却大不相同。

因此,这归结为在几种选择中务实地选择:

  1. 我们无法提供原始的专业知识,因此会产生简单,优雅,一致的API,但会带来高性能和GC开销;

  2. 我们可以提供一套完整的原始专业知识,但代价是使API混乱不堪,并给JDK开发人员带来了维护负担;要么

  3. 我们可以提供原始专业化的子集,提供中等大小的高性能API,从而在相当狭窄的用例范围内(字符处理)为调用者带来相对较小的负担。

我们选择了最后一个。


1
好答案!但是,它不能回答为什么不能有两种不同的方法chars(),一种返回的结果Stream<Character>(性能损失很小),另一种返回IntStream,是否也考虑了?Stream<Character>如果人们认为就性能损失而言值得考虑的话,人们很有可能最终将它映射到任何地方。
skiwi 2014年

3
极简主义来了。如果已经有一个chars()方法可以在中返回char值IntStream,那么添加另一个具有相同值但以框形式的API调用并不会增加多少。调用者可以轻松装箱值。当然,在这种情况(可能很少)中不必执行此操作会更方便,但是会给API增加混乱的代价。
Stuart Marks 2014年

5
由于重复的问题,我注意到了这一点。我同意chars()返回IntStream不是一个大问题,尤其是考虑到这种方法很少使用的事实。但是这将是很好有一个内置的方式转换回IntStreamString。可以使用完成.reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString(),但是确实很长。
Tagir Valeev 2015年

7
@TagirValeev是的,这有点麻烦。有了一系列代码点(IntStream),还不错:collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()。我猜它并不是真的很短,但是使用代码点可以避免(char)强制类型转换,并允许使用方法引用。另外,它可以正确处理代理。
斯图尔特·马克

2
@IlyaBystrov不幸的是,诸如此类的原始流IntStream没有采用的collect()方法Collectorcollect()正如前面的评论中所提到的,它们只有一个三参数方法。
斯图尔特(Stuart)标志
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.