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?
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?
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 daripada chars()
, 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:
for (int i = 0; i < hello.length(); i++) {
System.out.println(hello.charAt(i));
}
Dan saya pikir metode yang masuk akal untuk melakukannya di Java 8 adalah sebagai berikut:
hello.chars()
.mapToObj(i -> (char)i)
.forEach(System.out::println);
Di sini saya mendapatkan IntStream
dan memetakannya ke objek melalui lambda i -> (char)i
, ini akan secara otomatis memasukkannya ke dalam Stream<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 menggunakan map
, maka tidak ada yang mengeluh, tetapi Anda masih akan berakhir dengan IntStream
, 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:
hello.chars()
.forEach(i -> System.out.println((char)i));
Selain itu, menggunakan referensi metode ke metode Anda sendiri tidak berfungsi lagi! Pertimbangkan yang berikut ini:
private void print(char c) {
System.out.println(c);
}
lalu
hello.chars()
.forEach(this::print);
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 mengembalikan Stream<Character>
, dan solusi saat ini adalah menggunakan mapToObj(i -> (char)i)
padaIntStream
agar dapat bekerja dengan baik dengan mereka.
codePoints()
sebagai ganti chars()
dan Anda akan menemukan banyak fungsi pustaka yang telah menerima int
titik kode untuk tambahan char
, misalnya semua metode java.lang.Character
dan juga StringBuilder.appendCodePoint
, dll. Dukungan ini ada sejak saat itu jdk1.5
.
String
atau char[]
. Saya berani bertaruh bahwa kebanyakan char
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 untuk int
, long
, dan double
. (Secara pribadi saya akan ditinggalkan int
tetapi itu hanya saya.)
Karena CharSequence.chars()
kami mempertimbangkan untuk kembali Stream<Character>
(prototipe awal mungkin telah menerapkan ini) tetapi ditolak karena tinju overhead. Mempertimbangkan bahwa sebuah String memiliki char
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 sebagai ints
dan bahwa casting harus dilakukan di tempat yang tepat. Ini membingungkan ganda karena ada panggilan API kelebihan beban seperti PrintStream.print(char)
dan PrintStream.print(int)
yang sangat berbeda dalam perilaku mereka. Titik kebingungan tambahan mungkin muncul karena codePoints()
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.
chars()
, satu yang mengembalikan Stream<Character>
(dengan penalti kinerja kecil) dan yang lainnya IntStream
, apakah ini juga dipertimbangkan? Sangat mungkin bahwa orang akhirnya akan memetakannya menjadi suatu cara Stream<Character>
jika mereka pikir kenyamanan layak atas penalti kinerja.
chars()
metode yang mengembalikan nilai char dalam IntStream
, 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()
pengembalian IntStream
bukanlah masalah besar terutama mengingat fakta bahwa metode ini jarang digunakan sama sekali. Namun akan lebih baik untuk memiliki cara bawaan untuk mengkonversi kembali IntStream
ke String
. 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 memiliki collect()
metode yang membutuhkan Collector
. Mereka hanya memiliki metode tiga-arg collect()
seperti yang disebutkan dalam komentar sebelumnya.
CharStream
tidak ada, apa masalahnya untuk menambahkannya?