API yang akhirnya layak ditingkatkan dari Java 8. Bagian 1

Java 8 sejauh ini merupakan versi Java yang paling populer dan akan tetap bersamanya untuk beberapa waktu. Namun, lima versi baru Java telah dirilis sejak itu (9, 10, 11, 12, 13), dan segera Java 14 lainnya akan dirilis. Sejumlah besar fitur baru telah muncul di versi-versi baru ini. Misalnya, jika Anda menghitung dalam JEP, maka total 141 diterapkan:



Namun, dalam seri artikel ini tidak akan ada daftar JEP kering. Sebagai gantinya, saya hanya ingin berbicara tentang API menarik yang telah muncul di versi baru. Setiap artikel akan berisi 10 API. Dalam pilihan dan urutan API ini, tidak akan ada logika dan keteraturan tertentu. Ini hanya akan menjadi 10 API acak, bukan TOP 10 dan tanpa memilah dari API yang paling penting ke yang paling tidak penting. Mari kita mulai.


1. Metode Objects.requireNonNullElse() dan Objects.requireNonNullElseGet()


Diperkenalkan di: Jawa 9


Kita mulai daftar kita dengan dua metode yang sangat sederhana, tetapi sangat berguna di kelas java.util.Objects : requireNonNullElse() dan requireNonNullElseGet() . Metode ini memungkinkan Anda untuk mengembalikan objek yang ditransmisikan, jika tidak null , dan jika itu null , maka kembalikan objek secara default. Sebagai contoh:


 class MyCoder { private final Charset charset; MyCoder(Charset charset) { this.charset = Objects.requireNonNullElse( charset, StandardCharsets.UTF_8); } } 

requireNonNullElseGet() tidak lebih dari versi malas dari requireNonNullElse() . Mungkin berguna jika menghitung argumen default mahal:


 class MyCoder { private final Charset charset; MyCoder(Charset charset) { this.charset = Objects.requireNonNullElseGet( charset, MyCoder::defaultCharset); } private static Charset defaultCharset() { // long operation... } } 

Ya, tentu saja, dalam kedua kasus, seseorang dapat dengan mudah melakukan tanpa fungsi-fungsi ini, misalnya, menggunakan operator ternary biasa atau Optional , tetapi masih menggunakan fungsi khusus membuat kode sedikit lebih pendek dan lebih bersih. Dan jika Anda menggunakan impor statis dan menulis cukup requireNonNullElse() alih-alih Objects.requireNonNullElse() , maka kode dapat dikurangi lebih banyak lagi.



2. Metode pabrik mengembalikan koleksi yang tidak berubah


Diperkenalkan di: Jawa 9


Jika dua metode sebelumnya hanya kosmetik, maka metode pengumpulan statis dapat benar-benar mengurangi kode dan bahkan meningkatkan keamanannya. Ini adalah metode berikut yang diperkenalkan di Jawa 9:



Ke daftar yang sama, Anda bisa menambahkan metode Map.entry(K k, V v) menyertainya, yang membuat Entry dari kunci dan nilai, serta menyalin metode untuk koleksi yang muncul di Java 10:



Metode pabrik statis memungkinkan Anda untuk membuat koleksi abadi dan menginisialisasi dalam satu tindakan:


 List<String> imageExtensions = List.of("bmp", "jpg", "png", "gif"); 

Jika Anda tidak menggunakan pustaka pihak ketiga, maka kode yang serupa di Java 8 terlihat jauh lebih rumit:


 List<String> imageExtensions = Collections.unmodifiableList( Arrays.asList("bmp", "jpg", "png", "gif")); 

Dan dalam kasus Set atau Map masih lebih menyedihkan, karena tidak ada analog dari Arrays.asList() untuk Set dan Map .


Ketangkasan seperti itu memprovokasi banyak orang yang menulis di Java 8 untuk sepenuhnya meninggalkan koleksi abadi dan selalu menggunakan ArrayList , HashSet dan HashMap , bahkan di mana arti dari koleksi abadi diperlukan. Akibatnya, ini mematahkan konsep immutable-by-default dan mengurangi keamanan kode.


Jika Anda akhirnya memutakhirkan dari Java 8, maka bekerja dengan koleksi tidak berubah menjadi lebih mudah dan lebih menyenangkan berkat metode pabrik.



3. Files.readString() dan Files.writeString()


Diperkenalkan di: Jawa 11


Java selalu dikenal karena pengenalan metode siap pakai untuk operasi yang sering dilakukan. Misalnya, untuk salah satu operasi paling populer dalam pemrograman, membaca file, untuk waktu yang sangat lama tidak ada metode yang sudah jadi. Hanya 15 tahun setelah rilis Java 1.0, NIO muncul, di mana metode Files.readAllBytes() diperkenalkan untuk membaca file ke dalam array byte.


Tetapi ini masih belum cukup, karena orang sering harus bekerja dengan file teks dan untuk ini Anda perlu membaca string dari file, bukan byte. Oleh karena itu, di Java 8, metode Files.readAllLines() ditambahkan, mengembalikan List<String> .


Namun, ini tidak cukup, karena orang bertanya betapa mudahnya untuk membaca seluruh file sebagai satu baris. Sebagai hasilnya, untuk melengkapi gambar di Java 11, metode Files.readString() ditunggu-tunggu Files.readString() , sehingga akhirnya menutup pertanyaan ini. Anehnya, jika metode serupa hadir dalam banyak bahasa lain sejak awal, maka Java membutuhkan waktu lebih dari 20 tahun untuk melakukan ini.


Bersama dengan readString() tentu saja, metode writeString() simetris juga diperkenalkan. Metode ini juga memiliki kelebihan yang memungkinkan Anda untuk menentukan Charset . Bersama-sama, semua ini membuat bekerja dengan file teks menjadi sangat nyaman. Contoh:


 /**        */ private void reencodeFile(Path path, Charset from, Charset to) throws IOException { String content = Files.readString(path, from); Files.writeString(path, content, to); } 


4. Optional.ifPresentOrElse() dan Optional.stream()


Diperkenalkan di: Jawa 9


Ketika Optional muncul di Java 8, mereka tidak memiliki cara yang nyaman untuk melakukan dua tindakan berbeda, tergantung pada apakah itu memiliki nilai atau tidak. Akibatnya, orang harus menggunakan rantai yang biasanya isPresent() dan get() :


 Optional<String> opt = ... if (opt.isPresent()) { log.info("Value = " + opt.get()); } else { log.error("Empty"); } 

Atau Anda masih bisa mengelak dengan cara ini:


 Optional<String> opt = ... opt.ifPresent(str -> log.info("Value = " + str)); if (opt.isEmpty()) { log.error("Empty"); } 

Kedua opsi tersebut tidak sempurna. Tetapi, dimulai dengan Java 9, ini dapat dilakukan secara elegan menggunakan metode Optional.ifPresentOrElse() :


 Optional<String> opt = ... opt.ifPresentOrElse( str -> log.info("Value = " + str), () -> log.error("Empty")); 

Metode baru lain yang menarik di Java 9 adalah Optional.stream() , yang mengembalikan Stream dari satu elemen jika nilainya hadir, dan Stream kosong jika tidak. Metode seperti itu bisa sangat berguna dalam rantai dengan flatMap() . Misalnya, dalam contoh ini, sangat mudah untuk mendapatkan daftar semua nomor telepon perusahaan:


 class Employee { Optional<String> getPhoneNumber() { ... } } class Department { List<Employee> getEmployees() { ... } } class Company { List<Department> getDepartments() { ... } Set<String> getAllPhoneNumbers() { return getDepartments() .stream() .flatMap(d -> d.getEmployees().stream()) .flatMap(e -> e.getPhoneNumber().stream()) .collect(Collectors.toSet()); } } 

Di Java 8, Anda harus menulis sesuatu seperti:


 e -> e.getPhoneNumber().map(Stream::of).orElse(Stream.empty()) 

Terlihat tebal dan tidak mudah dibaca.



5. Process.pid() , Process.info() dan ProcessHandle


Diperkenalkan di: Jawa 9


Jika Anda masih dapat mengelola tanpa API sebelumnya, maka mengganti metode Process.pid() di Java 8 akan cukup bermasalah, terutama lintas platform. Metode ini mengembalikan ID proses asli:


 Process process = Runtime.getRuntime().exec("java -version"); System.out.println(process.pid()); 

Menggunakan metode Process.info() , Anda juga dapat menemukan informasi berguna tambahan tentang proses tersebut. Ini mengembalikan objek bertipe ProcessHandle.Info . Mari kita lihat apa yang dia kembalikan kepada kita untuk proses di atas:


 Process process = Runtime.getRuntime().exec("java -version"); ProcessHandle.Info info = process.info(); System.out.println("PID = " + process.pid()); System.out.println("User = " + info.user()); System.out.println("Command = " + info.command()); System.out.println("Args = " + info.arguments().map(Arrays::toString)); System.out.println("Command Line = " + info.commandLine()); System.out.println("Start Time = " + info.startInstant()); System.out.println("Total Time = " + info.totalCpuDuration()); 

Kesimpulan:


 PID = 174 User = Optional[orionll] Command = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java] Args = Optional[[-version]] Command Line = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java -version] Start Time = Optional[2020-01-24T05:54:25.680Z] Total Time = Optional[PT0.01S] 

