JavaRush /Blog Java /Random-MS /Antara Muka Fungsian dalam Java

Antara Muka Fungsian dalam Java

Diterbitkan dalam kumpulan
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. Antara Muka Fungsian dalam Java - 1Versi 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 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 @FunctionalInterfacebukanlah 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 @FunctionalInterfaceini 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
Konsep antara muka membayangkan bahawa unit kod tertentu tidak boleh melaksanakan sebarang kaedah. Tetapi bermula dengan Java 8, ia menjadi mungkin untuk menggunakan kaedah statik dan lalai dalam antara muka. Kaedah statik terikat terus ke kelas dan tidak memerlukan objek khusus kelas itu untuk memanggil kaedah sedemikian. Iaitu, kaedah ini sesuai dengan harmoni ke dalam konsep antara muka. Sebagai contoh, mari tambah kaedah statik untuk menyemak objek untuk nol ke kelas sebelumnya:
@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
Sebelum Java 8, jika kita perlu mencipta kaedah dalam antara muka yang diwarisi oleh kelas lain, kita hanya boleh mencipta kaedah abstrak yang dilaksanakan dalam setiap kelas tertentu. Tetapi bagaimana jika kaedah ini adalah sama untuk semua kelas? Dalam kes ini , kelas abstrak paling kerap digunakan . Tetapi bermula dengan Java 8, terdapat pilihan untuk menggunakan antara muka dengan kaedah yang dilaksanakan - kaedah lalai. Apabila mewarisi antara muka, anda boleh mengatasi kaedah ini atau membiarkan segala-galanya seperti sedia ada (meninggalkan logik lalai). Apabila membuat kaedah lalai, kita mesti menambah 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 bahawa pengkompil tidak mula mengadu, dan kami tidak melampaui batasan antara muka berfungsi.
  • Kaedah kelas objek
Dalam kuliah Membandingkan Objek , kami bercakap tentang fakta bahawa semua kelas mewarisi daripada kelas 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 Convertermasih 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, Converteria 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 Berfungsi dalam Java - 2

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 Predicateyang 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 ( ): FunctionStringInteger
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 applyuntuk 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? Antara Muka Berfungsi dalam Java - 3Dan sedemikian sehingga banyak kaedah Streamberfungsi 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 kelas Stream- filteryang diambil sebagai hujah Predicatedan Streamhanya mengembalikan elemen yang memenuhi syarat Predicate. Dalam konteks Stream-a, ini bermakna ia hanya melalui elemen tersebut yang dikembalikan trueapabila digunakan dalam kaedah testantara 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 evenNumbersakan terdiri daripada elemen {2, 4, 6, 8}. Dan, seperti yang kita ingat, collectia akan mengumpulkan semua elemen ke dalam koleksi tertentu: dalam kes kita, ke dalam List.

Kaedah dengan Pengguna

Salah satu kaedah dalam Stream, yang menggunakan antara muka berfungsi Consumer, ialah peek. Inilah contoh kami Consumerdalam 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 peekberfungsi dengan Consumer, pengubahsuaian rentetan dalam Streamtidak akan berlaku, tetapi peekakan kembali Streamdengan elemen asal: sama seperti ia datang kepadanya. Oleh itu, senarai itu peopleGreetingsakan 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 yang Streammenggunakan antara muka fungsi Supplierialah generate, yang menjana jujukan tak terhingga berdasarkan antara muka fungsi yang dihantar kepadanya. Mari gunakan contoh kami Supplieruntuk 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 dengan Streamhujah Functionialah kaedah mapyang mengambil elemen satu jenis, melakukan sesuatu dengannya dan meneruskannya, tetapi ini sudah boleh menjadi unsur jenis yang berbeza. Contoh dengan Functionin 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 digunakan UnaryOperatorsebagai 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.
Ini adalah contoh kami akan kelihatan seperti 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. Antara Muka Fungsian dalam Java - 4Itu sahaja! Alangkah baiknya jika selepas membaca artikel ini anda selangkah lebih dekat untuk memahami dan menguasai API Strim dalam Java!
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION