JavaRush /Java Blog /Random-ID /Antarmuka Fungsional di Java

Antarmuka Fungsional di Java

Dipublikasikan di grup Random-ID
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. Antarmuka Fungsional di Java - 1Versi 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 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 @FunctionalInterfacebukanlah 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 @FunctionalInterfaceini 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
Konsep antarmuka menyiratkan bahwa unit kode tertentu tidak dapat menerapkan metode apa pun. Namun mulai Java 8, metode statis dan default dapat digunakan di antarmuka. Metode statis terikat langsung ke suatu kelas dan tidak memerlukan objek spesifik dari kelas tersebut untuk memanggil metode tersebut. Artinya, metode ini selaras dengan konsep antarmuka. Sebagai contoh, mari tambahkan metode statis untuk memeriksa null suatu objek ke kelas sebelumnya:
@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
Sebelum Java 8, jika kita perlu membuat metode di antarmuka yang diwarisi oleh kelas lain, kita hanya bisa membuat metode abstrak yang diimplementasikan di setiap kelas tertentu. Namun bagaimana jika cara ini sama untuk semua kelas? Dalam hal ini , kelas abstrak paling sering digunakan . Namun mulai Java 8, ada opsi untuk menggunakan antarmuka dengan metode yang diterapkan - metode default. Saat mewarisi antarmuka, Anda dapat mengganti metode ini atau membiarkan semuanya apa adanya (biarkan logika default). Saat membuat metode default, kita harus menambahkan kata kunci - 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
Dalam kuliah Membandingkan Objek , kita berbicara tentang fakta bahwa semua kelas mewarisi dari kelas tersebut 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 Convertermasih 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, Converteria 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 di Java - 2

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 Predicateyang 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 ( ): FunctionStringInteger
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 applyuntuk 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? Antarmuka Fungsional di Java - 3Dan banyak metode Streamyang 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 kelas Stream- filteryang mengambil argumen Predicatedan Streamhanya mengembalikan elemen yang memenuhi kondisi Predicate. Dalam konteks Stream-a, ini berarti hanya melewati elemen-elemen yang dikembalikan truesaat digunakan dalam metode testantarmuka 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 evenNumbersakan terdiri dari elemen {2, 4, 6, 8}. Dan, seperti yang kita ingat, collectini akan mengumpulkan semua elemen ke dalam koleksi tertentu: dalam kasus kita, menjadi List.

Metode dengan Konsumen

Salah satu metode Streamyang menggunakan antarmuka fungsional Consumeradalah peek. Ini adalah contoh yang akan terlihat seperti Consumerini 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 peekbekerja dengan Consumer, modifikasi string Streamtidak akan terjadi, tetapi peekakan kembali Streamdengan elemen aslinya: sama seperti aslinya. Oleh karena itu, daftar tersebut peopleGreetingsakan 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 yang Streammenggunakan antarmuka fungsional Supplieradalah generate, yang menghasilkan urutan tak terbatas berdasarkan antarmuka fungsional yang diteruskan ke metode tersebut. Mari gunakan contoh kita Supplieruntuk 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 dengan Streamargumen Functionadalah metode mapyang mengambil elemen dari satu tipe, melakukan sesuatu dengannya dan meneruskannya, tetapi ini sudah bisa menjadi elemen dari tipe yang berbeda. Seperti apa contoh Functioninnya 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 digunakan UnaryOperatorsebagai 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.
Contoh kita akan terlihat seperti ini 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. Antarmuka Fungsional di Java - 4Itu saja! Alangkah baiknya jika setelah membaca artikel ini Anda selangkah lebih dekat untuk memahami dan menguasai Stream API di Java!
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION