Java 8には、文字コードを表すs()のString.chars()
ストリームを返す新しいメソッドがあります。代わりにここにsのストリームを期待する人が多いと思います。このようにAPIを設計する動機は何でしたか?int
IntStream
char
Java 8には、文字コードを表すs()のString.chars()
ストリームを返す新しいメソッドがあります。代わりにここにsのストリームを期待する人が多いと思います。このようにAPIを設計する動機は何でしたか?int
IntStream
char
回答:
他の人がすでに述べたように、これの背後にある設計上の決定は、メソッドとクラスの爆発を防ぐことでした。
それでも、個人的にはこれは非常に悪い決断だったと思います。そして、彼らが作りたくないと考えるならば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
適切に動作できるようにすることです。
codePoints()
のではなくchars()
、あなたがライブラリ関数の多くは、すでに受け入れていますint
に加えて、コード・ポイントのために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つは、ずっと前に行われた設計上の決定に対処することです。
プリミティブは1.0以降、Javaで使用されています。プリミティブはオブジェクトではないため、Javaは「純粋でない」オブジェクト指向言語になります。プリミティブの追加は、オブジェクト指向の純粋さを犠牲にしてパフォーマンスを改善するという実際的な決定だったと思います。
これは、今日、20年近く経った今でも生きているトレードオフです。Java 5で追加されたオートボクシング機能により、ソースコードをボクシングおよびアンボクシングのメソッド呼び出しで煩雑にする必要がほとんどなくなりましたが、オーバーヘッドは依然として残ります。多くの場合、それは目立ちません。ただし、内部ループ内でボックス化またはボックス化解除を実行すると、CPUとガベージコレクションのオーバーヘッドが大幅に増加する可能性があります。
Streams APIを設計するとき、プリミティブをサポートする必要があることは明らかでした。ボクシング/アンボクシングのオーバーヘッドは、並列処理によるパフォーマンス上の利点をすべて殺します。ただし、すべてのプリミティブをサポートする必要はありませんでした。APIに膨大な量の混乱が加わるからです。(本当に使用法がわかりShortStream
ますか?)「すべて」または「なし」は、デザインが快適な場所ですが、どちらも受け入れられませんでした。したがって、「ある程度」の妥当な値を見つける必要がありました。私たちは、プリミティブ専門になってしまったint
、long
とdouble
。(個人的に私は省略しint
たでしょうが、それは私だけです。)
CharSequence.chars()
戻ることを検討したためStream<Character>
(初期のプロトタイプはこれを実装した可能性があります)、ボクシングのオーバーヘッドのために拒否されました。文字列がchar
プリミティブとして値を持っていることを考えると、呼び出し元がおそらく値に対して少し処理を行い、それを文字列にアンボックスするだけの場合、無条件にボクシングを課すのは間違いのようです。
また、CharStream
プリミティブな特殊化についても検討しましたが、APIに追加するバルクの量に比べて、その使用はかなり狭いように思えます。追加する価値はないようです。
これが呼び出し元に課すペナルティは、IntStream
がchar
として表される値を含むこと、ints
およびキャストが適切な場所で行われる必要があることを知っている必要があることです。などのオーバーロードされたAPI呼び出しがPrintStream.print(char)
ありPrintStream.print(int)
、動作が著しく異なるため、これは二重に混乱します。codePoints()
呼び出しもを返すため、混乱が生じる可能性IntStream
がありますが、それに含まれる値はまったく異なります。
したがって、これは要約すると、いくつかの選択肢の中から実用的に選択することになります。
プリミティブな特殊化を提供できなかったため、シンプルでエレガントな一貫したAPIが得られましたが、パフォーマンスとGCのオーバーヘッドが高くなっていました。
APIが散らかり、JDK開発者にメンテナンスの負担がかかるという犠牲を払って、完全な一連の基本的な特殊化を提供できます。または
プリミティブな特殊化のサブセットを提供して、かなり狭い範囲のユースケース(char処理)で呼び出し元に比較的小さな負担をかける、適度なサイズの高性能APIを提供できます。
最後のものを選びました。
chars()
。1つはStream<Character>
(小さなパフォーマンスペナルティで)を返し、もう1つはである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
がありません。以前のコメントで述べたように、それらには3引数のメソッドしかありません。collect()
Collector
collect()
CharStream
存在しない場合、それを追加する際の問題は何でしょうか?