在Java 8中,有一个新方法String.chars()
可返回代表字符代码的int
s(IntStream
)流。我想很多人会期待char
这里有s 流。这样设计API的动机是什么?
在Java 8中,有一个新方法String.chars()
可返回代表字符代码的int
s(IntStream
)流。我想很多人会期待char
这里有s 流。这样设计API的动机是什么?
Answers:
正如其他人已经提到的那样,其背后的设计决策是防止方法和类的爆炸式增长。
尽管如此,我个人还是认为这是一个非常糟糕的决定,并且鉴于他们不想做出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
以便能够与之正常工作。
codePoints()
而不是,chars()
您会发现许多库函数已经接受了int
for代码点char
,例如java.lang.Character
,以及的所有方法StringBuilder.appendCodePoint
,等等jdk1.5
。自从开始存在这种支持。
String
或中表示为代理对char[]
。我敢打赌大多数char
处理代码会错误处理代理对。
void print(int ch) { System.out.println((char)ch); }
,然后可以使用方法引用。
Stream<Character>
被拒绝。
任何API的设计都是一系列折衷。在Java中,难题之一是处理很久以前做出的设计决策。
从1.0开始,基元就已经在Java中使用。它们使Java成为“不纯的”面向对象的语言,因为基元不是对象。我相信添加原语是一个务实的决定,它以牺牲面向对象的纯度为代价来提高性能。
这是我们在二十年后的今天仍然需要权衡的问题。Java 5中添加的自动装箱功能主要消除了使用装箱和拆箱方法调用使源代码混乱的问题,但开销仍然存在。在许多情况下,它并不明显。但是,如果要在一个内部循环中执行装箱或拆箱,您会发现它会带来大量的CPU和垃圾回收开销。
在设计Streams API时,很明显,我们必须支持原语。装箱/拆箱的开销将扼杀并行性带来的任何性能优势。但是,我们并不想支持所有的原语,因为那样会给API增加很多麻烦。(您真的可以看到ShortStream
“?” 的用途吗?)“全部”或“无”是设计的理想位置,但都不可接受。因此,我们必须找到“ some”的合理值。我们结束了与原始的专长int
,long
和double
。(就我个人而言,我会被排除在外,int
但那只是我。)
因为CharSequence.chars()
我们考虑过退货Stream<Character>
(早期的原型可能已经实现了),但由于装箱费用而被拒绝。考虑到字符串具有char
作为原始值的字符串,当调用者可能只是对该值进行一些处理并将其重新装箱回到字符串中时,无条件地施加装箱操作似乎是一个错误。
我们还考虑了CharStream
原始的专业化,但是与将其添加到API的大量数量相比,它的使用似乎非常狭窄。似乎没有必要添加它。
这给调用者带来的代价是,他们必须知道IntStream
包含char
表示为的值,ints
并且必须在适当的位置进行转换。这令人倍加困惑,因为存在诸如PrintStream.print(char)
和的API调用重载PrintStream.print(int)
,其行为明显不同。可能codePoints()
还会引起混乱,因为该调用还会返回一个,IntStream
但其中包含的值却大不相同。
因此,这归结为在几种选择中务实地选择:
我们无法提供原始的专业知识,因此会产生简单,优雅,一致的API,但会带来高性能和GC开销;
我们可以提供一套完整的原始专业知识,但代价是使API混乱不堪,并给JDK开发人员带来了维护负担;要么
我们可以提供原始专业化的子集,提供中等大小的高性能API,从而在相当狭窄的用例范围内(字符处理)为调用者带来相对较小的负担。
我们选择了最后一个。
chars()
,一种返回的结果Stream<Character>
(性能损失很小),另一种返回IntStream
,是否也考虑了?Stream<Character>
如果人们认为就性能损失而言值得考虑的话,人们很有可能最终将它映射到任何地方。
chars()
方法可以在中返回char值IntStream
,那么添加另一个具有相同值但以框形式的API调用并不会增加多少。调用者可以轻松装箱值。当然,在这种情况(可能很少)中不必执行此操作会更方便,但是会给API增加混乱的代价。
chars()
返回IntStream
不是一个大问题,尤其是考虑到这种方法很少使用的事实。但是这将是很好有一个内置的方式转换回IntStream
的String
。可以使用完成.reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString()
,但是确实很长。
collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()
。我猜它并不是真的很短,但是使用代码点可以避免(char)
强制类型转换,并允许使用方法引用。另外,它可以正确处理代理。
IntStream
没有采用的collect()
方法Collector
。collect()
正如前面的评论中所提到的,它们只有一个三参数方法。
CharStream
不存在,添加它会有什么问题?