Mulailah dengan referensi metode di Java

Bersama lambda, Java SE 8 membawa referensi metode ke bahasa Jawa. Tutorial ini menawarkan ikhtisar singkat tentang referensi metode di Java, lalu Anda mulai menggunakannya dengan contoh kode Java. Di akhir tutorial, Anda akan mengetahui cara menggunakan referensi metode untuk merujuk ke metode statis kelas, metode non-statis terikat dan tidak terikat, dan konstruktor, serta cara menggunakannya untuk merujuk ke metode instance di kelas super dan kelas saat ini. jenis. Anda juga akan memahami mengapa banyak pengembang Java mengadopsi ekspresi lambda dan referensi metode sebagai alternatif yang lebih bersih dan sederhana untuk kelas anonim.

Perhatikan bahwa contoh kode dalam tutorial ini kompatibel dengan JDK 12.

unduh Dapatkan kodenya Unduh kode sumber untuk aplikasi contoh dalam tutorial ini. Dibuat oleh Jeff Friesen untuk JavaWorld.

Referensi metode: Primer

Tutorial Java 101 saya sebelumnya memperkenalkan ekspresi lambda, yang digunakan untuk mendefinisikan metode anonim yang kemudian dapat diperlakukan sebagai contoh antarmuka fungsional. Terkadang, ekspresi lambda tidak lebih dari memanggil metode yang sudah ada. Misalnya, potongan kode berikut menggunakan lambda untuk memanggil System.out's void println(s)metode pada lambda ini argument-- tunggal s' s jenis belum diketahui:

(s) -> System.out.println(s)

Lambda disajikan (s)sebagai daftar parameter formal dan badan kode yang System.out.println(s)ekspresinya mencetak snilai ke aliran keluaran standar. Itu tidak memiliki tipe antarmuka eksplisit. Sebaliknya, kompilator menyimpulkan dari konteks sekitarnya yang antarmuka fungsional untuk dipakai. Misalnya, pertimbangkan fragmen kode berikut:

Consumer consumer = (s) -> System.out.println(s);

Kompilator menganalisis deklarasi sebelumnya dan menentukan bahwa metode java.util.function.Consumerantarmuka fungsional yang telah ditetapkan void accept(T t)cocok dengan daftar parameter formal lambda ( (s)). Hal ini juga menentukan bahwa accept()'s voidtipe kembali pertandingan println()' s voidtipe kembali. Lambda demikian terikat untuk Consumer.

Lebih khusus lagi, lambda terikat Consumer. Compiler menghasilkan kode sehingga sebuah doa Consumer's void accept(String s)hasil metode dalam argumen string dilewatkan ke sdilewatkan ke System.out' s void println(String s)metode. Doa ini ditunjukkan di bawah ini:

consumer.accept("Hello"); // Pass "Hello" to lambda body. Print Hello to standard output.

Untuk menyimpan penekanan tombol, Anda bisa mengganti lambda dengan referensi metode , yang merupakan referensi ringkas untuk metode yang ada. Sebagai contoh, berikut kode fragmen Menggantikan (String s) -> System.out.println(s)dengan System.out::println, di mana ::menandakan bahwa System.out's void println(String s)metode sedang direferensikan:

Consumer consumer2 = System.out::println; // The method reference is shorter. consumer2.accept("Hello"); // Pass "Hello" to lambda body. Print Hello to standard output.

Hal ini tidak perlu untuk menentukan daftar parameter formal untuk referensi metode sebelumnya karena compiler dapat menyimpulkan daftar ini didasarkan pada Consumerjenis parameter ini ini java.lang.StringMenggantikan jenis argumen yang sebenarnya Tdi void accept(T t), dan juga jenis parameter tunggal dalam lambda tubuh System.out.println()metode panggilan.

Referensi metode secara mendalam

Sebuah referensi metode adalah cara pintas sintaksis untuk menciptakan lambda dari metode yang ada. Alih-alih menyediakan badan implementasi, referensi metode merujuk ke metode objek atau kelas yang ada. Seperti lambda, referensi metode memerlukan jenis target.

Anda dapat menggunakan referensi metode untuk merujuk ke metode statis kelas, metode non-statis terikat dan tidak terikat, dan konstruktor. Anda juga dapat menggunakan referensi metode untuk merujuk ke metode instance dalam superclass dan tipe kelas saat ini. Saya akan memperkenalkan Anda pada masing-masing kategori referensi metode ini dan menunjukkan bagaimana mereka digunakan dalam demo kecil.

