为什么 String.chars() 是 Java 8 中的 ints 流?

2022-08-31 05:50:51

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


答案 1

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

尽管如此,我个人认为这是一个非常糟糕的决定,鉴于他们不想做出,这是合理的,不同的方法,而不是,我会想到:CharStreamchars()

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

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

请注意,如果你忘记并使用 ,那么没有什么会抱怨,但你仍然会得到一个,你可能会想知道为什么它打印整数值而不是表示字符的字符串。mapToObjmapIntStream

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之所以这样设计,是因为不想添加,我个人认为该方法应该返回一个,目前的解决方法是在上使用,以便能够正确地使用它们。CharStreamStream<Character>mapToObj(i -> (char)i)IntStream


答案 2

skiwi的答案已经涵盖了许多要点。我将填写更多的背景信息。

任何 API 的设计都是一系列权衡。在Java中,一个困难的问题是处理很久以前做出的设计决策。

自 1.0 以来,原语一直在 Java 中。它们使Java成为一种“不纯”的面向对象语言,因为原语不是对象。我相信,添加原语是一个务实的决定,以牺牲面向对象的纯度为代价来提高性能。

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

在设计 Streams API 时,很明显我们必须支持基元。装箱/取消装箱开销会扼杀并行性带来的任何性能优势。但是,我们并不想支持所有的基元,因为这会给API增加大量的混乱。(你真的能看到一个的用途吗?“全部”或“无”是设计可以舒适的地方,但两者都是不可接受的。因此,我们必须找到一个合理的“一些”值。我们最终得到了 、 和 的原始特化。(就我个人而言,我会省略,但那只是我。ShortStreamintlongdoubleint

因为我们考虑过返回(早期的原型可能已经实现了这一点),但由于拳击开销而被拒绝。考虑到 String 具有作为基元的值,当调用方可能只是对值进行一些处理并将其重新装箱到字符串中时,无条件地强制实施装箱似乎是错误的。CharSequence.chars()Stream<Character>char

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

这对调用方造成的惩罚是,他们必须知道包含表示为的值,并且必须在正确的位置进行强制转换。这是加倍令人困惑的,因为存在过载的API调用,并且其行为明显不同。可能会出现另一个混淆点,因为调用还返回一个,但它包含的值完全不同。IntStreamcharintsPrintStream.print(char)PrintStream.print(int)codePoints()IntStream

因此,这归结为在几种替代方案中务实地进行选择:

  1. 我们无法提供原始的特化,从而产生一个简单,优雅,一致的API,但它带来了高性能和GC开销;

  2. 我们可以提供一套完整的原始专业化,代价是使API混乱并给JDK开发人员带来维护负担;或

  3. 我们可以提供原始特化的子集,提供一个中等大小,高性能的API,在相当狭窄的用例(char处理)范围内给调用方带来相对较小的负担。

我们选择了最后一个。