JavaRush /Java Blog /Random-ID /Panduan Java 8. 1 bagian.
ramhead
Level 13

Panduan Java 8. 1 bagian.

Dipublikasikan di grup Random-ID

“Jawa masih hidup – dan orang-orang mulai memahaminya.”

Selamat datang di perkenalan saya tentang Java 8. Panduan ini akan membawa Anda langkah demi langkah melalui semua fitur baru bahasa ini. Melalui contoh kode singkat dan sederhana, Anda akan mempelajari cara menggunakan metode default antarmuka , ekspresi lambda , metode referensi , dan anotasi berulang . Di akhir artikel, Anda akan memahami perubahan terbaru pada API seperti aliran, antarmuka fungsi, ekstensi asosiasi, dan API Tanggal baru. Tidak ada dinding teks yang membosankan - hanya sekumpulan cuplikan kode yang dikomentari. Menikmati!

Metode default untuk antarmuka

Java 8 memungkinkan kita untuk menambahkan metode non-abstrak yang diterapkan di antarmuka melalui penggunaan kata kunci default . Fitur ini juga dikenal sebagai metode penyuluhan . Berikut contoh pertama kita: interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } Selain metode abstrak hitung , antarmuka Rumus juga mendefinisikan metode default sqrt . Kelas yang mengimplementasikan antarmuka Formula hanya mengimplementasikan metode penghitungan abstrak . Metode sqrt default dapat langsung digunakan. Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0 Objek rumus diimplementasikan sebagai objek anonim. Kode ini cukup mengesankan: 6 baris kode untuk menghitung sqrt(a * 100) . Seperti yang akan kita lihat di bagian selanjutnya, ada cara yang lebih menarik untuk mengimplementasikan objek metode tunggal di Java 8.

Ekspresi Lambda

Mari kita mulai dengan contoh sederhana tentang cara mengurutkan array string di Java versi awal: Metode pembantu statistik Collections.sort mengambil daftar dan Komparator untuk mengurutkan elemen dari daftar yang diberikan. Yang sering terjadi adalah Anda membuat pembanding anonim dan meneruskannya ke metode pengurutan. Alih-alih membuat objek anonim sepanjang waktu, Java 8 memberi Anda kemampuan untuk menggunakan lebih sedikit sintaksis, ekspresi lambda : Seperti yang Anda lihat, kodenya jauh lebih pendek dan lebih mudah dibaca. Namun di sini menjadi lebih singkat lagi: Untuk metode satu baris, Anda bisa menghilangkan {} kurung kurawal dan return kata kunci . Namun di sinilah kodenya menjadi lebih pendek: kompiler Java mengetahui tipe parameternya, jadi Anda juga bisa mengabaikannya. Sekarang mari selami lebih dalam bagaimana ekspresi lambda dapat digunakan dalam kehidupan nyata. List names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator () { @Override public int compare(String a, String b) { return b.compareTo(a); } }); Collections.sort(names, (String a, String b) -> { return b.compareTo(a); }); Collections.sort(names, (String a, String b) -> b.compareTo(a)); Collections.sort(names, (a, b) -> b.compareTo(a));

Antarmuka Fungsional

Bagaimana ekspresi lambda cocok dengan sistem tipe Java? Setiap lambda sesuai dengan tipe tertentu yang ditentukan oleh antarmuka. Dan apa yang disebut antarmuka fungsional harus berisi satu metode abstrak yang dideklarasikan. Setiap ekspresi lambda dari tipe tertentu akan sesuai dengan metode abstrak ini. Karena metode default bukan metode abstrak, Anda bebas menambahkan metode default ke antarmuka fungsional Anda. Kita dapat menggunakan antarmuka sembarang sebagai ekspresi lambda, asalkan antarmuka tersebut hanya berisi satu metode abstrak. Untuk memastikan antarmuka Anda memenuhi ketentuan ini, Anda harus menambahkan anotasi @FunctionalInterface . Kompiler akan diberitahu melalui anotasi ini bahwa antarmuka harus berisi hanya satu metode, dan jika ia menemukan metode abstrak kedua di antarmuka ini, ia akan menimbulkan kesalahan. Contoh: Ingatlah bahwa kode ini juga akan valid meskipun anotasi @FunctionalInterface belum dideklarasikan. @FunctionalInterface interface Converter { T convert(F from); } Converter converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123

Referensi ke metode dan konstruktor

Contoh di atas dapat disederhanakan lebih lanjut dengan menggunakan referensi metode statistik: Java 8 memungkinkan Anda meneruskan referensi ke metode dan konstruktor menggunakan :: kata kunci simbol . Contoh di atas menunjukkan bagaimana metode statistik dapat digunakan. Tapi kita juga bisa mereferensikan metode pada objek: Mari kita lihat bagaimana penggunaan :: bekerja untuk konstruktor. Pertama, mari kita definisikan contoh dengan konstruktor yang berbeda: Selanjutnya, kita mendefinisikan antarmuka pabrik PersonFactory untuk membuat objek person baru : Converter converter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123 class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } } Something something = new Something(); Converter converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J" class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } interface PersonFactory