Pelajari lebih lanjut tentang referensi metode

Setelah membaca bagian ini, lihat Referensi Metode di Java 8 (Toby Weston, Februari 2014) untuk lebih banyak wawasan tentang referensi metode dalam konteks metode non-statis terikat dan tidak terikat.

Referensi ke metode statis

Sebuah referensi metode statis mengacu pada metode statis di kelas tertentu. Sintaksnya adalah , di mana mengidentifikasi kelas dan mengidentifikasi metode statis. Contohnya adalah . Kode 1 menunjukkan referensi metode statis.className::staticMethodNameclassNamestaticMethodNameInteger::bitCount

Daftar 1. MRDemo.java (versi 1)

import java.util.Arrays; import java.util.function.Consumer; public class MRDemo { public static void main(String[] args) { int[] array = { 10, 2, 19, 5, 17 }; Consumer consumer = Arrays::sort; consumer.accept(array); for (int i = 0; i < array.length; i++) System.out.println(array[i]); System.out.println(); int[] array2 = { 19, 5, 14, 3, 21, 4 }; Consumer consumer2 = (a) -> Arrays.sort(a); consumer2.accept(array2); for (int i = 0; i < array2.length; i++) System.out.println(array2[i]); } }

main()Metode Listing 1 mengurutkan sepasang array integer melalui metode java.util.Arrayskelas static void sort(int[] a), yang muncul dalam referensi metode statis dan konteks ekspresi lambda yang setara. Setelah mengurutkan array, sebuah forloop mencetak konten array yang diurutkan ke aliran keluaran standar.

Sebelum kita bisa menggunakan referensi metode atau lambda, itu harus terikat ke antarmuka fungsional. Saya menggunakan Consumerantarmuka fungsional yang telah ditetapkan , yang memenuhi persyaratan referensi / lambda metode. The dimulai operasi semacam dengan melewati array yang akan diurutkan ke Consumer's accept()metode.

Kompilasi Listing 1 ( javac MRDemo.java) dan jalankan application ( java MRDemo). Anda akan mengamati keluaran berikut:

2 5 10 17 19 3 4 5 14 19 21

Referensi ke metode non-statis terikat

Sebuah metode non-statis referensi terikat mengacu pada metode non-statis yang terikat ke penerima objek. Sintaksnya adalah , di mana mengidentifikasi penerima dan mengidentifikasi metode instance. Contohnya adalah . Kode 2 menunjukkan referensi metode non-statis terikat.objectName::instanceMethodNameobjectNameinstanceMethodNames::trim

Kode 2. MRDemo.java (versi 2)

