JavaRush /Blog Java /Random-MS /Panduan Java 8. 1 bahagian.
ramhead
Tahap

Panduan Java 8. 1 bahagian.

Diterbitkan dalam kumpulan

"Jawa masih hidup - dan orang ramai mula memahaminya."

Selamat datang ke pengenalan saya kepada Java 8. Panduan ini akan membawa anda langkah demi langkah melalui semua ciri baharu bahasa tersebut. Melalui contoh kod ringkas dan ringkas, anda akan belajar cara menggunakan kaedah lalai antara muka , ungkapan lambda , kaedah rujukan dan anotasi boleh berulang . Menjelang akhir artikel, anda akan biasa dengan perubahan terkini pada API seperti strim, antara muka fungsi, sambungan persatuan dan API Tarikh baharu. Tiada dinding teks yang membosankan - hanya sekumpulan coretan kod yang diulas. Nikmati!

Kaedah lalai untuk antara muka

Java 8 membolehkan kami menambah kaedah bukan abstrak yang dilaksanakan dalam antara muka melalui penggunaan kata kunci lalai . Ciri ini juga dikenali sebagai kaedah sambungan . Berikut ialah contoh pertama kami: interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } Selain kaedah abstrak hitung , antara muka Formula juga mentakrifkan kaedah lalai sqrt . Kelas yang melaksanakan antara muka Formula hanya melaksanakan kaedah pengiraan abstrak . Kaedah sqrt lalai boleh digunakan terus dari kotak. 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 formula dilaksanakan sebagai objek tanpa nama. Kod ini agak mengagumkan: 6 baris kod untuk hanya mengira sqrt(a * 100) . Seperti yang akan kita lihat dalam bahagian seterusnya, terdapat cara yang lebih menarik untuk melaksanakan objek kaedah tunggal dalam Java 8.

Ungkapan Lambda

Mari kita mulakan dengan contoh mudah tentang cara mengisih tatasusunan rentetan dalam versi awal Java: Kaedah pembantu statistik Collections.sort mengambil senarai dan Pembanding untuk mengisih elemen senarai yang diberikan. Perkara yang sering berlaku ialah anda membuat pembanding tanpa nama dan menyerahkannya untuk mengisih kaedah. Daripada mencipta objek tanpa nama sepanjang masa, Java 8 memberi anda keupayaan untuk menggunakan sintaks yang lebih sedikit, ungkapan lambda : Seperti yang anda lihat, kod itu lebih pendek dan lebih mudah dibaca. Tetapi di sini ia menjadi lebih pendek: Untuk kaedah satu baris, anda boleh menyingkirkan {} pendakap kerinting dan kata kunci kembali . Tetapi di sinilah kod menjadi lebih pendek: pengkompil Java mengetahui jenis parameter, jadi anda boleh meninggalkannya juga. Sekarang mari kita selami lebih mendalam tentang cara ungkapan lambda boleh digunakan dalam kehidupan sebenar. 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));

Antara Muka Berfungsi

Bagaimanakah ungkapan lambda sesuai dengan sistem jenis Java? Setiap lambda sepadan dengan jenis tertentu yang ditakrifkan oleh antara muka. Dan antara muka berfungsi yang dipanggil mesti mengandungi tepat satu kaedah abstrak yang diisytiharkan. Setiap ungkapan lambda bagi jenis tertentu akan sepadan dengan kaedah abstrak ini. Memandangkan kaedah lalai bukan kaedah abstrak, anda bebas untuk menambah kaedah lalai pada antara muka berfungsi anda. Kita boleh menggunakan antara muka sewenang-wenangnya sebagai ungkapan lambda, dengan syarat antara muka mengandungi hanya satu kaedah abstrak. Untuk memastikan antara muka anda memenuhi syarat ini, anda mesti menambah anotasi @FunctionalInterface . Pengkompil akan dimaklumkan melalui anotasi ini bahawa antara muka mesti mengandungi hanya satu kaedah dan jika ia menemui kaedah abstrak kedua dalam antara muka ini, ia akan menimbulkan ralat. Contoh: Perlu diingat bahawa kod ini juga akan sah walaupun anotasi @FunctionalInterface belum diisytiharkan. @FunctionalInterface interface Converter { T convert(F from); } Converter converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123

Rujukan kepada kaedah dan pembina

Contoh di atas boleh dipermudahkan lagi dengan menggunakan rujukan kaedah statistik: Java 8 membolehkan anda menghantar rujukan kepada kaedah dan pembina menggunakan simbol kata kunci :: . Contoh di atas menunjukkan bagaimana kaedah statistik boleh digunakan. Tetapi kita juga boleh merujuk kaedah pada objek: Mari kita lihat cara menggunakan :: berfungsi untuk pembina. Mula-mula, mari kita takrifkan contoh dengan pembina yang berbeza: Seterusnya, kita takrifkan antara muka kilang PersonFactory untuk mencipta objek orang baharu : 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 melaksanakan kilang secara manual, kami mengikat semuanya bersama-sama menggunakan rujukan pembina: Kami membuat rujukan kepada pembina kelas Person melalui Person::new . Pengkompil Java secara automatik akan memanggil pembina yang sesuai dengan membandingkan tandatangan pembina dengan tandatangan kaedah PersonFactory.create . PersonFactory personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");

rantau Lambda

Mengatur akses kepada pembolehubah skop luar daripada ungkapan lambda adalah serupa dengan mengakses daripada objek tanpa nama. Anda boleh mengakses pembolehubah akhir dari skop setempat, serta medan contoh dan pembolehubah agregat.
Mengakses Pembolehubah Setempat
Kita boleh membaca pembolehubah tempatan dengan pengubah suai akhir daripada skop ungkapan lambda: Tetapi tidak seperti objek tanpa nama, pembolehubah tidak perlu diisytiharkan muktamad untuk boleh diakses daripada ungkapan lambda . Kod ini juga betul: Walau bagaimanapun, pembolehubah num mesti kekal tidak berubah, i.e. menjadi muktamad tersirat untuk penyusunan kod. Kod berikut tidak akan disusun: Perubahan kepada num dalam ungkapan lambda juga tidak dibenarkan. 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 Medan Contoh dan Pembolehubah Statistik
Tidak seperti pembolehubah tempatan, kita boleh membaca dan menukar medan contoh dan pembolehubah statistik dalam ungkapan lambda. Kami tahu tingkah laku ini daripada objek tanpa nama. 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 kepada kaedah lalai antara muka
Ingat contoh dengan contoh formula dari bahagian pertama? Antara muka Formula mentakrifkan kaedah sqrt lalai yang boleh diakses daripada setiap contoh formula , termasuk objek tanpa nama. Ini tidak berfungsi dengan ungkapan lambda. Kaedah lalai tidak boleh diakses di dalam ungkapan lambda. Kod berikut tidak disusun: Formula formula = (a) -> sqrt( a * 100);

Antara muka berfungsi terbina dalam

API JDK 1.8 mengandungi banyak antara muka berfungsi terbina dalam. Sebahagian daripada mereka terkenal daripada versi Java sebelumnya. Contohnya Comparator atau Runnable . Antara muka ini dilanjutkan untuk memasukkan sokongan lambda menggunakan anotasi @FunctionalInterface . Tetapi API Java 8 juga penuh dengan antara muka berfungsi baharu yang akan menjadikan hidup anda lebih mudah. Beberapa antara muka ini terkenal daripada perpustakaan Guava Google . Walaupun anda sudah biasa dengan perpustakaan ini, anda harus melihat dengan lebih dekat cara antara muka ini dilanjutkan, dengan beberapa kaedah sambungan yang berguna.
Predikat
Predikat ialah fungsi Boolean dengan satu hujah. Antara muka mengandungi pelbagai kaedah lalai untuk mencipta ungkapan logik yang kompleks (dan, atau, menafikan) 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 hujah dan menghasilkan keputusan. Kaedah lalai boleh digunakan untuk menggabungkan beberapa fungsi menjadi satu rantai (karang, dan Kemudian). Function toInteger = Integer::valueOf; Function backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
Pembekal
Pembekal mengembalikan hasil (contoh) daripada satu jenis atau yang lain. Tidak seperti fungsi, pembekal tidak mengambil hujah. Supplier personSupplier = Person::new; personSupplier.get(); // new Person
Pengguna
Pengguna mewakili kaedah antara muka dengan satu hujah. Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
Pembanding
Pembanding diketahui oleh kami daripada versi Java sebelumnya. Java 8 membolehkan anda menambah pelbagai kaedah lalai pada antara muka. 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
Pilihan
Antara muka Pilihan tidak berfungsi, tetapi ia adalah utiliti yang hebat untuk menghalang NullPointerException . Ini adalah perkara penting untuk bahagian seterusnya, jadi mari kita lihat dengan pantas cara antara muka ini berfungsi. Antara muka Pilihan ialah bekas mudah untuk nilai yang boleh menjadi nol atau tidak nol. Bayangkan bahawa kaedah boleh mengembalikan nilai atau tidak. Dalam Java 8, bukannya mengembalikan null , anda mengembalikan contoh Pilihan . 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

Strim

java.util.Stream ialah jujukan elemen di mana satu atau banyak operasi dilakukan. Setiap operasi Strim adalah sama ada perantaraan atau terminal. Operasi terminal mengembalikan hasil daripada jenis tertentu, manakala operasi perantaraan mengembalikan objek strim itu sendiri, membenarkan rantaian panggilan kaedah dibuat. Strim ialah antara muka, seperti java.util.Collection untuk senarai dan set (peta tidak disokong). Setiap operasi Strim boleh dilaksanakan sama ada secara berurutan atau selari. Mari kita lihat cara aliran berfungsi. Mula-mula, kami akan mencipta kod sampel dalam bentuk senarai rentetan: Koleksi dalam Java 8 dipertingkatkan supaya anda boleh mencipta strim dengan mudah dengan memanggil Collection.stream() atau Collection.parallelStream() . Bahagian seterusnya akan menerangkan operasi strim yang paling penting dan mudah. 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");
Penapis
Penapis menerima predikat untuk menapis semua elemen strim. Operasi ini adalah perantaraan, yang membolehkan kami memanggil operasi strim lain (contohnya untukSetiap) pada hasil (ditapis) yang terhasil. ForEach menerima operasi yang akan dilakukan pada setiap elemen strim yang telah ditapis. ForEach ialah operasi terminal. Selanjutnya, memanggil operasi lain adalah mustahil. stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"
Diisih
Isih ialah operasi perantaraan yang mengembalikan perwakilan strim yang diisih. Unsur-unsur diisih mengikut susunan yang betul melainkan anda menentukan Pembanding anda . stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2" Perlu diingat bahawa diisih mencipta perwakilan strim yang diisih tanpa menjejaskan koleksi itu sendiri. Susunan elemen stringCollection kekal tidak disentuh: System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Peta
Operasi peta perantaraan menukar setiap elemen kepada objek lain menggunakan fungsi yang terhasil. Contoh berikut menukarkan setiap rentetan kepada rentetan huruf besar. Tetapi anda juga boleh menggunakan peta untuk menukar setiap objek kepada jenis yang berbeza. Jenis objek aliran yang terhasil bergantung pada jenis fungsi yang anda hantar ke peta. stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Perlawanan
Pelbagai operasi padanan boleh digunakan untuk menguji kebenaran predikat tertentu dalam hubungan aliran. Semua operasi padanan adalah 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
Kira
Count ialah operasi terminal yang mengembalikan bilangan elemen aliran sebagai panjang . long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3
Kurangkan
Ini ialah operasi terminal yang memendekkan elemen aliran menggunakan fungsi yang diluluskan. Hasilnya akan menjadi Pilihan yang mengandungi nilai yang dipendekkan. Optional reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Aliran Selari

Seperti yang dinyatakan di atas, aliran boleh berurutan atau selari. Operasi aliran berurutan dilakukan pada benang bersiri, manakala operasi aliran selari dilakukan pada berbilang benang selari. Contoh berikut menunjukkan cara meningkatkan prestasi dengan mudah menggunakan aliran selari. Mula-mula, mari kita buat senarai besar elemen unik: Sekarang kita akan menentukan masa yang digunakan untuk mengisih strim 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()); }
Strim bersiri
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 selari
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-dua serpihan adalah hampir sama, tetapi pengisihan selari adalah 50% lebih cepat. Apa yang anda perlukan ialah menukar stream() kepada parallelStream() .

Peta

Seperti yang telah disebutkan, peta tidak menyokong aliran. Sebaliknya, peta mula menyokong kaedah baharu dan berguna untuk menyelesaikan masalah biasa. Kod di atas hendaklah intuitif: putIfAbsent memberi amaran kepada kami supaya tidak menulis semakan nol tambahan. forEach menerima fungsi untuk melaksanakan bagi setiap nilai peta. Contoh ini menunjukkan cara operasi dilakukan pada nilai peta menggunakan fungsi: Seterusnya, kita akan belajar cara mengalih keluar masukan untuk kunci yang diberikan hanya jika ia memetakan kepada nilai tertentu: Kaedah lain yang baik: Menggabungkan entri peta agak mudah: Menggabungkan sama ada akan memasukkan kunci/nilai ke dalam peta , jika tiada entri untuk kunci yang diberikan, atau fungsi gabungan akan dipanggil, yang akan mengubah nilai entri sedia 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
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION