Bagaimana cara menentukan tipe fungsi untuk metode void (bukan Void) di Java8?

143

Saya bermain-main dengan Java 8 untuk mencari tahu bagaimana fungsinya sebagai warga negara kelas satu. Saya memiliki cuplikan berikut:

package test;

import java.util.*;
import java.util.function.*;

public class Test {

    public static void myForEach(List<Integer> list, Function<Integer, Void> myFunction) {
      list.forEach(functionToBlock(myFunction));
    }

    public static void displayInt(Integer i) {
      System.out.println(i);
    }


    public static void main(String[] args) {
      List<Integer> theList = new ArrayList<>();
      theList.add(1);
      theList.add(2);
      theList.add(3);
      theList.add(4);
      theList.add(5);
      theList.add(6);
      myForEach(theList, Test::displayInt);
    }
}

Apa yang saya coba lakukan adalah meneruskan metode displayIntke metode myForEachmenggunakan referensi metode. Untuk mengkompilasi menghasilkan kesalahan berikut:

src/test/Test.java:9: error: cannot find symbol
      list.forEach(functionToBlock(myFunction));
                   ^
  symbol:   method functionToBlock(Function<Integer,Void>)
  location: class Test
src/test/Test.java:25: error: method myForEach in class Test cannot be applied to given ty
pes;
      myForEach(theList, Test::displayInt);
      ^
  required: List<Integer>,Function<Integer,Void>
  found: List<Integer>,Test::displayInt
  reason: argument mismatch; bad return type in method reference
      void cannot be converted to Void

Kompiler mengeluh itu void cannot be converted to Void. Saya tidak tahu bagaimana cara menentukan jenis antarmuka fungsi di tanda tangan myForEachsedemikian rupa sehingga kode dikompilasi. Saya tahu saya bisa dengan mudah mengubah jenis kembali displayIntke Voiddan kemudian kembali null. Namun, mungkin ada situasi di mana tidak mungkin untuk mengubah metode yang ingin saya sampaikan di tempat lain. Apakah ada cara mudah untuk menggunakannya kembali displayInt?

Valentin
sumber

Jawaban:

244

Anda mencoba menggunakan jenis antarmuka yang salah. Tipe fungsi tidak sesuai dalam kasus ini karena menerima parameter dan memiliki nilai balik. Sebaliknya Anda harus menggunakan Konsumen (sebelumnya dikenal sebagai Blok)

Jenis fungsi dinyatakan sebagai

interface Function<T,R> {
  R apply(T t);
}

Namun, tipe Konsumen kompatibel dengan yang Anda cari:

interface Consumer<T> {
   void accept(T t);
}

Dengan demikian, Konsumen kompatibel dengan metode yang menerima T dan tidak mengembalikan apa pun (tidak berlaku). Dan inilah yang Anda inginkan.

Misalnya, jika saya ingin menampilkan semua elemen dalam daftar, saya cukup membuat konsumen untuk itu dengan ekspresi lambda:

List<String> allJedi = asList("Luke","Obiwan","Quigon");
allJedi.forEach( jedi -> System.out.println(jedi) );

Anda dapat melihat di atas bahwa dalam kasus ini, ekspresi lambda menerima parameter dan tidak memiliki nilai balik.

Sekarang, jika saya ingin menggunakan referensi metode alih-alih ekspresi lambda untuk membuat konsumsi jenis ini, maka saya memerlukan metode yang menerima String dan mengembalikan kekosongan, kan ?.

Saya bisa menggunakan berbagai jenis referensi metode, tetapi dalam kasus ini mari kita manfaatkan referensi metode objek dengan menggunakan printlnmetode dalam System.outobjek, seperti ini:

Consumer<String> block = System.out::println

Atau saya bisa melakukannya

allJedi.forEach(System.out::println);

The printlnmetode adalah tepat karena menerima nilai dan memiliki tipe kembali batal, seperti acceptmetode Konsumen.

Jadi, dalam kode Anda, Anda perlu mengubah tanda tangan metode Anda menjadi seperti:

public static void myForEach(List<Integer> list, Consumer<Integer> myBlock) {
   list.forEach(myBlock);
}

Dan kemudian Anda harus dapat membuat konsumen, menggunakan referensi metode statis, dalam hal Anda dengan melakukan:

myForEach(theList, Test::displayInt);

Pada akhirnya, Anda bahkan bisa menyingkirkan myForEachmetode Anda sama sekali dan cukup lakukan:

theList.forEach(Test::displayInt);

Tentang Fungsinya sebagai Warga Kelas Satu