Bagaimana jika prosesnya tidak dimulai dari proses Java saat ini? Untuk ini, ProcessHandle datang untuk ProcessHandle . Sebagai contoh, mari kita dapatkan semua informasi yang sama untuk proses saat ini menggunakan metode ProcessHandle.current() :


 ProcessHandle handle = ProcessHandle.current(); ProcessHandle.Info info = handle.info(); System.out.println("PID = " + handle.pid()); System.out.println("User = " + info.user()); System.out.println("Command = " + info.command()); System.out.println("Args = " + info.arguments().map(Arrays::toString)); System.out.println("Command Line = " + info.commandLine()); System.out.println("Start Time = " + info.startInstant()); System.out.println("Total Time = " + info.totalCpuDuration()); 

Kesimpulan:


 PID = 191 User = Optional[orionll] Command = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java] Args = Optional[[Main.java]] Command Line = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java Main.java] Start Time = Optional[2020-01-24T05:59:17.060Z] Total Time = Optional[PT1.56S] 

Untuk mendapatkan ProcessHandle untuk proses apa pun dengan PID-nya, Anda dapat menggunakan metode ProcessHandle.of() (ini akan mengembalikan Optional.empty jika prosesnya tidak ada).


Juga di ProcessHandle ada banyak metode menarik lainnya, misalnya, ProcessHandle.allProcesses() .



6. Metode String : isBlank() , strip() , stripLeading() , stripTrailing() , repeat() dan lines()


Diperkenalkan di: Jawa 11


Seluruh gunung metode yang berguna untuk string muncul di Jawa 11.


Metode String.isBlank() memungkinkan Anda untuk mengetahui apakah string hanya terdiri dari spasi putih:


 System.out.println(" \n\r\t".isBlank()); // true 

Metode String.stripLeading() , String.stripTrailing() dan String.strip() menghapus karakter spasi di awal baris, di ujung baris, atau di kedua ujungnya:


 String str = " \tHello, world!\t\n"; String str1 = str.stripLeading(); // "Hello, world!\t\n" String str2 = str.stripTrailing(); // " \tHello, world!" String str3 = str.strip(); // "Hello, world!" 

Perhatikan bahwa String.strip() tidak sama dengan String.trim() : yang kedua hanya menghapus karakter yang kodenya kurang dari atau sama dengan U + 0020, dan yang pertama juga menghilangkan spasi dari Unicode:


 System.out.println("str\u2000".strip()); // "str" System.out.println("str\u2000".trim()); // "str\u2000" 


Metode String.repeat() menggabungkan string itu sendiri n kali:


 System.out.print("Hello, world!\n".repeat(3)); 

Kesimpulan:


 Hello, world! Hello, world! Hello, world! 