{ P create(String firstName, String lastName); } Daripada mengimplementasikan pabrik secara manual, kami menyatukan semuanya menggunakan referensi konstruktor: Kami membuat referensi ke konstruktor kelas Person melalui Person::new . Kompiler Java akan secara otomatis memanggil konstruktor yang sesuai dengan membandingkan tanda tangan konstruktor dengan tanda tangan metode PersonFactory.create . PersonFactory personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");

wilayah Lambda

Mengorganisir akses ke variabel lingkup luar dari ekspresi lambda mirip dengan mengakses dari objek anonim. Anda dapat mengakses variabel akhir dari lingkup lokal, serta bidang instan dan variabel agregat.
Mengakses Variabel Lokal
Kita dapat membaca variabel lokal dengan pengubah final dari lingkup ekspresi lambda: Namun tidak seperti objek anonim, variabel tidak perlu dideklarasikan final agar dapat diakses dari ekspresi lambda . Kode ini juga benar: Namun, variabel num harus tetap tidak berubah, mis. menjadi final implisit untuk kompilasi kode. Kode berikut tidak dapat dikompilasi: Perubahan pada num dalam ekspresi lambda juga tidak diperbolehkan. final int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3 int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3 int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); num = 3;
Mengakses Bidang Instance dan Variabel Statistik
Tidak seperti variabel lokal, kita dapat membaca dan memodifikasi kolom instance dan variabel statistik di dalam ekspresi lambda. Kami mengetahui perilaku ini dari objek anonim. class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
Akses ke metode antarmuka default
Ingat contoh contoh rumus dari bagian pertama? Antarmuka Rumus mendefinisikan metode sqrt default yang dapat diakses dari setiap contoh rumus , termasuk objek anonim. Ini tidak berfungsi dengan ekspresi lambda. Metode default tidak dapat diakses di dalam ekspresi lambda. Kode berikut tidak dapat dikompilasi: Formula formula = (a) -> sqrt( a * 100);

Antarmuka fungsional bawaan

API JDK 1.8 berisi banyak antarmuka fungsional bawaan. Beberapa di antaranya sudah terkenal dari versi Java sebelumnya. Misalnya Comparator atau Runnable . Antarmuka ini diperluas untuk menyertakan dukungan lambda menggunakan anotasi @FunctionalInterface . Namun Java 8 API juga penuh dengan antarmuka fungsional baru yang akan membuat hidup Anda lebih mudah. Beberapa antarmuka ini terkenal dari perpustakaan Guava Google . Meskipun Anda sudah familiar dengan perpustakaan ini, Anda harus melihat lebih dekat bagaimana antarmuka ini diperluas, dengan beberapa metode ekstensi yang berguna.
Predikat
Predikat adalah fungsi Boolean dengan satu argumen. Antarmuka berisi berbagai metode default untuk membuat ekspresi logika kompleks (dan, atau, meniadakan) menggunakan predikat Predicate predicate = (s) -> s.length() > 0; predicate.test("foo"); // true predicate.negate().test("foo"); // false Predicate nonNull = Objects::nonNull; Predicate isNull = Objects::isNull; Predicate isEmpty = String::isEmpty; Predicate isNotEmpty = isEmpty.negate();
Fungsi
Fungsi mengambil satu argumen dan menghasilkan hasil. Metode default dapat digunakan untuk menggabungkan beberapa fungsi menjadi satu rantai (compose, andThen). Function toInteger = Integer::valueOf; Function backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
Pemasok
Pemasok mengembalikan hasil (contoh) dari satu jenis atau lainnya. Berbeda dengan fungsi, penyedia tidak menerima argumen. Supplier personSupplier = Person::new; personSupplier.get(); // new Person
Konsumen
Konsumen mewakili metode antarmuka dengan satu argumen. Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
Pembanding
Pembanding kita ketahui dari versi Java sebelumnya. Java 8 memungkinkan Anda menambahkan berbagai metode default ke antarmuka. Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0
Opsional
Antarmuka Opsional tidak berfungsi, tetapi merupakan utilitas hebat untuk mencegah NullPointerException . Ini adalah poin penting untuk bagian selanjutnya, jadi mari kita lihat sekilas cara kerja antarmuka ini. Antarmuka Opsional adalah wadah sederhana untuk nilai yang bisa berupa null atau non-null. Bayangkan suatu metode dapat mengembalikan suatu nilai atau tidak sama sekali. Di Java 8, alih-alih mengembalikan null , Anda mengembalikan instance Optional . Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0

Sungai kecil

java.util.Stream adalah urutan elemen tempat satu atau banyak operasi dilakukan. Setiap operasi Stream bersifat perantara atau terminal. Operasi terminal mengembalikan hasil dari tipe tertentu, sementara operasi perantara mengembalikan objek aliran itu sendiri, sehingga memungkinkan terciptanya rangkaian pemanggilan metode. Stream adalah antarmuka, seperti java.util.Collection untuk daftar dan set (peta tidak didukung). Setiap operasi Stream dapat dijalankan secara berurutan atau paralel. Mari kita lihat cara kerja streaming. Pertama, kita akan membuat kode contoh dalam bentuk daftar string: Koleksi di Java 8 ditingkatkan sehingga Anda dapat membuat aliran cukup sederhana dengan memanggil Collection.stream() atau Collection.parallelStream() . Bagian selanjutnya akan menjelaskan operasi aliran yang paling penting dan sederhana. List stringCollection = new ArrayList<>(); stringCollection.add("ddd2"); stringCollection.add("aaa2"); stringCollection.add("bbb1"); stringCollection.add("aaa1"); stringCollection.add("bbb3"); stringCollection.add("ccc"); stringCollection.add("bbb2"); stringCollection.add("ddd1");
Saring
Filter menerima predikat untuk memfilter semua elemen aliran. Operasi ini bersifat perantara, yang memungkinkan kita memanggil operasi aliran lainnya (misalnya forEach) pada hasil yang dihasilkan (difilter). ForEach menerima operasi yang akan dilakukan pada setiap elemen aliran yang sudah difilter. ForEach adalah operasi terminal. Selanjutnya, memanggil operasi lain tidak mungkin dilakukan. stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"
Diurutkan
Diurutkan adalah operasi perantara yang mengembalikan representasi aliran yang diurutkan. Elemen diurutkan dalam urutan yang benar kecuali Anda menentukan Comparator Anda . stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2" Ingatlah bahwa sortir membuat representasi aliran yang diurutkan tanpa memengaruhi koleksi itu sendiri. Urutan elemen stringCollection tetap tidak tersentuh: System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Peta
Operasi peta perantara mengubah setiap elemen menjadi objek lain menggunakan fungsi yang dihasilkan. Contoh berikut mengonversi setiap string menjadi string huruf besar. Namun Anda juga dapat menggunakan map untuk mengonversi setiap objek ke tipe berbeda. Jenis objek aliran yang dihasilkan bergantung pada jenis fungsi yang Anda teruskan ke peta. stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Cocok
Berbagai operasi pencocokan dapat digunakan untuk menguji kebenaran predikat tertentu dalam relasi aliran. Semua operasi pencocokan bersifat terminal dan mengembalikan hasil Boolean. boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true
Menghitung
Hitungan adalah operasi terminal yang mengembalikan jumlah elemen aliran sebagai long . long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3
Mengurangi
Ini adalah operasi terminal yang memperpendek elemen aliran menggunakan fungsi yang diteruskan. Hasilnya akan menjadi Opsional yang berisi nilai yang dipersingkat. Optional reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Aliran Paralel

Seperti disebutkan di atas, aliran bisa berurutan atau paralel. Operasi aliran berurutan dilakukan pada thread serial, sedangkan operasi aliran paralel dilakukan pada beberapa thread paralel. Contoh berikut menunjukkan cara mudah meningkatkan kinerja menggunakan aliran paralel. Pertama, mari kita buat daftar besar elemen unik: Sekarang kita akan menentukan waktu yang dihabiskan untuk menyortir aliran koleksi ini. int max = 1000000; List values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }
Aliran serial
long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("sequential sort took: %d ms", millis)); // sequential sort took: 899 ms
Aliran paralel
long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); // parallel sort took: 472 ms Seperti yang Anda lihat, kedua fragmen hampir identik, tetapi pengurutan paralel 50% lebih cepat. Yang Anda perlukan hanyalah mengubah stream() menjadi parallelStream() .

Peta

Seperti yang telah disebutkan, peta tidak mendukung aliran. Sebaliknya, peta mulai mendukung metode baru dan berguna untuk memecahkan masalah-masalah umum. Kode di atas harus intuitif: putIfAbsent memperingatkan kita agar tidak menulis pemeriksaan nol tambahan. forEach menerima fungsi untuk dieksekusi untuk setiap nilai peta. Contoh ini menunjukkan bagaimana operasi dilakukan pada nilai peta menggunakan fungsi: Selanjutnya, kita akan mempelajari cara menghapus entri untuk kunci tertentu hanya jika kunci tersebut dipetakan ke nilai tertentu: Metode bagus lainnya: Menggabungkan entri peta cukup mudah: Menggabungkan akan memasukkan kunci/nilai ke dalam map , jika tidak ada entri untuk kunci yang diberikan, atau fungsi penggabungan akan dipanggil, yang akan mengubah nilai entri yang ada. Map map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.putIfAbsent(i, "val" + i); } map.forEach((id, val) -> System.out.println(val)); map.computeIfPresent(3, (num, val) -> val + num); map.get(3); // val33 map.computeIfPresent(9, (num, val) -> null); map.containsKey(9); // false map.computeIfAbsent(23, num -> "val" + num); map.containsKey(23); // true map.computeIfAbsent(3, num -> "bam"); map.get(3); // val33 map.remove(3, "val3"); map.get(3); // val33 map.remove(3, "val33"); map.get(3); // null map.getOrDefault(42, "not found"); // not found map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); map.get(9); // val9 map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); map.get(9); // val9concat
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION