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


194

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


4
@RohitJain私は特定のストリームを意味していませんでした。CharStream存在しない場合、それを追加する際の問題は何でしょうか?
アダムディガ2014年

5
@AdamDyga:設計者は、プリミティブストリームを3つのタイプに制限することにより、クラスとメソッドの爆発を回避することを明示的に選択しました。パフォーマンスペナルティ。
JBニゼット2014年

3
@JBNizetわかりました。しかし、いくつかの新しいクラスを保存するためだけに、それはまだ汚いソリューションのように感じます。
アダムディガ2014年

9
@JB Nizet:私に、すべてのストリームオーバーロードとすべての関数インターフェイスを考えると、既にインターフェイスが爆発的に増加しているようです…
Holger

5
はい、プリミティブストリームの特殊化が3つしかなくても、すでに爆発的です。8つのプリミティブすべてにストリームの特殊化があったらどうなるでしょうか。大変動?:-)
スチュアートマーク

回答:


214

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

それでも、個人的にはこれは非常に悪い決断だったと思います。そして、彼らが作りたくないと考えるならば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してラムダを介してオブジェクトにマップし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適切に動作できるようにすることです。


7
私の結論:APIのこの部分は設計上壊れています。しかし、幅広い回答に感謝
アダムディガ

26
+1は、私の提案が使用するcodePoints()のではなくchars()、あなたがライブラリ関数の多くは、すでに受け入れていますintに加えて、コード・ポイントのためにchar、例えば、すべての方法java.lang.Characterだけでなく、としてStringBuilder.appendCodePoint、などこのサポートがあるために存在しますjdk1.5
Holger

6
コードポイントについての良い点。それらを使用すると、Stringまたはでサロゲートペアとして表される補助文字が処理されchar[]ます。ほとんどのchar処理コードがサロゲートペアを誤って処理していると思います。
Stuart Marks

2
@skiwi、定義するvoid print(int ch) { System.out.println((char)ch); }と、メソッド参照を使用できます。
スチュアートマークス

2
Stream<Character>拒否された理由については、私の回答を参照してください。
Stuart Marks

90

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

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

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

これは、今日、20年近く経った今でも生きているトレードオフです。Java 5で追加されたオートボクシング機能により、ソースコードをボクシングおよびアンボクシングのメソッド呼び出しで煩雑にする必要がほとんどなくなりましたが、オーバーヘッドは依然として残ります。多くの場合、それは目立ちません。ただし、内部ループ内でボックス化またはボックス化解除を実行すると、CPUとガベージコレクションのオーバーヘッドが大幅に増加する可能性があります。

Streams APIを設計するとき、プリミティブをサポートする必要があることは明らかでした。ボクシング/アンボクシングのオーバーヘッドは、並列処理によるパフォーマンス上の利点をすべて殺します。ただし、すべてのプリミティブをサポートする必要はありませんでした。APIに膨大な量の混乱が加わるからです。(本当に使用法がわかりShortStreamますか?)「すべて」または「なし」は、デザインが快適な場所ですが、どちらも受け入れられませんでした。したがって、「ある程度」の妥当な値を見つける必要がありました。私たちは、プリミティブ専門になってしまったintlongdouble。(個人的に私は省略しintたでしょうが、それは私だけです。)

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

また、CharStreamプリミティブな特殊化についても検討しましたが、APIに追加するバルクの量に比べて、その使用はかなり狭いように思えます。追加する価値はないようです。

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

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

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

  2. APIが散らかり、JDK開発者にメンテナンスの負担がかかるという犠牲を払って、完全な一連の基本的な特殊化を提供できます。または

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

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


1
素敵な答え!しかし、それはに2つの異なる方法が存在しない理由には答えませんchars()。1つはStream<Character>(小さなパフォーマンスペナルティで)を返し、もう1つはであるIntStreamと考えられましたか?Stream<Character>パフォーマンスのペナルティよりも利便性に価値があると考えた場合、とにかくそれをマッピングすることになるでしょう。
skiwi、2014年

3
ミニマリズムがここに入ります。chars()char値をで返すメソッドがすでに存在する場合IntStream、同じ値をボックス化された形式で取得する別のAPI呼び出しを追加しても、それほど追加されません。呼び出し元は、それほど問題なく値をボックス化できます。確かに、この(おそらくまれな)ケースでこれを実行する必要がない方が便利ですが、APIが煩雑になるためです。
スチュワートマークス

5
重複した質問のおかげで、私はこれに気づきました。このメソッドがめったに使用されなかったという事実を考えると、chars()戻ることIntStreamは特に大きな問題ではないことに同意します。しかし内蔵のバック変換する方法持つことが良いでしょうIntStreamString。それはで行うことができます.reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString()が、それは本当に長いです。
Tagir Valeev 2015年

7
@TagirValeevはい、やや面倒です。コードポイントのストリーム(IntStream)があれば、それほど悪くはありませんcollect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()。私はそれは本当に短くはないと思いますが、コードポイントを(char)使用するとキャストを回避し、メソッド参照の使用を許可します。さらに、サロゲートを適切に処理します。
スチュアートマークス2015年

2
@IlyaBystrov残念ながら、などのプリミティブストリームにはを受け取るメソッドIntStreamがありません。以前のコメントで述べたように、それらには3引数のメソッドしかありません。collect()Collectorcollect()
スチュアートマークス2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.