Di Java 8, ada metode baru String.chars()
yang mengembalikan aliran int
s ( IntStream
) yang mewakili kode karakter. Saya kira banyak orang akan mengharapkan aliran char
di sini sebagai gantinya. Apa motivasi untuk merancang API dengan cara ini?
198
CharStream
tidak ada, apa masalahnya untuk menambahkannya?Jawaban:
Seperti yang telah disebutkan orang lain, keputusan desain di balik ini adalah untuk mencegah ledakan metode dan kelas.
Namun, secara pribadi saya pikir ini adalah keputusan yang sangat buruk, dan harus ada, mengingat mereka tidak ingin membuat
CharStream
, yang masuk akal, metode yang berbeda daripadachars()
, saya akan memikirkan:Stream<Character> chars()
, yang memberikan aliran karakter kotak, yang akan memiliki beberapa penalti performa ringan.IntStream unboxedChars()
, yang akan digunakan untuk kode kinerja.Namun , alih-alih berfokus pada mengapa hal itu dilakukan dengan cara saat ini, saya pikir jawaban ini harus fokus pada menunjukkan cara untuk melakukannya dengan API yang kami dapatkan dengan Java 8.
Di Java 7 saya akan melakukannya seperti ini:
Dan saya pikir metode yang masuk akal untuk melakukannya di Java 8 adalah sebagai berikut:
Di sini saya mendapatkan
IntStream
dan memetakannya ke objek melalui lambdai -> (char)i
, ini akan secara otomatis memasukkannya ke dalamStream<Character>
, dan kemudian kita bisa melakukan apa yang kita inginkan, dan masih menggunakan referensi metode sebagai nilai tambah.Sadarilah bahwa Anda harus melakukannya
mapToObj
, jika Anda lupa dan menggunakanmap
, maka tidak ada yang mengeluh, tetapi Anda masih akan berakhir denganIntStream
, dan Anda mungkin akan bertanya-tanya mengapa ia mencetak nilai integer alih-alih string yang mewakili karakter.Alternatif jelek lainnya untuk Java 8:
Dengan tetap dalam
IntStream
dan ingin mencetaknya pada akhirnya, Anda tidak dapat menggunakan referensi metode lagi untuk mencetak:Selain itu, menggunakan referensi metode ke metode Anda sendiri tidak berfungsi lagi! Pertimbangkan yang berikut ini:
lalu
Ini akan memberikan kesalahan kompilasi, karena mungkin ada konversi lossy.
Kesimpulan:
API dirancang dengan cara ini karena tidak ingin menambahkan
CharStream
, saya pribadi berpikir bahwa metode tersebut harus mengembalikanStream<Character>
, dan solusi saat ini adalah menggunakanmapToObj(i -> (char)i)
padaIntStream
agar dapat bekerja dengan baik dengan mereka.sumber
codePoints()
sebagai gantichars()
dan Anda akan menemukan banyak fungsi pustaka yang telah menerimaint
titik kode untuk tambahanchar
, misalnya semua metodejava.lang.Character
dan jugaStringBuilder.appendCodePoint
, dll. Dukungan ini ada sejak saat itujdk1.5
.String
atauchar[]
. Saya berani bertaruh bahwa kebanyakanchar
kode pemrosesan salah pasang pasangan pengganti.void print(int ch) { System.out.println((char)ch); }
dan kemudian Anda dapat menggunakan referensi metode.Stream<Character>
ditolak.The jawaban dari skiwi mencakup banyak poin utama sudah. Saya akan mengisi sedikit lebih banyak latar belakang.
Desain API apa pun adalah serangkaian pengorbanan. Di Jawa, salah satu masalah yang sulit adalah berurusan dengan keputusan desain yang dibuat sejak lama.
Primitif telah ada di Jawa sejak 1.0. Mereka menjadikan Java sebagai bahasa berorientasi objek yang "tidak murni", karena primitif bukan objek. Penambahan primitif, saya percaya, merupakan keputusan pragmatis untuk meningkatkan kinerja dengan mengorbankan kemurnian berorientasi objek.
Ini adalah tradeoff yang masih kita jalani hingga hari ini, hampir 20 tahun kemudian. Fitur autoboxing ditambahkan di Java 5 sebagian besar menghilangkan kebutuhan untuk mengacaukan kode sumber dengan panggilan metode tinju dan unboxing, tetapi overhead masih ada. Dalam banyak kasus itu tidak terlihat. Namun, jika Anda melakukan tinju atau unboxing dalam loop batin, Anda akan melihat bahwa itu dapat memaksakan CPU dan pengumpulan sampah yang signifikan.
Saat merancang Streams API, jelas bahwa kami harus mendukung primitif. Overhead tinju / unboxing akan membunuh manfaat kinerja dari paralelisme. Kami tidak ingin mendukung semua primitif, karena itu akan menambah kekacauan besar pada API. (Dapatkah Anda benar-benar melihat kegunaan untuk
ShortStream
?) "Semua" atau "tidak ada" adalah tempat yang nyaman untuk desain, namun tidak ada yang dapat diterima. Jadi kami harus menemukan nilai "beberapa" yang masuk akal. Kami berakhir dengan spesialisasi primitif untukint
,long
, dandouble
. (Secara pribadi saya akan ditinggalkanint
tetapi itu hanya saya.)Karena
CharSequence.chars()
kami mempertimbangkan untuk kembaliStream<Character>
(prototipe awal mungkin telah menerapkan ini) tetapi ditolak karena tinju overhead. Mempertimbangkan bahwa sebuah String memilikichar
nilai sebagai primitif, itu akan menjadi kesalahan untuk memaksakan tinju tanpa syarat ketika penelepon mungkin hanya akan melakukan sedikit pemrosesan pada nilai dan membuka kotak itu kembali menjadi sebuah string.Kami juga menganggap
CharStream
spesialisasi primitif, tetapi penggunaannya tampaknya cukup sempit dibandingkan dengan jumlah massal yang akan ditambahkan ke API. Tampaknya tidak ada gunanya menambahkannya.Hukuman yang dikenakan pada penelepon adalah bahwa mereka harus tahu bahwa nilai
IntStream
-char
nilai yang diwakili sebagaiints
dan bahwa casting harus dilakukan di tempat yang tepat. Ini membingungkan ganda karena ada panggilan API kelebihan beban sepertiPrintStream.print(char)
danPrintStream.print(int)
yang sangat berbeda dalam perilaku mereka. Titik kebingungan tambahan mungkin muncul karenacodePoints()
panggilan juga mengembalikan sebuahIntStream
tetapi yang dikandungnya sangat berbeda.Jadi, ini bermula untuk memilih secara pragmatis di antara beberapa alternatif:
Kami tidak dapat memberikan spesialisasi primitif, menghasilkan API yang sederhana, elegan, konsisten, tetapi yang memaksakan kinerja tinggi dan overhead GC;
kami dapat menyediakan satu set lengkap spesialisasi primitif, dengan biaya mengacaukan API dan membebankan beban pemeliharaan pada pengembang JDK; atau
kami dapat menyediakan subkumpulan spesialisasi primitif, memberikan API berkinerja sedang, berukuran sedang, dan tinggi yang membebankan beban yang relatif kecil pada penelepon dalam rentang kasus penggunaan yang cukup sempit (pemrosesan char).
Kami memilih yang terakhir.
sumber
chars()
, satu yang mengembalikanStream<Character>
(dengan penalti kinerja kecil) dan yang lainnyaIntStream
, apakah ini juga dipertimbangkan? Sangat mungkin bahwa orang akhirnya akan memetakannya menjadi suatu caraStream<Character>
jika mereka pikir kenyamanan layak atas penalti kinerja.chars()
metode yang mengembalikan nilai char dalamIntStream
, itu tidak menambah banyak untuk memiliki panggilan API lain yang mendapatkan nilai yang sama tetapi dalam bentuk kotak. Penelepon dapat mengemas nilai-nilai tanpa banyak kesulitan. Tentu akan lebih mudah untuk tidak harus melakukan ini dalam hal ini (mungkin jarang), tetapi dengan biaya menambahkan kekacauan ke API.chars()
pengembalianIntStream
bukanlah masalah besar terutama mengingat fakta bahwa metode ini jarang digunakan sama sekali. Namun akan lebih baik untuk memiliki cara bawaan untuk mengkonversi kembaliIntStream
keString
. Itu bisa dilakukan dengan.reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString()
, tapi itu sangat panjang.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()
. Saya kira itu tidak benar-benar lebih pendek, tetapi menggunakan poin kode menghindari(char)
gips dan memungkinkan penggunaan referensi metode. Plus itu menangani pengganti dengan benar.IntStream
tidak memilikicollect()
metode yang membutuhkanCollector
. Mereka hanya memiliki metode tiga-argcollect()
seperti yang disebutkan dalam komentar sebelumnya.