为什么String.chars()是Java 8中的一个int流?

问题

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


#1 热门回答(163 赞)

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

不过,我个人认为这是一个非常糟糕的决定,并且应该,鉴于他们不想制作CharStream,这是合理的,不同的方法而不是chars(),我会想到:

  • Stream <Character> chars(),它提供了一个盒子字符流,这将有一些轻微的性能损失。
  • IntStream unboxedChars(),它将用于性能代码。

然而,我认为这个答案应该集中在用我们用Java 8获得的API来实现它,而不是专注于onwhyit。

在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);

在这里,我获得anIntStream并通过lambdai -> (char)i将其映射到一个对象,这将自动将其框入aStream<Character>,然后我们可以做我们想要的,并仍然使用方法引用作为加号。

请注意虽然你是myustusmapToObj,如果你忘记并使用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是这样设计的,因为不想添加288686170,我个人认为该方法应返回aStream<Character>,目前的解决方法是使用mapToObj(i -> (char)i)和230303348能够正常使用它们。


#2 热门回答(65 赞)

Theanswer from skiwi已经发现了许多重点。我会填写更多背景信息。

任何API的设计都是一系列的权衡。在Java中,难题之一是处理很久以前制定的设计决策。

从1.0开始,基元一直在Java中。它们使Java成为一种"不纯的"面向对象语言,因为原语不是对象。我认为,添加原语是一种务实的决定,以牺牲面向对象的纯度为代价来提高性能。

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

在设计Streams API时,很明显我们必须支持原语。装箱/拆箱开销会破坏并行性带来的任何性能优势。但是,我们不想支持所有原始数据,因为这会给API增加大量的混乱。 (你能真正看到aShortStream的使用吗?)"全部"或"无"是设计的舒适场所,但都不可接受。所以我们必须找到合理的"一些"价值。我们最终得到了原始专业化,分别为int,longdouble。 (就个人而言,我会遗漏掉int但那只是我。)

ForCharSequence.chars()我们考虑返回Stream<Character>(早期的原型可能实现了这个),但由于拳击开销而被拒绝。考虑到String具有char值作为基元,当调用者可能只对该值进行一些处理并将其反转回字符串时,无条件地强加拳击似乎是错误的。

我们还考虑了aCharStream原型专业化,但与其添加到API的批量相比,它的使用似乎相当狭窄。添加它似乎不值得。

对呼叫者施加的惩罚是他们必须知道IntStream包含的char值代表为ints并且必须在适当的位置进行投射。这是令人困惑的,因为有超重的API调用,如PrintStream.print(char)PrintStream.print(int),它们的行为明显不同。可能会出现另一个混乱点,因为codePoints()call也返回一个IntStream但它包含的值非常不同。

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

  • 我们不能提供原始的特化,从而形成一个简单,优雅,一致的API,但它会带来高性能和GC开销;
  • 我们可以提供一整套原始专业化,代价是混乱API并给JDK开发人员带来维护负担;要么
  • 我们可以提供原始特化的子集,提供适度大小,高性能的API,在相当窄的用例范围内(char处理)对调用者施加相对较小的负担。

我们选择了最后一个。