Akhirnya, metode String.lines() memecah string menjadi beberapa baris. Selamat Tinggal String.split() , yang membuat orang selalu bingung, argumen mana yang digunakan untuk itu, baik "\n" , atau "\r" atau "\n\r" (pada kenyataannya, yang terbaik adalah menggunakan regular ekspresi "\R" , yang mencakup semua kombinasi). Selain itu, String.lines() seringkali lebih efisien karena mengembalikan baris dengan malas.


 System.out.println("line1\nline2\nline3\n" .lines() .map(String::toUpperCase) .collect(Collectors.joining("\n"))); 

Kesimpulan:


 LINE1 LINE2 LINE3 


7. String.indent()


Muncul di: Java 12


Mari mencairkan cerita kita dengan sesuatu yang segar yang muncul baru-baru ini. Meet: the String.indent() method, yang meningkatkan (atau mengurangi) indentasi setiap baris dalam baris yang diberikan oleh nilai yang ditentukan. Sebagai contoh:


 String body = "<h1>Title</h1>\n" + "<p>Hello, world!</p>"; System.out.println("<html>\n" + " <body>\n" + body.indent(4) + " </body>\n" + "</html>"); 

Kesimpulan:


 <html> <body> <h1>Title</h1> <p>Hello, world!</p> </body> </html> 

Perhatikan bahwa untuk baris terakhir, String.indent() sendiri memasukkan umpan baris, jadi kami tidak perlu menambahkan '\n' setelah body.indent(4) .


Tentu saja, metode seperti itu akan sangat menarik dalam kombinasi dengan blok teks ketika mereka menjadi stabil, tetapi tidak ada yang menghalangi kita untuk menggunakannya sekarang tanpa blok teks.



8. Metode Stream : takeWhile() , dropWhile() , iterate() dengan predikat dan ofNullable()


Diperkenalkan di: Jawa 9


Stream.takeWhile() mirip dengan Stream.limit() , tetapi membatasi Stream bukan oleh kuantitas, tetapi dengan predikat. Kebutuhan akan pemrograman seperti itu sangat sering muncul. Misalnya, jika kita perlu mendapatkan semua entri buku harian untuk tahun berjalan:


 [ { "date" : "2020-01-27", "text" : "..." }, { "date" : "2020-01-25", "text" : "..." }, { "date" : "2020-01-22", "text" : "..." }, { "date" : "2020-01-17", "text" : "..." }, { "date" : "2020-01-11", "text" : "..." }, { "date" : "2020-01-02", "text" : "..." }, { "date" : "2019-12-30", "text" : "..." }, { "date" : "2019-12-27", "text" : "..." }, ... ] 

Stream catatan hampir tidak ada habisnya, jadi filter() tidak dapat digunakan. Lalu takeWhile() datang untuk takeWhile() :


 getNotesStream() .takeWhile(note -> note.getDate().getYear() == 2020); 

Dan jika kita ingin mendapatkan catatan untuk 2019, maka kita bisa menggunakan dropWhile() :


 getNotesStream() .dropWhile(note -> note.getDate().getYear() == 2020) .takeWhile(note -> note.getDate().getYear() == 2019); 

Di Java 8, Stream.iterate() hanya bisa menghasilkan Stream tanpa batas. Namun di Java 9, metode ini memiliki yang membutuhkan predikat. Berkat ini, banyak loop sekarang dapat diganti dengan Stream :


 // Java 8 for (int i = 1; i < 100; i *= 2) { System.out.println(i); } 

 // Java 9+ IntStream .iterate(1, i -> i < 100, i -> i * 2) .forEach(System.out::println); 

Kedua versi ini mencetak semua derajat deuce yang tidak melebihi 100 :


 1 2 4 8 16 32 64 

Omong-omong, kode terakhir bisa ditulis ulang menggunakan takeWhile() :


 IntStream .iterate(1, i -> i * 2) .takeWhile(i -> i < 100) .forEach(System.out::println); 

Namun, opsi dengan tiga argumen iterate() masih lebih bersih (dan IntelliJ IDEA menyarankan untuk memperbaikinya kembali).


