为什么String.chars()是Java 8中的整数串?
在Java 8中,有一个新的方法String.chars()
,它返回一个代表字符代码的int
( IntStream
)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一个合理的“一些”的价值。 我们结束了int
, long
和double
原始专业化。 (就个人而言,我可能会忽略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:
-
我们不能提供原始的专业化,导致一个简单,优雅,一致的API,但是这会产生高性能和GC开销;
-
我们可以提供一套完整的原始专业化服务,代价是搞乱了API,给JDK开发人员带来了维护的负担; 要么
-
我们可以提供原始专业化的一个子集,给出一个适度大小,高性能的API,在相当狭窄的用例范围内(char处理)对调用者施加相对较小的负担。
我们select了最后一个。