Semua telah dikatakan, kebenarannya adalah bahwa Java 8 tidak akan memiliki fungsi sebagai warga negara kelas satu karena jenis fungsi struktural tidak akan ditambahkan ke bahasa. Java hanya akan menawarkan cara alternatif untuk membuat implementasi antarmuka fungsional dari ekspresi lambda dan referensi metode. Pada akhirnya ekspresi lambda dan referensi metode akan terikat pada referensi objek, oleh karena itu yang kita miliki adalah objek sebagai warga negara kelas satu. Yang penting adalah fungsionalitas yang ada karena kita dapat melewatkan objek sebagai parameter, mengikatnya ke referensi variabel dan mengembalikannya sebagai nilai dari metode lain, kemudian mereka cukup banyak melayani tujuan yang sama.

Edwin Dalorzo
sumber
1
Omong-omong, Blockdiubah untuk Consumerrilis terbaru JDK 8 API
Edwin Dalorzo
4
Paragraf terakhir membahas beberapa fakta penting: Ekspresi Lambda dan referensi metode lebih dari sekadar tambahan sintaksis. JVM itu sendiri menyediakan tipe baru untuk menangani referensi metode lebih efisien daripada dengan kelas anonim (yang paling penting MethodHandle), dan menggunakan ini, kompiler Java mampu menghasilkan kode yang lebih efisien, misalnya menerapkan lambdas tanpa penutup sebagai metode statis, sehingga mencegah pembentukan kelas tambahan.
Feuermurmel
7
Dan versi yang benar Function<Void, Void>adalah Runnable.
OrangeDog
2
@OrangeDog Ini tidak sepenuhnya benar. Dalam komentar Runnabledan Callableantarmuka tertulis bahwa mereka seharusnya digunakan bersama dengan utas . Konsekuensi yang mengganggu dari itu adalah bahwa beberapa alat analisis statis (seperti Sonar) akan mengeluh jika Anda memanggil run()metode secara langsung.
Denis
Berasal murni dari latar belakang Jawa, saya bertanya-tanya apa khususnya kasus di mana Java tidak dapat memperlakukan fungsi sebagai warga negara kelas satu bahkan setelah pengenalan antarmuka fungsional dan lambda sejak Java8?
Vivek Sethi
21

Ketika Anda perlu menerima fungsi sebagai argumen yang tidak menggunakan argumen dan tidak mengembalikan hasil (batal), menurut pendapat saya masih lebih baik untuk memiliki sesuatu seperti

  public interface Thunk { void apply(); }

di suatu tempat dalam kode Anda. Dalam kursus pemrograman fungsional saya, kata 'thunk' digunakan untuk menggambarkan fungsi-fungsi tersebut. Mengapa itu tidak ada di java.util.fungsi adalah di luar pemahaman saya.

Dalam kasus lain saya menemukan bahwa bahkan ketika java.util.function memang memiliki sesuatu yang cocok dengan tanda tangan yang saya inginkan - itu masih tidak selalu terasa benar ketika penamaan antarmuka tidak cocok dengan penggunaan fungsi dalam kode saya. Saya kira itu adalah poin yang sama yang dibuat di tempat lain di sini mengenai 'Runnable' - yang merupakan istilah yang terkait dengan kelas Thread - jadi meskipun mungkin dia harus membubuhkan tanda tangan yang saya butuhkan, masih mungkin membingungkan pembaca.

LOAS
sumber
Khususnya Runnabletidak mengizinkan pengecualian yang diperiksa, menjadikannya tidak berguna untuk banyak aplikasi. Karena Callable<Void>tidak berguna juga, Anda perlu mendefinisikan sendiri tampaknya. Jelek.
Jesse Glick
Mengacu pada masalah pengecualian yang diperiksa, fungsi-fungsi baru Java secara umum berjuang dengan itu. itu pemblokir besar untuk beberapa hal seperti Stream API. Sangat buruk mendesain pada bagian mereka.
user2223059
0

Setel jenis kembali ke Voidalih-alih voiddanreturn null

// Modify existing method
public static Void displayInt(Integer i) {
    System.out.println(i);
    return null;
}

ATAU

// Or use Lambda
myForEach(theList, i -> {System.out.println(i);return null;});
Dojo
sumber
-2

Saya rasa Anda harus menggunakan antarmuka Konsumen alih-alih Function<T, R>.

Konsumen pada dasarnya adalah antarmuka fungsional yang dirancang untuk menerima nilai dan tidak mengembalikan apa pun (mis. Batal)

Dalam kasus Anda, Anda dapat membuat konsumen di tempat lain dalam kode Anda seperti ini:

Consumer<Integer> myFunction = x -> {
    System.out.println("processing value: " + x);    
    .... do some more things with "x" which returns nothing...
}

Kemudian Anda dapat mengganti myForEachkode Anda dengan cuplikan di bawah ini:

public static void myForEach(List<Integer> list, Consumer<Integer> myFunction) 
{
  list.forEach(x->myFunction.accept(x));
}

Anda memperlakukan fungsi saya sebagai objek kelas satu.

Anthony Anyanwu
sumber
2
Apa yang ditambahkan di luar jawaban yang ada?
Matius Baca