Sebelas Mutiara Tersembunyi di Jawa 11

Java 11 tidak memperkenalkan fitur inovatif apa pun, tetapi berisi beberapa permata yang mungkin belum pernah Anda dengar. Sudah melihat yang terbaru di String , Optional , Collection , dan workhorses lainnya? Jika tidak, maka Anda telah datang ke alamat: hari ini kita akan melihat 11 permata tersembunyi dari Jawa 11!


Ketik inferensi untuk parameter lambda


Saat menulis ekspresi lambda, Anda dapat memilih di antara jenis yang secara eksplisit menentukan dan melewatkannya:


 Function<String, String> append = string -> string + " "; Function<String, String> append = (String s) -> s + " "; 

Java 10 memperkenalkan var , tetapi tidak bisa digunakan dalam lambdas:


 //    Java 10 Function<String, String> append = (var string) -> string + " "; 

Di Jawa 11 sudah dimungkinkan. Tapi mengapa? Sepertinya var memberikan lebih dari sekadar pass type. Meskipun demikian, penggunaan var memiliki dua keuntungan kecil:


  • membuat penggunaan var lebih universal dengan menghapus pengecualian pada aturan
  • memungkinkan Anda untuk menambahkan anotasi ke tipe parameter tanpa menggunakan nama lengkapnya

Ini adalah contoh dari kasus kedua:


 List<EnterpriseGradeType<With, Generics>> types = /*...*/; types .stream() // ,     @Nonnull   .filter(type -> check(type)) //  Java 10    ~>  .filter((@Nonnull EnterpriseGradeType<With, Generics> type) -> check(type)) //  Java 11    ~>   .filter((@Nonnull var type) -> check(type)) 

Meskipun mencampur tipe turunan, eksplisit dan implisit dalam ekspresi lambda dari bentuk (var type, String option, index) -> ... dapat diimplementasikan, tetapi ( dalam kerangka JEP-323 ) pekerjaan ini tidak dilakukan. Oleh karena itu, perlu untuk memilih salah satu dari tiga pendekatan dan mematuhinya untuk semua parameter ekspresi lambda. Kebutuhan untuk menentukan var untuk semua parameter untuk menambahkan anotasi untuk salah satunya dapat sedikit mengganggu, tetapi secara umum dapat ditoleransi.


Aliran pemrosesan string dengan 'String::lines'


Punya string multi-line? Ingin melakukan sesuatu dengan setiap barisnya? Maka String::lines adalah pilihan yang tepat:


 var multiline = "\r\n\r\n\r\n"; multiline .lines() //Stream<String> .map(line -> "// " + line) .forEach(System.out::println); // : //  //  //  //  

Perhatikan bahwa baris asli menggunakan pembatas sekrup \r\n dan meskipun saya di Linux, lines() masih mematahkannya. Hal ini disebabkan oleh fakta bahwa, terlepas dari OS saat ini, metode ini mengartikan \r , \n , dan \r\n sebagai jeda baris - bahkan jika mereka dicampur pada baris yang sama.


Aliran garis tidak pernah mengandung pemisah garis itu sendiri. Baris dapat kosong ( "\n\n \n\n" , yang berisi 5 baris), tetapi baris terakhir dari baris asli diabaikan jika kosong ( "\n\n" ; 2 baris). (Catatan oleh penerjemah: lebih mudah bagi mereka untuk memiliki line , tetapi untuk memiliki string , dan kami memiliki keduanya.)


Tidak seperti split("\R") , lines() malas dan, saya kutip , "memberikan kinerja yang lebih baik [...] dengan lebih cepat mencari jeda baris baru". (Jika seseorang ingin mengajukan tolok ukur pada JMH untuk verifikasi, beri tahu saya). Ini juga lebih baik mencerminkan algoritma pemrosesan dan menggunakan struktur data yang lebih nyaman (stream bukan array).


Menghapus spasi dengan 'String::strip' , dll.


Awalnya, String memiliki metode trim untuk menghapus spasi, yang dianggap semuanya dengan kode hingga U+0020 . Ya, BACKSPACE ( U+0008) adalah ruang putih seperti BELL ( U+0007 ), tetapi LINE SEPARATOR ( U+2028 ) tidak lagi dianggap seperti itu.


