hello! Dalam pencarian Java Syntax Pro, kami mengkaji ungkapan lambda dan mengatakan bahawa ia tidak lebih daripada pelaksanaan kaedah berfungsi daripada antara muka berfungsi. Dalam erti kata lain, ini ialah pelaksanaan beberapa kelas tanpa nama (tidak diketahui), kaedahnya yang tidak direalisasikan. Dan jika dalam kuliah kursus kita menyelidiki manipulasi dengan ungkapan lambda, kini kita akan mempertimbangkan, boleh dikatakan, sisi lain: iaitu, antara muka ini. Versi kelapan Java memperkenalkan konsep antara muka berfungsi . Apakah ini? Antara muka dengan satu kaedah yang tidak dilaksanakan (abstrak) dianggap berfungsi. Banyak antara muka luar kotak termasuk di bawah takrifan ini, seperti, sebagai contoh, antara muka yang dibincangkan sebelum ini Predikat
Pengguna
Pembekal
Fungsi
UnaryOperator
Comparator
. Dan juga antara muka yang kami cipta sendiri, seperti:
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
}
Kami mempunyai antara muka yang tugasnya adalah untuk menukar objek satu jenis kepada objek lain (sejenis penyesuai). Anotasi @FunctionalInterface
bukanlah sesuatu yang sangat kompleks atau penting, kerana tujuannya adalah untuk memberitahu pengkompil bahawa antara muka ini berfungsi dan tidak boleh mengandungi lebih daripada satu kaedah. Jika antara muka dengan anotasi ini mempunyai lebih daripada satu kaedah (abstrak) yang tidak dilaksanakan, pengkompil tidak akan melangkau antara muka ini, kerana ia akan menganggapnya sebagai kod yang salah. Antara muka tanpa anotasi ini boleh dianggap berfungsi dan akan berfungsi, tetapi @FunctionalInterface
ini tidak lebih daripada insurans tambahan. Jom balik kelas Comparator
. Jika anda melihat pada kodnya (atau dokumentasi ), anda boleh melihat bahawa ia mempunyai lebih daripada satu kaedah. Kemudian anda bertanya: bagaimana, kemudian, bolehkah ia dianggap sebagai antara muka berfungsi? Antara muka abstrak boleh mempunyai kaedah yang tidak berada dalam skop kaedah tunggal:
- statik
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
}
Setelah menerima kaedah ini, pengkompil tidak merungut, yang bermaksud antara muka kami masih berfungsi.
- kaedah lalai
default
:
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
default void writeToConsole(T t) {
System.out.println("Текущий an object - " + t.toString());
}
}
Sekali lagi, kami melihat bahawa pengkompil tidak mula mengadu, dan kami tidak melampaui batasan antara muka berfungsi.
- Kaedah kelas objek
Object
. Ini tidak terpakai kepada antara muka. Tetapi jika kami mempunyai kaedah abstrak dalam antara muka yang sepadan dengan tandatangan dengan beberapa kaedah class Object
, kaedah (atau kaedah) sedemikian tidak akan memecahkan sekatan antara muka fungsi kami:
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
default void writeToConsole(T t) {
System.out.println("Текущий an object - " + t.toString());
}
boolean equals(Object obj);
}
Dan sekali lagi, pengkompil kami tidak merungut, jadi antara muka Converter
masih dianggap berfungsi. Sekarang persoalannya ialah: mengapa kita perlu mengehadkan diri kita kepada satu kaedah yang tidak dilaksanakan dalam antara muka berfungsi? Dan kemudian supaya kita boleh melaksanakannya menggunakan lambdas. Mari kita lihat ini dengan contoh Converter
. Untuk melakukan ini, mari buat kelas Dog
:
public class Dog {
String name;
int age;
int weight;
public Dog(final String name, final int age, final int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
}
Dan yang serupa Raccoon
(raccoon):
public class Raccoon {
String name;
int age;
int weight;
public Raccoon(final String name, final int age, final int weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
}
Katakan kita mempunyai objek Dog
, dan kita perlu mencipta objek berdasarkan medannya Raccoon
. Iaitu, Converter
ia menukar objek dari satu jenis kepada yang lain. Bagaimana ia akan kelihatan seperti:
public static void main(String[] args) {
Dog dog = new Dog("Bobbie", 5, 3);
Converter<Dog, Raccoon> converter = x -> new Raccoon(x.name, x.age, x.weight);
Raccoon raccoon = converter.convert(dog);
System.out.println("Raccoon has parameters: name - " + raccoon.name + ", age - " + raccoon.age + ", weight - " + raccoon.weight);
}
Apabila kami menjalankannya, kami mendapat output berikut ke konsol:
Raccoon has parameters: name - Bobbbie, age - 5, weight - 3
Dan ini bermakna kaedah kami berfungsi dengan betul.
Antara Muka Fungsian Asas Java 8
Nah, sekarang mari kita lihat beberapa antara muka berfungsi yang Java 8 bawakan kepada kita dan yang digunakan secara aktif bersama dengan API Strim.Predikat
Predicate
— antara muka berfungsi untuk menyemak sama ada syarat tertentu dipenuhi. Jika syarat dipenuhi, kembalikan true
, sebaliknya - false
:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Sebagai contoh, pertimbangkan untuk mencipta a Predicate
yang akan menyemak pariti beberapa jenis Integer
:
public static void main(String[] args) {
Predicate<Integer> isEvenNumber = x -> x % 2==0;
System.out.println(isEvenNumber.test(4));
System.out.println(isEvenNumber.test(3));
}
Output konsol:
true
false
Pengguna
Consumer
(dari bahasa Inggeris - "pengguna") - antara muka berfungsi yang mengambil objek jenis T sebagai hujah input, melakukan beberapa tindakan, tetapi tidak mengembalikan apa-apa:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Sebagai contoh, pertimbangkan , yang tugasnya adalah untuk mengeluarkan ucapan kepada konsol dengan hujah rentetan yang diluluskan: Consumer
public static void main(String[] args) {
Consumer<String> greetings = x -> System.out.println("Hello " + x + " !!!");
greetings.accept("Elena");
}
Output konsol:
Hello Elena !!!
Pembekal
Supplier
(dari bahasa Inggeris - pembekal) - antara muka berfungsi yang tidak mengambil sebarang hujah, tetapi mengembalikan objek jenis T:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Sebagai contoh, pertimbangkan Supplier
, yang akan menghasilkan nama rawak daripada senarai:
public static void main(String[] args) {
ArrayList<String> nameList = new ArrayList<>();
nameList .add("Elena");
nameList .add("John");
nameList .add("Alex");
nameList .add("Jim");
nameList .add("Sara");
Supplier<String> randomName = () -> {
int value = (int)(Math.random() * nameList.size());
return nameList.get(value);
};
System.out.println(randomName.get());
}
Dan jika kita menjalankan ini, kita akan melihat hasil rawak daripada senarai nama dalam konsol.
Fungsi
Function
— antara muka berfungsi ini mengambil hujah T dan menghantarnya ke objek jenis R, yang dikembalikan sebagai hasilnya:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Sebagai contoh, mari kita ambil , yang menukar nombor daripada format rentetan ( ) kepada format nombor ( ): Function
String
Integer
public static void main(String[] args) {
Function<String, Integer> valueConverter = x -> Integer.valueOf(x);
System.out.println(valueConverter.apply("678"));
}
Apabila kami menjalankannya, kami mendapat output berikut ke konsol:
678
PS: jika kita lulus bukan sahaja nombor, tetapi juga aksara lain ke dalam rentetan, pengecualian akan dilemparkan - NumberFormatException
.
UnaryOperator
UnaryOperator
— antara muka berfungsi yang mengambil objek jenis T sebagai parameter, melakukan beberapa operasi padanya dan mengembalikan hasil operasi dalam bentuk objek jenis T yang sama:
@FunctionalInterface
public interface UnaryOperator<T> {
T apply(T t);
}
UnaryOperator
, yang menggunakan kaedahnya apply
untuk mengduakan nombor:
public static void main(String[] args) {
UnaryOperator<Integer> squareValue = x -> x * x;
System.out.println(squareValue.apply(9));
}
Output konsol:
81
Kami melihat lima antara muka berfungsi. Ini bukan semua yang tersedia untuk kami bermula dengan Java 8 - ini adalah antara muka utama. Selebihnya yang ada adalah analog rumit mereka. Senarai lengkap boleh didapati dalam dokumentasi Oracle rasmi .
Antara muka berfungsi dalam Strim
Seperti yang dibincangkan di atas, antara muka berfungsi ini digabungkan dengan API Strim. Bagaimana, anda bertanya? Dan sedemikian sehingga banyak kaedahStream
berfungsi secara khusus dengan antara muka berfungsi ini. Mari lihat bagaimana antara muka berfungsi boleh digunakan dalam Stream
.
Kaedah dengan Predikat
Sebagai contoh, mari kita ambil kaedah kelasStream
- filter
yang diambil sebagai hujah Predicate
dan Stream
hanya mengembalikan elemen yang memenuhi syarat Predicate
. Dalam konteks Stream
-a, ini bermakna ia hanya melalui elemen tersebut yang dikembalikan true
apabila digunakan dalam kaedah test
antara muka Predicate
. Ini adalah contoh kami akan kelihatan seperti Predicate
, tetapi untuk penapis elemen dalam Stream
:
public static void main(String[] args) {
List<Integer> evenNumbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8)
.filter(x -> x % 2==0)
.collect(Collectors.toList());
}
Akibatnya, senarai evenNumbers
akan terdiri daripada elemen {2, 4, 6, 8}. Dan, seperti yang kita ingat, collect
ia akan mengumpulkan semua elemen ke dalam koleksi tertentu: dalam kes kita, ke dalam List
.
Kaedah dengan Pengguna
Salah satu kaedah dalamStream
, yang menggunakan antara muka berfungsi Consumer
, ialah peek
. Inilah contoh kami Consumer
dalam akan kelihatan seperti Stream
:
public static void main(String[] args) {
List<String> peopleGreetings = Stream.of("Elena", "John", "Alex", "Jim", "Sara")
.peek(x -> System.out.println("Hello " + x + " !!!"))
.collect(Collectors.toList());
}
Output konsol:
Hello Elena !!!
Hello John !!!
Hello Alex !!!
Hello Jim !!!
Hello Sara !!!
Tetapi memandangkan kaedah itu peek
berfungsi dengan Consumer
, pengubahsuaian rentetan dalam Stream
tidak akan berlaku, tetapi peek
akan kembali Stream
dengan elemen asal: sama seperti ia datang kepadanya. Oleh itu, senarai itu peopleGreetings
akan terdiri daripada elemen "Elena", "John", "Alex", "Jim", "Sara". Terdapat juga kaedah yang biasa digunakan foreach
, yang serupa dengan kaedah peek
, tetapi perbezaannya ialah ia adalah terminal terakhir.
Kaedah dengan Pembekal
Contoh kaedah yangStream
menggunakan antara muka fungsi Supplier
ialah generate
, yang menjana jujukan tak terhingga berdasarkan antara muka fungsi yang dihantar kepadanya. Mari gunakan contoh kami Supplier
untuk mencetak lima nama rawak ke konsol:
public static void main(String[] args) {
ArrayList<String> nameList = new ArrayList<>();
nameList.add("Elena");
nameList.add("John");
nameList.add("Alex");
nameList.add("Jim");
nameList.add("Sara");
Stream.generate(() -> {
int value = (int) (Math.random() * nameList.size());
return nameList.get(value);
}).limit(5).forEach(System.out::println);
}
Dan ini adalah output yang kami dapat dalam konsol:
John
Elena
Elena
Elena
Jim
Di sini kami menggunakan kaedah limit(5)
untuk menetapkan had pada kaedah generate
, jika tidak program akan mencetak nama rawak ke konsol selama-lamanya.
Kaedah dengan Fungsi
Contoh tipikal kaedah denganStream
hujah Function
ialah kaedah map
yang mengambil elemen satu jenis, melakukan sesuatu dengannya dan meneruskannya, tetapi ini sudah boleh menjadi unsur jenis yang berbeza. Contoh dengan Function
in mungkin kelihatan seperti Stream
:
public static void main(String[] args) {
List<Integer> values = Stream.of("32", "43", "74", "54", "3")
.map(x -> Integer.valueOf(x)).collect(Collectors.toList());
}
Akibatnya, kami mendapat senarai nombor, tetapi dalam Integer
.
Kaedah dengan UnaryOperator
Sebagai kaedah yang digunakanUnaryOperator
sebagai hujah, mari kita ambil kaedah kelas Stream
- iterate
. Kaedah ini serupa dengan kaedah generate
: ia juga menghasilkan urutan tak terhingga tetapi mempunyai dua hujah:
- yang pertama ialah elemen dari mana penjanaan jujukan bermula;
- yang kedua ialah
UnaryOperator
, yang menunjukkan prinsip penjanaan elemen baru daripada elemen pertama.
UnaryOperator
, tetapi dalam kaedah iterate
:
public static void main(String[] args) {
Stream.iterate(9, x -> x * x)
.limit(4)
.forEach(System.out::println);
}
Apabila kami menjalankannya, kami mendapat output berikut ke konsol:
9
81
6561
43046721
Iaitu, setiap elemen kami didarab dengan sendirinya, dan seterusnya untuk empat nombor pertama. Itu sahaja! Alangkah baiknya jika selepas membaca artikel ini anda selangkah lebih dekat untuk memahami dan menguasai API Strim dalam Java!
GO TO FULL VERSION