Panduan untuk Antaramuka BiFungsi Java

1. Pengenalan

Java 8 memperkenalkan pengaturcaraan gaya fungsional, yang memungkinkan kita untuk memetarasikan kaedah tujuan umum dengan meneruskan fungsi.

Kami mungkin paling biasa dengan antara muka fungsional Java 8 parameter tunggal seperti Fungsi , Predikat, dan Pengguna .

Dalam tutorial ini, kita akan melihat antara muka fungsional yang menggunakan dua parameter . Fungsi tersebut disebut fungsi binari dan diwakili di Java dengan antara muka fungsi BiFunction .

2. Fungsi Parameter Tunggal

Mari kita buat semula bagaimana kita menggunakan parameter tunggal atau fungsi unary, seperti yang kita lakukan dalam aliran:

List mapped = Stream.of("hello", "world") .map(word -> word + "!") .collect(Collectors.toList()); assertThat(mapped).containsExactly("hello!", "world!");

Seperti yang kita lihat, peta menggunakan Fungsi , yang mengambil satu parameter dan memungkinkan kita melakukan operasi pada nilai tersebut, mengembalikan nilai baru.

3. Operasi Dua Parameter

Perpustakaan Java Stream memberi kita fungsi pengurangan yang memungkinkan kita menggabungkan unsur-unsur aliran . Kita perlu menyatakan bagaimana nilai-nilai yang telah kita kumpulkan sejauh ini diubah dengan menambahkan item seterusnya.

Yang mengurangkan fungsi menggunakan antara muka fungsi BinaryOperator , yang mengambil masa dua objek dari jenis yang sama dengan input.

Bayangkan kita mahu menggabungkan semua item dalam aliran kita dengan meletakkan yang baru di hadapan dengan pemisah tanda pisah. Kami akan melihat beberapa cara untuk melaksanakannya di bahagian berikut.

3.1. Menggunakan Lambda

Pelaksanaan lambda untuk BiFunction diawali oleh dua parameter, dikelilingi oleh tanda kurung:

String result = Stream.of("hello", "world") .reduce("", (a, b) -> b + "-" + a); assertThat(result).isEqualTo("world-hello-");

Seperti yang dapat kita lihat, dua nilai, a dan b adalah String . Kami telah menulis lambda yang menggabungkannya untuk menghasilkan output yang diinginkan, dengan yang kedua terlebih dahulu, dan tanda hubung di antara.

Kita harus perhatikan bahawa mengurangkan menggunakan nilai permulaan - dalam kes ini, rentetan kosong. Oleh itu, kami berakhir dengan tanda hubung dengan kod di atas, kerana nilai pertama dari aliran kami bergabung dengannya.

Juga, kita harus perhatikan bahawa inferensi jenis Java memungkinkan kita untuk menghilangkan jenis parameter kita sepanjang masa. Dalam keadaan di mana jenis lambda tidak jelas dari konteksnya, kita boleh menggunakan jenis untuk parameter kami:

String result = Stream.of("hello", "world") .reduce("", (String a, String b) -> b + "-" + a);

3.2. Menggunakan Fungsi

Bagaimana jika kita ingin menjadikan algoritma di atas tidak sedikitpun? Kami boleh menulis lebih banyak kod dalam lambda kami, tetapi itu mungkin menjadi tidak kemas. Mari kita ekstrak fungsi sebagai gantinya:

private String combineWithoutTrailingDash(String a, String b) { if (a.isEmpty()) { return b; } return b + "-" + a; }

Dan panggilnya:

String result = Stream.of("hello", "world") .reduce("", (a, b) -> combineWithoutTrailingDash(a, b)); assertThat(result).isEqualTo("world-hello");

Seperti yang kita lihat, lambda memanggil fungsi kita, yang lebih mudah dibaca daripada meletakkan pelaksanaan yang lebih kompleks sebaris.

3.3. Menggunakan Rujukan Kaedah

Beberapa IDE secara automatik akan meminta kami untuk menukar lambda di atas menjadi rujukan kaedah, kerana lebih jelas untuk dibaca.

Mari tulis semula kod kami untuk menggunakan rujukan kaedah:

String result = Stream.of("hello", "world") .reduce("", this::combineWithoutTrailingDash); assertThat(result).isEqualTo("world-hello");

Rujukan kaedah sering menjadikan kod berfungsi lebih jelas.

4. Menggunakan BiFungsi

Sejauh ini, kami telah menunjukkan cara menggunakan fungsi di mana kedua-dua parameter adalah jenis yang sama. Antara muka BiFunction membolehkan kita menggunakan parameter dari pelbagai jenis , dengan nilai kembali dari jenis ketiga.

Mari kita bayangkan bahawa kita membuat algoritma untuk menggabungkan dua senarai dengan ukuran yang sama menjadi senarai ketiga dengan melakukan operasi pada setiap pasangan elemen:

List list1 = Arrays.asList("a", "b", "c"); List list2 = Arrays.asList(1, 2, 3); List result = new ArrayList(); for (int i=0; i < list1.size(); i++) { result.add(list1.get(i) + list2.get(i)); } assertThat(result).containsExactly("a1", "b2", "c3");