Java 11 memperkenalkan metode strip , yang pendekatannya memiliki lebih banyak nuansa. Ia menggunakan metode Character::isWhitespace dari Java 5 untuk menentukan apa yang sebenarnya perlu dihapus. Dari dokumentasinya jelas bahwa ini:


  • SPACE SEPARATOR , LINE SEPARATOR , PARAGRAPH SEPARATOR , tetapi bukan ruang yang tidak bisa dipisahkan
  • HORIZONTAL TABULATION ( U+0009 ), LINE FEED ( U+000A ), VERTICAL TABULATION ( U+000B ), FORM FEED ( U+000C ), CARRIAGE RETURN U+000D ( U+000D )
  • FILE SEPARATOR ( U+001C ), GROUP SEPARATOR ( U+001D ), RECORD SEPARATOR ( U+001E ), UNIT SEPARATOR ( U+001F )

Dengan logika yang sama, ada dua metode pembersihan lagi, stripLeading dan stripTailing , yang melakukan apa yang diharapkan dari mereka.


Dan akhirnya, jika Anda hanya perlu mencari tahu apakah garis menjadi kosong setelah menghapus spasi, maka tidak perlu untuk benar-benar menghapusnya - cukup gunakan isBlank :


 " ".isBlank(); //  ~> true " ".isBlank(); //   ~> false 

Mengulang string dengan 'String::repeat'


Tangkap idenya:


Langkah 1: Menyimpan Arloji di JDK

Mengawasi perkembangan JDK


Langkah 2: Menemukan Pertanyaan Terkait StackOverflow

Mencari pertanyaan terkait di Stackoverflow


Langkah 3: Tiba dengan jawaban baru berdasarkan perubahan di masa depan

Masuk dengan jawaban baru berdasarkan perubahan yang akan datang


Langkah 4: ????

Langkah 4: Untung

¯ \ _ (ツ) _ / ¯


Seperti yang dapat Anda bayangkan, String memiliki metode repeat(int) . Ia bekerja persis sesuai dengan harapan, dan ada sedikit untuk didiskusikan.


Membuat jalur dengan 'Path::of'


Saya sangat suka Path API, tetapi mengubah jalur di antara tampilan yang berbeda (seperti Path , File , URL , URI dan String ) masih mengganggu. Poin ini menjadi kurang membingungkan di Java 11 dengan menyalin dua Paths::get methods ke Path::of methods:


 Path tmp = Path.of("/home/nipa", "tmp"); Path codefx = Path.of(URI.create("http://codefx.org")); 

Mereka dapat dianggap kanonik, karena kedua Paths::get methods lama menggunakan opsi baru.


Membaca dan menulis file dengan 'Files::readString' dan 'Files::writeString'


Jika saya perlu membaca dari file besar, saya biasanya menggunakan Files::lines untuk mendapatkan aliran malas dari barisnya. Demikian pula, untuk menulis sejumlah besar data yang mungkin tidak disimpan dalam memori secara keseluruhan, saya menggunakan Files::write meneruskannya sebagai Iterable<String> .


Tapi bagaimana dengan kasus sederhana ketika saya ingin memproses isi file sebagai satu baris? Ini sangat tidak nyaman, karena Files::readAllBytes dan varian Files::write beroperasi pada array byte.


Dan kemudian Java 11 muncul, menambahkan readString dan writeString ke Files :


 String haiku = Files.readString(Path.of("haiku.txt")); String modified = modify(haiku); Files.writeString(Path.of("haiku-mod.txt"), modified); 

Jelas dan mudah digunakan. Jika perlu, Anda bisa meneruskan Charset ke readString , dan di writeString juga sebuah array OpenOptions .


Kosong I / O dengan 'Reader::nullReader' , dll.


Perlu OutputStream yang tidak menulis di mana pun? Atau InputStream kosong? Bagaimana dengan Reader dan Writer yang tidak melakukan apa-apa? Java 11 memiliki semuanya:


 InputStream input = InputStream.nullInputStream(); OutputStream output = OutputStream.nullOutputStream(); Reader reader = Reader.nullReader(); Writer writer = Writer.nullWriter(); 

(Catatan Penerjemah: di commons-io kelas-kelas ini telah ada sejak sekitar 2014).


Namun, saya terkejut - apakah null benar-benar awalan terbaik? Saya tidak suka bagaimana ini berarti "ketidakhadiran disengaja" ... Mungkin lebih baik menggunakan noOp ? (Catatan Penerjemah: kemungkinan besar awalan ini dipilih karena penggunaan umum /dev/null .)


{ } ~> [ ] dengan 'Collection::toArray'


Bagaimana Anda mengonversi koleksi ke array?


 //  Java 11 List<String> list = /*...*/; Object[] objects = list.toArray(); String[] strings_0 = list.toArray(new String[0]); String[] strings_size = list.toArray(new String[list.size()]); 

Opsi pertama, objects , kehilangan semua informasi tentang jenis, sehingga sedang dalam penerbangan. Bagaimana dengan yang lainnya? Keduanya tebal, tetapi yang pertama lebih pendek. Yang terakhir menciptakan array ukuran yang diperlukan, sehingga terlihat lebih produktif (yaitu, "tampaknya lebih produktif", lihat kredibilitas ). Tetapi apakah ini benar-benar lebih produktif? Tidak, sebaliknya, itu lebih lambat (saat ini).


Tetapi mengapa saya harus peduli tentang ini? Apakah tidak ada cara yang lebih baik untuk melakukan ini? Di Jawa 11 ada:


 String[] strings_fun = list.toArray(String[]::new); 

Varian baru Collection::toArray , yang menerima IntFunction<T[]> , yaitu. fungsi yang menerima ukuran array dan mengembalikan array ukuran yang diperlukan. Ini dapat secara singkat dinyatakan sebagai referensi ke konstruktor dari bentuk T[]::new (untuk T terkenal).


Fakta menarik, implementasi default Collection#toArray(IntFunction<T[]>) selalu melewati 0 ke generator array. Pada awalnya, saya memutuskan bahwa solusi ini didasarkan pada kinerja terbaik untuk array dengan panjang nol, tetapi sekarang saya berpikir bahwa alasannya mungkin karena beberapa koleksi, menghitung ukuran bisa menjadi operasi yang sangat mahal dan Anda tidak boleh menggunakan pendekatan ini dalam implementasi default Collection . Namun, implementasi koleksi tertentu, seperti ArrayList , dapat mengubah pendekatan ini, tetapi mereka tidak berubah di Java 11. Tidak layak, kurasa.


Tidak ada pemeriksaan dengan 'Optional::isEmpty'


Dengan banyaknya penggunaan Optional , terutama dalam proyek-proyek besar, di mana Anda sering menghadapi pendekatan non- Optional , Anda sering harus memeriksa apakah itu memiliki nilai. Ada metode Optional::isPresent untuk ini. Tetapi sama sering Anda perlu tahu sebaliknya - bahwa Optional kosong. Tidak masalah, cukup gunakan !opt.isPresent() , kan?


Tentu saja, itu mungkin, tetapi hampir selalu lebih mudah untuk memahami logika if kondisinya tidak terbalik. Dan kadang-kadang Optional muncul di akhir rantai panjang panggilan dan jika Anda perlu memeriksanya secara gratis, maka Anda harus bertaruh ! di awal:


 public boolean needsToCompleteAddress(User user) { return !getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isPresent(); } 

Kalau begitu, lewati saja ! sangat mudah. Mulai dengan Java 11 ada opsi yang lebih baik:


 public boolean needsToCompleteAddress(User user) { return getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isEmpty(); } 

Menolak predikat dengan 'Predicate::not'


Berbicara tentang pembalikan ... Antarmuka Predicate memiliki negate instance negate : mengembalikan predikat baru yang melakukan pemeriksaan yang sama, tetapi membalikkan hasilnya. Sayangnya, saya jarang berhasil menggunakannya ...


 //      Stream .of("a", "b", "", "c") // ,  ~>        .filter(s -> !s.isBlank()) //          ~>  .filter((String::isBlank).negate()) // ,  ~>       .filter(((Predicate<String>) String::isBlank).negate()) .forEach(System.out::println); 

Masalahnya adalah saya jarang memiliki akses ke instance Predicate . Lebih sering, saya ingin mendapatkan instance seperti itu melalui tautan ke suatu metode (dan membalikkannya), tetapi agar ini berfungsi, kompiler harus tahu apa yang harus dibawa ke metode referensi - tanpa itu, ia tidak dapat melakukan apa-apa. Dan ini persis apa yang terjadi jika Anda menggunakan (String::isBlank).negate() : kompiler tidak lagi tahu apa yang harus String::isBlank pada ini dan menyerah. Kasta yang ditentukan dengan benar memperbaikinya, tetapi berapa biayanya?


Meski ada solusi sederhana. Jangan gunakan negate instance negate , tetapi gunakan metode statis baru Predicate.not(Predicate<T>) dari Java 11:


 Stream .of("a", "b", "", "c") //   `java.util.function.Predicate.not` .filter(not(String::isBlank)) .forEach(System.out::println); 

Sudah lebih baik!


Ekspresi reguler sebagai predikat dengan 'Pattern::asMatchPredicate'


Apakah ada ekspresi reguler? Perlu memfilter data? Bagaimana dengan ini:


 Pattern nonWordCharacter = Pattern.compile("\\W"); Stream .of("Metallica", "Motörhead") .filter(nonWordCharacter.asPredicate()) .forEach(System.out::println); 

Saya sangat senang menemukan metode ini! Perlu ditambahkan bahwa ini adalah metode dari Java 8. Ups, saya melewatkannya saat itu. Java 11 menambahkan metode serupa lainnya: Pattern::asMatchPredicate . Apa bedanya?


  • asPredicate memeriksa apakah string atau bagian dari string cocok dengan pola (berfungsi seperti s -> this.matcher(s).find() )
  • asMatchPredicate memeriksa bahwa seluruh string cocok dengan pola (berfungsi seperti s -> this.matcher(s).matches() )

Misalnya, kami memiliki ekspresi reguler yang memeriksa nomor telepon, tetapi tidak mengandung ^ dan $ untuk melacak awal dan akhir suatu baris. Maka kode berikut tidak akan berfungsi seperti yang Anda harapkan:


 prospectivePhoneNumbers .stream() .filter(phoneNumberPatter.asPredicate()) .forEach(this::robocall); 

Apakah Anda memperhatikan kesalahan? Baris seperti " -152 ? +1-202-456-1414" akan difilter, karena berisi nomor telepon yang valid. Di sisi lain, Pattern::asMatchPredicate tidak akan mengizinkan ini, karena seluruh string tidak lagi cocok dengan pola.


Tes diri


Berikut ini ikhtisar dari semua sebelas mutiara - apakah Anda masih ingat apa yang dilakukan setiap metode? Jika demikian, Anda telah lulus ujian.


  • dalam String :
    • Stream<String> lines()
    • String strip()
    • String stripLeading()
    • String stripTrailing()
    • boolean isBlank()
    • String repeat(int)
  • di Path :
    • static Path of(String, String...)
    • static Path of(URI)
  • dalam Files :
    • String readString(Path) throws IOException
    • Path writeString(Path, CharSequence, OpenOption...) throws IOException
    • Path writeString(Path, CharSequence, Charset, OpenOption...) throws IOException
  • di InputStream : static InputStream nullInputStream()
  • dalam OutputStream : OutputStream static OutputStream nullOutputStream()
  • di Reader : static Reader nullReader()
  • dalam Writer : static Writer nullWriter()
  • dalam Collection : T[] toArray(IntFunction<T[]>)
  • dalam Optional : boolean isEmpty()
  • dalam Predicate : Predicate static Predicate<T> not(Predicate<T>)
  • dalam Pattern : Predicate<String> asMatchPredicate()

Bersenang-senang dengan Java 11!

Source: https://habr.com/ru/post/id441154/


All Articles