Halo! Dalam pencarian Java Syntax Pro, kami mempelajari ekspresi lambda dan mengatakan bahwa itu tidak lebih dari implementasi metode fungsional dari antarmuka fungsional. Dengan kata lain, ini adalah implementasi dari beberapa kelas anonim (tidak diketahui), metodenya yang belum direalisasi. Dan jika dalam perkuliahan kursus kita mempelajari manipulasi dengan ekspresi lambda, sekarang kita akan mempertimbangkan, bisa dikatakan, sisi lain: yaitu, antarmuka ini. Versi kedelapan Java memperkenalkan konsep antarmuka fungsional . Apa ini? Antarmuka dengan satu metode (abstrak) yang tidak diterapkan dianggap fungsional. Banyak antarmuka out-of-the-box termasuk dalam definisi ini, seperti, misalnya, antarmuka yang telah dibahas sebelumnya Predikat
Konsumen
Pemasok
Fungsi
Operator Unary
Comparator
. Dan juga interface yang kami buat sendiri, seperti:
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
}
Kami memiliki antarmuka yang tugasnya mengubah objek dari satu jenis menjadi objek jenis lain (semacam adaptor). Anotasi @FunctionalInterface
bukanlah sesuatu yang super rumit atau penting, karena tujuannya adalah untuk memberi tahu kompiler bahwa antarmuka ini berfungsi dan tidak boleh berisi lebih dari satu metode. Jika antarmuka dengan anotasi ini memiliki lebih dari satu metode (abstrak) yang belum diimplementasikan, kompiler tidak akan melewatkan antarmuka ini karena akan menganggapnya sebagai kode yang salah. Antarmuka tanpa anotasi ini dapat dianggap berfungsi dan akan berfungsi, tetapi @FunctionalInterface
ini tidak lebih dari jaminan tambahan. Ayo kembali ke kelas Comparator
. Jika Anda melihat kodenya (atau dokumentasinya ), Anda dapat melihat bahwa ia memiliki lebih dari satu metode. Lalu Anda bertanya: lalu bagaimana itu bisa dianggap sebagai antarmuka fungsional? Antarmuka abstrak dapat memiliki metode yang tidak berada dalam cakupan metode tunggal:
- statis
@FunctionalInterface
public interface Converter<T, N> {
N convert(T t);
static <T> boolean isNotNull(T t){
return t != null;
}
}
Setelah menerima metode ini, kompiler tidak mengeluh, yang berarti antarmuka kami masih berfungsi.
- metode default
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 bahwa kompiler tidak mulai mengeluh, dan kami tidak melampaui batasan antarmuka fungsional.
- Metode kelas objek
Object
. Ini tidak berlaku untuk antarmuka. Tetapi jika kita memiliki metode abstrak di antarmuka yang cocok dengan tanda tangan dengan beberapa metode kelas Object
, metode (atau metode) seperti itu tidak akan melanggar batasan fungsional antarmuka kita:
@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, kompiler kami tidak mengeluh, sehingga antarmuka Converter
masih dianggap berfungsi. Sekarang pertanyaannya adalah: mengapa kita perlu membatasi diri pada satu metode yang tidak diterapkan dalam antarmuka fungsional? Lalu agar kita bisa mengimplementasikannya menggunakan lambda. Mari kita lihat ini dengan sebuah 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
(rakun):
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;
}
}
Misalkan kita mempunyai sebuah objek Dog
, dan kita perlu membuat sebuah objek berdasarkan bidangnya Raccoon
. Artinya, Converter
ia mengubah suatu objek dari satu tipe ke tipe lainnya. Bagaimana tampilannya:
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);
}
Saat kami menjalankannya, kami mendapatkan output berikut ke konsol:
Raccoon has parameters: name - Bobbbie, age - 5, weight - 3
Ini berarti metode kami bekerja dengan benar.
Antarmuka Fungsional Java 8 Dasar
Nah, sekarang mari kita lihat beberapa antarmuka fungsional yang dihadirkan Java 8 dan yang secara aktif digunakan bersama dengan Stream API.Predikat
Predicate
— antarmuka fungsional untuk memeriksa apakah kondisi tertentu terpenuhi. Jika kondisi terpenuhi, return true
, jika tidak - false
:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Sebagai contoh, pertimbangkan untuk membuat Predicate
yang akan memeriksa paritas dari sejumlah tipe 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));
}
Keluaran konsol:
true
false
Konsumen
Consumer
(dari bahasa Inggris - "konsumen") - antarmuka fungsional yang mengambil objek bertipe T sebagai argumen masukan, melakukan beberapa tindakan, tetapi tidak mengembalikan apa pun:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Sebagai contoh, pertimbangkan , yang tugasnya adalah mengeluarkan salam ke konsol dengan argumen string yang diteruskan: Consumer
public static void main(String[] args) {
Consumer<String> greetings = x -> System.out.println("Hello " + x + " !!!");
greetings.accept("Elena");
}
Keluaran konsol:
Hello Elena !!!
Pemasok
Supplier
(dari bahasa Inggris - penyedia) - antarmuka fungsional yang tidak menerima argumen apa pun, tetapi mengembalikan objek bertipe T:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Sebagai contoh, pertimbangkan Supplier
, yang akan menghasilkan nama acak dari daftar:
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 acak dari daftar nama di konsol.
Fungsi
Function
— antarmuka fungsional ini mengambil argumen T dan melemparkannya ke objek bertipe R, yang dikembalikan sebagai hasilnya:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Sebagai contoh, mari kita ambil , yang mengonversi angka dari format string ( ) ke format angka ( ): Function
String
Integer
public static void main(String[] args) {
Function<String, Integer> valueConverter = x -> Integer.valueOf(x);
System.out.println(valueConverter.apply("678"));
}
Saat kami menjalankannya, kami mendapatkan output berikut ke konsol:
678
PS: jika kita memasukkan tidak hanya angka, tetapi juga karakter lain ke dalam string, pengecualian akan diberikan - NumberFormatException
.
Operator Unary
UnaryOperator
— antarmuka fungsional yang mengambil objek bertipe T sebagai parameter, melakukan beberapa operasi padanya, dan mengembalikan hasil operasi dalam bentuk objek bertipe T yang sama:
@FunctionalInterface
public interface UnaryOperator<T> {
T apply(T t);
}
UnaryOperator
, yang menggunakan metodenya apply
untuk mengkuadratkan suatu bilangan:
public static void main(String[] args) {
UnaryOperator<Integer> squareValue = x -> x * x;
System.out.println(squareValue.apply(9));
}
Keluaran konsol:
81
Kami melihat lima antarmuka fungsional. Ini tidak semua yang tersedia bagi kita mulai dari Java 8 - ini adalah antarmuka utama. Sisanya yang tersedia adalah analognya yang rumit. Daftar lengkapnya dapat ditemukan di dokumentasi resmi Oracle .
Antarmuka fungsional di Stream
Seperti dibahas di atas, antarmuka fungsional ini digabungkan erat dengan Stream API. Bagaimana caranya, Anda bertanya? Dan banyak metodeStream
yang bekerja secara khusus dengan antarmuka fungsional ini. Mari kita lihat bagaimana antarmuka fungsional dapat digunakan dalam Stream
.
Metode dengan Predikat
Misalnya, mari kita ambil metode kelasStream
- filter
yang mengambil argumen Predicate
dan Stream
hanya mengembalikan elemen yang memenuhi kondisi Predicate
. Dalam konteks Stream
-a, ini berarti hanya melewati elemen-elemen yang dikembalikan true
saat digunakan dalam metode test
antarmuka Predicate
. Seperti inilah contoh kita Predicate
, namun untuk filter elemen di 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());
}
Hasilnya, daftar tersebut evenNumbers
akan terdiri dari elemen {2, 4, 6, 8}. Dan, seperti yang kita ingat, collect
ini akan mengumpulkan semua elemen ke dalam koleksi tertentu: dalam kasus kita, menjadi List
.
Metode dengan Konsumen
Salah satu metodeStream
yang menggunakan antarmuka fungsional Consumer
adalah peek
. Ini adalah contoh yang akan terlihat seperti Consumer
ini 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());
}
Keluaran konsol:
Hello Elena !!!
Hello John !!!
Hello Alex !!!
Hello Jim !!!
Hello Sara !!!
Tetapi karena metode ini peek
bekerja dengan Consumer
, modifikasi string Stream
tidak akan terjadi, tetapi peek
akan kembali Stream
dengan elemen aslinya: sama seperti aslinya. Oleh karena itu, daftar tersebut peopleGreetings
akan terdiri dari elemen "Elena", "John", "Alex", "Jim", "Sara". Ada juga metode yang umum digunakan foreach
, yang mirip dengan metode peek
, tetapi perbedaannya adalah terminal final.
Metode dengan Pemasok
Contoh metode yangStream
menggunakan antarmuka fungsional Supplier
adalah generate
, yang menghasilkan urutan tak terbatas berdasarkan antarmuka fungsional yang diteruskan ke metode tersebut. Mari gunakan contoh kita Supplier
untuk mencetak lima nama acak 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 inilah output yang kita dapatkan di konsol:
John
Elena
Elena
Elena
Jim
Di sini kami menggunakan metode limit(5)
untuk menetapkan batasan pada metode tersebut generate
, jika tidak, program akan mencetak nama acak ke konsol tanpa batas waktu.
Metode dengan Fungsi
Contoh umum dari metode denganStream
argumen Function
adalah metode map
yang mengambil elemen dari satu tipe, melakukan sesuatu dengannya dan meneruskannya, tetapi ini sudah bisa menjadi elemen dari tipe yang berbeda. Seperti apa contoh Function
innya 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());
}
Hasilnya, kami mendapatkan daftar angka, tetapi dalam Integer
.
Metode dengan UnaryOperator
Sebagai metode yang digunakanUnaryOperator
sebagai argumen, mari kita ambil metode kelas Stream
- iterate
. Metode ini mirip dengan metode ini generate
: metode ini juga menghasilkan barisan tak terhingga namun memiliki dua argumen:
- yang pertama adalah elemen dari mana pembuatan urutan dimulai;
- yang kedua adalah
UnaryOperator
, yang menunjukkan prinsip menghasilkan elemen baru dari elemen pertama.
UnaryOperator
, tetapi dalam metodenya iterate
:
public static void main(String[] args) {
Stream.iterate(9, x -> x * x)
.limit(4)
.forEach(System.out::println);
}
Saat kami menjalankannya, kami mendapatkan output berikut ke konsol:
9
81
6561
43046721
Artinya, setiap elemen kita dikalikan dengan dirinya sendiri, dan seterusnya untuk empat angka pertama. Itu saja! Alangkah baiknya jika setelah membaca artikel ini Anda selangkah lebih dekat untuk memahami dan menguasai Stream API di Java!
GO TO FULL VERSION