import java.util.function.Supplier; public class MRDemo { public static void main(String[] args) { String s = "The quick brown fox jumped over the lazy dog"; print(s::length); print(() -> s.length()); print(new Supplier() { @Override public Integer get() { return s.length(); // closes over s } }); } public static void print(Supplier supplier) { System.out.println(supplier.get()); } }

main()Metode Listing 2 memberikan string ke Stringvariabel sdan kemudian memanggil print()metode kelas dengan fungsionalitas untuk mendapatkan panjang string ini sebagai argumen metode ini. print()dipanggil dalam referensi metode ( s::length- length()terikat ke s), lambda setara, dan konteks kelas anonim yang setara.

Saya telah menetapkan print()untuk menggunakan java.util.function.Supplierantarmuka fungsional yang telah ditentukan sebelumnya, yang get()metodenya mengembalikan pemasok hasil. Dalam hal ini, Supplierinstance yang diteruskan untuk print()mengimplementasikan get()metode pengembaliannya s.length(); print()menghasilkan panjang ini.

s::lengthmemperkenalkan penutupan yang menutup s. Anda dapat melihat ini lebih jelas di contoh lambda. Karena lambda tidak memiliki argumen, nilai shanya tersedia dari cakupan yang melingkupi. Oleh karena itu, tubuh lambda adalah penutup yang tertutup s. Contoh kelas anonim membuatnya semakin jelas.

Kompilasi Listing 2 dan jalankan aplikasi. Anda akan mengamati keluaran berikut:

44 44 44

Referensi ke metode non-statis tak terikat

Sebuah metode non-statis referensi terikat mengacu pada metode non-statis yang tidak terikat ke objek penerima. Sintaksnya adalah , di mana mengidentifikasi kelas yang mendeklarasikan metode instance dan mengidentifikasi metode instance. Contohnya adalah .className::instanceMethodNameclassNameinstanceMethodNameString::toLowerCase

String::toLowerCase is an unbound non-static method reference that identifies the non-static String toLowerCase() method of the String class. However, because a non-static method still requires a receiver object (in this example a String object, which is used to invoke toLowerCase() via the method reference), the receiver object is created by the virtual machine. toLowerCase() will be invoked on this object. String::toLowerCase specifies a method that takes a single String argument, which is the receiver object, and returns a String result. String::toLowerCase() is equivalent to lambda (String s) -> { return s.toLowerCase(); }.

Listing 3 demonstrates this unbound non-static method reference.

Listing 3. MRDemo.java (version 3)

import java.util.function.Function; public class MRDemo { public static void main(String[] args) { print(String::toLowerCase, "STRING TO LOWERCASE"); print(s -> s.toLowerCase(), "STRING TO LOWERCASE"); print(new Function() { @Override public String apply(String s) // receives argument in parameter s; { // doesn't need to close over s return s.toLowerCase(); } }, "STRING TO LOWERCASE"); } public static void print(Function function, String s) { System.out.println(function.apply(s)); } }

Listing 3's main() method invokes the print() class method with functionality to convert a string to lowercase and the string to be converted as the method's arguments. print() is invoked in method reference (String::toLowerCase, where toLowerCase() isn't bound to a user-specified object) and equivalent lambda and anonymous class contexts.

I've defined print() to use the java.util.function.Function predefined functional interface, which represents a function that accepts one argument and produces a result. In this case, the Function instance passed to print() implements its R apply(T t) method to return s.toLowerCase(); print() outputs this string.

Although the String part of String::toLowerCase makes it look like a class is being referenced, only an instance of this class is referenced. The anonymous class example makes this more obvious. Note that in the anonymous class example the lambda receives an argument; it doesn't close over parameter s (i.e., it's not a closure).

Compile Listing 3 and run the application. You'll observe the following output:

string to lowercase string to lowercase string to lowercase

References to constructors

You can use a method reference to refer to a constructor without instantiating the named class. This kind of method reference is known as a constructor reference. Its syntax is className::new. className must support object creation; it cannot name an abstract class or interface. Keyword new names the referenced constructor. Here are some examples:

  • Character::new: equivalent to lambda (Character ch) -> new Character(ch)
  • Long::new: equivalent to lambda (long value) -> new Long(value) or (String s) -> new Long(s)
  • ArrayList::new: equivalent to lambda () -> new ArrayList()
  • float[]::new: equivalent to lambda (int size) -> new float[size]

The last constructor reference example specifies an array type instead of a class type, but the principle is the same. The example demonstrates an array constructor reference to the "constructor" of an array type.

To create a constructor reference, specify new without a constructor. When a class such as java.lang.Long declares multiple constructors, the compiler compares the functional interface's type against all of the constructors and chooses the best match. Listing 4 demonstrates a constructor reference.

Listing 4. MRDemo.java (version 4)

import java.util.function.Supplier; public class MRDemo { public static void main(String[] args) { Supplier supplier = MRDemo::new; System.out.println(supplier.get()); } }

Listing 4's MRDemo::new constructor reference is equivalent to lambda () -> new MRDemo(). Expression supplier.get() executes this lambda, which invokes MRDemo's default no-argument constructor and returns the MRDemo object, which is passed to System.out.println(). This method converts the object to a string, which it prints.

Now suppose you have a class with a no-argument constructor and a constructor that takes an argument, and you want to call the constructor that takes an argument. You can accomplish this task by choosing a different functional interface, such as the predefined Function interface shown in Listing 5.

Listing 5. MRDemo.java (version 5)

import java.util.function.Function; public class MRDemo { private String name; MRDemo() { name = ""; } MRDemo(String name) { this.name = name; System.out.printf("MRDemo(String name) called with %s%n", name); } public static void main(String[] args) { Function function = MRDemo::new; System.out.println(function.apply("some name")); } }

Function function = MRDemo::new;menyebabkan compiler untuk mencari konstruktor yang mengambil sebuah Stringargumen, karena Function's apply()metode memerlukan tunggal (dalam konteks ini) Stringargumen. function.apply("some name")Hasil eksekusi "some name"diteruskan ke MRDemo(String name).