Akhirnya, Stream.ofNullable() mengembalikan Stream dengan satu elemen jika bukan null , dan Stream kosong jika itu null . Metode ini sempurna dalam contoh di atas dengan telepon perusahaan jika getPhoneNumber() akan mengembalikan String dibatalkan alih-alih Optional<String> :


 class Employee { String getPhoneNumber() { ... } } class Department { List<Employee> getEmployees() { ... } } class Company { List<Department> getDepartments() { ... } Set<String> getAllPhoneNumbers() { return getDepartments() .stream() .flatMap(d -> d.getEmployees().stream()) .flatMap(e -> Stream.ofNullable(e.getPhoneNumber())) .collect(Collectors.toSet()); } } 


9. Predicate.not()


Muncul di: Jawa 11


Metode ini tidak memperkenalkan sesuatu yang secara fundamental baru dan lebih kosmetik daripada fundamental. Meskipun demikian, kemampuan untuk sedikit mempersingkat kode selalu sangat menyenangkan. Menggunakan Predicate.not() lambdas yang memiliki negasi dapat diganti dengan referensi metode:


 Files.lines(path) .filter(str -> !str.isEmpty()) .forEach(System.out::println); 

Dan sekarang menggunakan not() :


 Files.lines(path) .filter(not(String::isEmpty)) .forEach(System.out::println); 

Ya, penghematannya tidak terlalu besar, dan jika Anda menggunakan s -> !s.isEmpty() , maka jumlah karakter, sebaliknya, menjadi lebih besar. Tetapi bahkan dalam kasus ini, saya masih lebih suka opsi kedua, karena lebih deklaratif dan tidak menggunakan variabel di dalamnya, yang berarti bahwa namespace tidak berantakan.



10. Pembersih


Muncul di: Java 9


Saya ingin mengakhiri cerita hari ini dengan API baru yang menarik yang muncul di Jawa 9 dan berfungsi untuk membersihkan sumber daya sebelum dibuang oleh pengumpul sampah. Cleaner adalah pengganti yang aman untuk metode Object.finalize() , yang itu sendiri menjadi usang di Jawa 9.


Menggunakan Cleaner Anda dapat mendaftarkan pembersihan sumber daya yang akan terjadi jika Anda lupa melakukannya secara eksplisit (misalnya, Anda lupa memanggil metode close() atau Anda tidak menggunakan try-with-resources ). Berikut adalah contoh sumber daya abstrak yang tindakan pembersihannya terdaftar di konstruktor:


 public class Resource implements Closeable { private static final Cleaner CLEANER = Cleaner.create(); private final Cleaner.Cleanable cleanable; public Resource() { cleanable = CLEANER.register(this, () -> { //   // (,  ) }); } @Override public void close() { cleanable.clean(); } } 

Dengan cara yang baik, pengguna harus membuat sumber daya seperti itu di blok try :


 try (var resource = new Resource()) { //   } 

Namun, mungkin ada pengguna yang lupa melakukan ini dan menulis hanya var resource = new Resource() . Dalam kasus seperti itu, pembersihan tidak akan dilakukan segera, tetapi akan dipanggil nanti dalam salah satu siklus pengumpulan sampah berikut. Itu lebih baik daripada tidak sama sekali.


Jika Anda ingin mempelajari Cleaner lebih baik dan mencari tahu mengapa Anda tidak boleh menggunakan finalize() , maka saya sarankan Anda mendengarkan ceramah saya tentang topik ini.



Kesimpulan


Jawa tidak tinggal diam dan secara bertahap berkembang. Saat Anda duduk di Java 8, dengan setiap rilis ada semakin banyak API baru yang menarik. Hari ini kami meninjau 10 API semacam itu. Dan Anda dapat menggunakan semuanya jika Anda akhirnya memutuskan untuk bermigrasi dari Java 8.


Lain kali kita akan melihat 10 API baru.


Jika Anda tidak ingin melewatkan bagian selanjutnya, saya sarankan Anda berlangganan saluran Telegram saya , tempat saya juga menerbitkan berita Java.

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


All Articles