在Java 8中,有一种新方法String.chars()
,它返回表示字符代码的int
s(IntStream
)流。我想很多人会期待这里有char
的流。以这种方式设计API的动机是什么?
答案 0 :(得分:188)
正如其他人已经提到的,这背后的设计决策是为了防止方法和类的爆炸。
不过,我个人认为这是一个非常糟糕的决定,鉴于他们不想制作CharStream
,这是合理的,不同的方法而不是chars()
,我会想到:
Stream<Character> chars()
,它会给出一个盒子字符流,这会对性能造成轻微影响。IntStream unboxedChars()
,将用于效果代码。然而,而不是专注于为什么目前以这种方式完成,我认为这个答案应该专注于展示一种方法来实现我们拥有的API得到了Java 8。
在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
能够与他们合作。
答案 1 :(得分:74)
answer from skiwi已涵盖许多重点。我会填写更多背景信息。
任何API的设计都是一系列的权衡。在Java中,其中一个难题是处理很久以前做出的设计决策。
自1.0以来,基元一直在Java中。它们使Java成为一种“不纯的”面向对象语言,因为原语不是对象。我相信,添加原语是一种务实的决定,以牺牲面向对象的纯度为代价来提高性能。
这是我们今天仍然生活的权衡,近20年后。 Java 5中添加的自动装箱功能大多消除了使用装箱和拆箱方法调用来混乱源代码的需要,但开销仍然存在。在许多情况下,它并不明显。但是,如果您要在内部循环中执行装箱或拆箱,您会发现它可能会产生大量的CPU和垃圾收集开销。
在设计Streams API时,很明显我们必须支持原语。装箱/拆箱开销会破坏并行性带来的任何性能优势。但是,我们不想支持所有基元,因为这会给API增加大量的混乱。 (你能真正看到ShortStream
的用途吗?)“全部”或“无”是设计的舒适场所,但两者都不可接受。所以我们必须找到合理的“一些”价值。我们最终得到了int
,long
和double
的原始特化。 (就个人而言,我会遗漏int
,但那只是我。)
对于CharSequence.chars()
我们考虑返回Stream<Character>
(早期原型可能实现了这个),但由于拳击开销而被拒绝。考虑到String具有char
值作为基元,当调用者可能只对该值进行一些处理并将其反转回字符串时,无条件地强加拳击似乎是错误的。
我们还考虑了一个CharStream
原始特化,但与它添加到API的批量相比,它的使用似乎相当狭窄。添加它似乎不值得。
这对来电者施加的惩罚是他们必须知道IntStream
包含char
代表ints
的值,并且必须在适当的位置进行投射。这是一个令人困惑的问题,因为像PrintStream.print(char)
和PrintStream.print(int)
这样的重载API调用在行为方面存在显着差异。可能会出现另一个混淆点,因为codePoints()
调用也会返回IntStream
,但它包含的值却大不相同。
因此,这归结为在几种选择中实际选择:
我们不能提供原始的特化,从而形成一个简单,优雅,一致的API,但它会带来高性能和GC开销;
我们可以提供一整套原始专业化,但代价是混淆了API并给JDK开发人员带来了维护负担;或
我们可以提供一个原始特化的子集,提供一个中等大小,高性能的API,在相当狭窄的用例范围内(char处理)对调用者施加相对较小的负担。
我们选择了最后一个。