4.1. Umumkan Fungsi

Kami dapat menggeneralisasikan fungsi khusus ini menggunakan BiFunction sebagai penggabung:

private static  List listCombiner( List list1, List list2, BiFunction combiner) { List result = new ArrayList(); for (int i = 0; i < list1.size(); i++) { result.add(combiner.apply(list1.get(i), list2.get(i))); } return result; }

Mari lihat apa yang berlaku di sini. Terdapat tiga jenis parameter: T untuk jenis item dalam senarai pertama, U untuk jenis dalam senarai kedua, dan kemudian R untuk jenis apa pun fungsi kombinasi kembali.

Kami menggunakan BiFungsi yang disediakan untuk fungsi ini dengan memanggil kaedah aplikasinya untuk mendapatkan hasilnya.

4.2. Memanggil Fungsi Umum

Penggabung kami adalah BiFungsi , yang membolehkan kita memasukkan algoritma, apa sahaja jenis input dan output. Mari mencubanya:

List list1 = Arrays.asList("a", "b", "c"); List list2 = Arrays.asList(1, 2, 3); List result = listCombiner(list1, list2, (a, b) -> a + b); assertThat(result).containsExactly("a1", "b2", "c3");

Kita juga boleh menggunakan ini untuk pelbagai jenis input dan output.

Mari kita suntik algoritma untuk menentukan sama ada nilai dalam senarai pertama lebih besar daripada nilai di kedua dan menghasilkan hasil boolean :

List list1 = Arrays.asList(1.0d, 2.1d, 3.3d); List list2 = Arrays.asList(0.1f, 0.2f, 4f); List result = listCombiner(list1, list2, (a, b) -> a > b); assertThat(result).containsExactly(true, true, false);

4.3. A BiFunction Menghubungi Rujukan

Mari tulis semula kod di atas dengan kaedah yang diekstrak dan rujukan kaedah:

List list1 = Arrays.asList(1.0d, 2.1d, 3.3d); List list2 = Arrays.asList(0.1f, 0.2f, 4f); List result = listCombiner(list1, list2, this::firstIsGreaterThanSecond); assertThat(result).containsExactly(true, true, false); private boolean firstIsGreaterThanSecond(Double a, Float b) { return a > b; }

We should note that this makes the code a little easier to read, as the method firstIsGreaterThanSecond describes the algorithm injected as a method reference.

4.4. BiFunction Method References Using this

Let's imagine we want to use the above BiFunction-based algorithm to determine if two lists are equal:

List list1 = Arrays.asList(0.1f, 0.2f, 4f); List list2 = Arrays.asList(0.1f, 0.2f, 4f); List result = listCombiner(list1, list2, (a, b) -> a.equals(b)); assertThat(result).containsExactly(true, true, true);

We can actually simplify the solution:

List result = listCombiner(list1, list2, Float::equals);

This is because the equals function in Float has the same signature as a BiFunction. It takes an implicit first parameter of this, an object of type Float. The second parameter, other, of type Object, is the value to compare.

5. Composing BiFunctions

What if we could use method references to do the same thing as our numeric list comparison example?

List list1 = Arrays.asList(1.0d, 2.1d, 3.3d); List list2 = Arrays.asList(0.1d, 0.2d, 4d); List result = listCombiner(list1, list2, Double::compareTo); assertThat(result).containsExactly(1, 1, -1);

This is close to our example but returns an Integer, rather than the original Boolean. This is because the compareTo method in Double returns Integer.

We can add the extra behavior we need to achieve our original by using andThen to compose a function. This produces a BiFunction that first does one thing with the two inputs and then performs another operation.

Next, let's create a function to coerce our method reference Double::compareTo into a BiFunction:

private static  BiFunction asBiFunction(BiFunction function) { return function; }

A lambda or method reference only becomes a BiFunction after it has been converted by a method invocation. We can use this helper function to convert our lambda into the BiFunction object explicitly.

Now, we can use andThen to add behavior on top of the first function:

List list1 = Arrays.asList(1.0d, 2.1d, 3.3d); List list2 = Arrays.asList(0.1d, 0.2d, 4d); List result = listCombiner(list1, list2, asBiFunction(Double::compareTo).andThen(i -> i > 0)); assertThat(result).containsExactly(true, true, false);

6. Conclusion

Dalam tutorial ini, kami telah meneroka BiFunction dan BinaryOperator dari segi perpustakaan Java Streams yang disediakan dan fungsi tersuai kami sendiri. Kami telah melihat bagaimana meneruskan BiFunctions menggunakan rujukan lambda dan kaedah, dan kami telah melihat bagaimana menyusun fungsi.

Perpustakaan Java hanya menyediakan antara muka fungsi satu dan dua parameter. Untuk situasi yang memerlukan lebih banyak parameter, lihat artikel kami mengenai kariing untuk mendapatkan lebih banyak idea.

Seperti biasa, sampel kod lengkap boleh didapati di GitHub.