web-dev-qa-db-ja.com

String.chars()がJava 8のintのストリームなのはなぜですか?

Java 8には、文字コードを表すints(IntStream)のストリームを返す新しいメソッド String.chars() があります。代わりに、多くの人がここでcharsのストリームを期待していると思います。このようにAPIを設計する動機は何ですか?

161
Adam Dyga

他の人がすでに述べたように、この背後にある設計上の決定は、メソッドとクラスの爆発を防ぐことでした。

それでも、個人的にはこれは非常に悪い決定であり、chars()の代わりにCharStream(合理的で異なるメソッド)を作成したくない場合は、次のように考える必要があります。

  • Stream<Character> chars()、ボックス文字のストリームを提供しますが、パフォーマンスが若干低下します。
  • IntStream unboxedChars()。これはパフォーマンスコードに使用されます。

ただしwhyに焦点を当てる代わりに、現在この方法で行われているので、この答えは、 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を取得し、ラムダi -> (char)iを介してオブジェクトにマップします。これにより、自動的にStream<Character>にボックス化されます。その後、メソッド参照をプラスとして使用できます。

注意してくださいただし、mustmapToObjを行う必要があります。忘れて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>を返すべきであり、回避策は現在IntStreamに対してmapToObj(i -> (char)i)を使用して彼らと。

188
skiwi

skiwiからの回答 は、すでに主要なポイントの多くをカバーしています。もう少し背景を説明します。

APIの設計は、一連のトレードオフです。 Javaでの難しい問題の1つは、ずっと前に行われた設計上の決定を扱うことです。

1.0以降、プリミティブはJavaにあります。プリミティブはオブジェクトではないため、Javaを「不純な」オブジェクト指向言語にします。プリミティブの追加は、オブジェクト指向の純度を犠牲にしてパフォーマンスを改善するための実用的な決定であったと思います。

これは、約20年後の現在も私たちが抱えているトレードオフです。 Java 5で追加されたオートボクシング機能により、ボクシングとアンボクシングメソッド呼び出しでソースコードを乱雑にする必要性がほとんどなくなりましたが、オーバーヘッドはまだ残っています。多くの場合、それは目立ちません。ただし、内側のループ内でボックス化またはボックス化解除を実行する場合、かなりのCPUおよびガベージコレクションのオーバーヘッドが発生する可能性があることがわかります。

Streams APIを設計するとき、プリミティブをサポートする必要があることは明らかでした。ボクシング/アンボクシングのオーバーヘッドは、並列処理によるパフォーマンス上のメリットを損ないます。ただし、allのプリミティブをサポートしたくありませんでした。それは、APIに大量の混乱を追加してしまうからです。 (ShortStreamの使用法を実際に見ることができますか?)「すべて」または「なし」は、デザインにとって快適な場所ですが、どちらも受け入れられませんでした。そのため、「some」という妥当な値を見つける必要がありました。最終的に、intlong、およびdoubleのプリミティブな特殊化が行われました。 (個人的にはintを省いたでしょうが、それは私だけです。)

CharSequence.chars()については、Stream<Character>を返すことを検討しました(初期のプロトタイプがこれを実装していた可能性があります)が、ボクシングのオーバーヘッドのために拒否されました。 Stringがchar値をプリミティブとして持っていることを考えると、呼び出し側がおそらく値に対して少し処理を行い、それをすぐに文字列に戻す場合、無条件にボクシングを課すのは間違いのようです。

CharStreamプリミティブ特殊化も検討しましたが、その使用は、APIに追加するバルクの量と比較して非常に狭いようです。それを追加する価値はありませんでした。

これが呼び出し側に課すペナルティは、IntStreamcharとして表されるints値を含み、キャストが適切な場所で行われなければならないことを知る必要があることです。 PrintStream.print(char)PrintStream.print(int)のようなオーバーロードされたAPI呼び出しがあり、それらの動作が著しく異なるため、これは二重に混乱します。 codePoints()呼び出しもIntStreamを返すため、追加の混乱点が生じる可能性がありますが、それに含まれる値はまったく異なります。

したがって、これはいくつかの選択肢の中から実用的に選択することに要約されます。

  1. プリミティブな特殊化を提供できなかったため、シンプルでエレガントで一貫性のあるAPIが得られましたが、高いパフォーマンスとGCオーバーヘッドが発生しました。

  2. aPIが乱雑になり、JDK開発者にメンテナンスの負担がかかるという代償を払って、完全なプリミティブスペシャライゼーションのセットを提供できます。または

  3. プリミティブな特殊化のサブセットを提供し、かなり狭い範囲のユースケース(char処理)で呼び出し元に比較的小さな負担をかける中程度のサイズの高性能APIを提供できます。

最後のものを選びました。

75
Stuart Marks