为什么String.chars()是Java 8中的整数串?

在Java 8中,有一个新的方法String.chars() ,它返回一个代表字符代码的intIntStream )stream。 我想很多人会期望这里有一串char 。 这样deviseAPI的动机是什么?

正如其他人已经提到的那样,这背后的devise决定是为了防止方法和类的爆炸。

不过,我个人认为这是一个非常糟糕的决定,因为他们不想让CharStream ,这是合理的,不同的方法,而不是chars() ,我会想到:

  • Stream<Character> chars() ,给出了一个box的字符stream,这将会有一些轻的性能损失。
  • 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的其他难看的select:

通过保留在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是这样devise的,因为不想添加CharStream ,我个人认为这个方法应该返回一个Stream<Character> ,目前的解决方法是在mapToObj(i -> (char)i)上使用mapToObj(i -> (char)i)能够与他们正常工作。

skiwi的答案已经涵盖了很多主要的观点。 我会填补更多的背景。

任何API的devise都是一系列的权衡。 在Java中,其中一个难题就是处理很久以前的devise决策。

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

这是近20年后我们仍然与今天生活在一起的一种折衷。 Java 5中添加的自动装箱function大多消除了使用装箱和拆箱方法调用混乱源代码的需要,但开销依然存在。 在很多情况下,这并不明显。 但是,如果要在内部循环中执行装箱或取消装箱操作,则会看到它可能会导致大量的CPU和垃圾收集开销。

在deviseStreams API时,显然我们必须支持原语。 装箱/拆箱开销将会消除并行性带来的任何性能收益。 但是我们不想支持所有的原语,因为这会给API增加大量的混乱。 (你真的可以看到一个ShortStream吗?)“所有”或“没有”是一个devise的舒适的地方,但都不是可以接受的。 所以我们必须find一个合理的“一些”的价值。 我们结束了intlongdouble原始专业化。 (就个人而言,我可能会忽略int但这只是我。)

对于CharSequence.chars()我们考虑返回Stream<Character> (一个早期的原型可能已经实现了这个),但是由于装箱开销而被拒绝。 考虑到一个String有char值作为原语,当调用者可能只是对值进行一些处理并将其解除为一个string的时候,无条件地强加boxing似乎是错误的。

我们也考虑了一个CharStream专门化,但是它的使用看起来相当狭窄,相比之下,它会增加到API的批量。 这似乎不值得添加它。

这对调用者造成的惩罚是他们必须知道IntStream包含以char值表示的char值,并且必须在适当的地方进行转换。 这是双重混淆,因为有重载的API调用,如PrintStream.print(char)PrintStream.print(int) ,它们的行为显着不同。 由于codePoints()调用也返回一个IntStream但是它所包含的值是完全不同的,所以可能会出现一个额外的混淆点。

所以,这可以归结为在几个备选scheme中务实的select:

  1. 我们不能提供原始的专业化,导致一个简单,优雅,一致的API,但是这会产生高性能和GC开销;

  2. 我们可以提供一套完整的原始专业化服务,代价是搞乱了API,给JDK开发人员带来了维护的负担; 要么

  3. 我们可以提供原始专业化的一个子集,给出一个适度大小,高性能的API,在相当狭窄的用例范围内(char处理)对调用者施加相对较小的负担。

我们select了最后一个。