Mulakan dengan kaedah rujukan di Java

Bersama dengan lambdas, Java SE 8 membawa rujukan kaedah ke bahasa Java. Tutorial ini memberikan gambaran ringkas mengenai rujukan kaedah di Java, dan kemudian Anda mulai menggunakannya dengan contoh kod Java. Pada akhir tutorial, anda akan mengetahui cara menggunakan rujukan kaedah untuk merujuk kepada kaedah statik kelas, kaedah tidak statik terikat dan tidak terikat, dan pembina, serta cara menggunakannya untuk merujuk kepada kaedah contoh di kelas super dan kelas semasa jenis. Anda juga akan memahami mengapa banyak pembangun Java menggunakan ungkapan lambda dan rujukan kaedah sebagai alternatif yang lebih bersih dan sederhana daripada kelas tanpa nama.

Perhatikan bahawa contoh kod dalam tutorial ini sesuai dengan JDK 12.

muat turun Dapatkan kod Muat turun kod sumber misalnya aplikasi dalam tutorial ini. Dicipta oleh Jeff Friesen untuk JavaWorld.

Rujukan kaedah: Primer

Tutorial Java 101 saya yang lalu memperkenalkan ungkapan lambda, yang digunakan untuk menentukan kaedah tanpa nama yang kemudian dapat dianggap sebagai contoh antara muka yang berfungsi. Kadang kala, ungkapan lambda tidak lebih daripada memanggil kaedah yang ada. Sebagai contoh, kod serpihan berikut menggunakan lambda yang sembah System.out's void println(s)kaedah di argument-- tunggal lambda yang s' s jenis belum diketahui:

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

Lambda hadir (s)sebagai senarai parameter formal dan badan kod yang System.out.println(s)ekspresinya mencetak snilai ke aliran output standard. Ia tidak mempunyai jenis antara muka yang jelas. Sebaliknya, penyusun menyimpang dari konteks sekitarnya yang antara muka fungsional untuk dijadikan contoh. Sebagai contoh, pertimbangkan pecahan kod berikut:

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

Penyusun menganalisis deklarasi sebelumnya dan menentukan bahawa kaedah java.util.function.Consumerantara muka fungsional yang telah ditentukan void accept(T t)sesuai dengan senarai parameter formal lambda ( (s)). Ia juga menentukan bahawa accept()'s voidjenis pulangan perlawanan println()' s voidjenis pulangan. Lambda ini dengan itu terikat kepada Consumer.

Lebih khusus lagi, lambda terikat Consumer. Pengkompil menjana kod supaya satu doa Consumer's void accept(String s)keputusan kaedah dalam hujah rentetan diserahkan kepada syang dihantar ke System.out' s void println(String s)kaedah. Permintaan ini ditunjukkan di bawah:

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

Untuk menyimpan ketukan kekunci, anda boleh mengganti lambda dengan rujukan kaedah , yang merupakan rujukan ringkas untuk kaedah yang ada. Sebagai contoh, berikut Menggantikan kod serpihan (String s) -> System.out.println(s)dengan System.out::println, di mana ::melambangkan bahawa System.out's void println(String s)kaedah sedang dirujuk:

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

Ia tidak perlu untuk menentukan senarai parameter formal untuk rujukan kaedah sebelumnya kerana pengkompil boleh membuat kesimpulan senarai ini berdasarkan Consumerjenis ini parameterized ini java.lang.StringMenggantikan jenis hujah sebenar Tdalam void accept(T t), dan juga jenis parameter tunggal dalam badan lambda yang System.out.println()panggilan kaedah.

Kaedah merujuk secara mendalam

A rujukan kaedah adalah jalan pintas sintaktik untuk mewujudkan lambda dari kaedah yang sedia ada. Daripada menyediakan badan pelaksanaan, rujukan kaedah merujuk kepada kaedah kelas atau objek yang ada. Seperti lambda, rujukan kaedah memerlukan jenis sasaran.

Anda boleh menggunakan rujukan kaedah untuk merujuk kepada kaedah statik kelas, kaedah bukan statik terikat dan tidak terikat, dan pembina. Anda juga boleh menggunakan rujukan kaedah untuk merujuk kepada kaedah contoh dalam kelas superclass dan kelas semasa. Saya akan memperkenalkan anda kepada setiap kategori rujukan kaedah ini dan menunjukkan bagaimana ia digunakan dalam demo kecil.

Ketahui lebih lanjut mengenai rujukan kaedah

Setelah membaca bahagian ini, lihat Kaedah Rujukan di Java 8 (Toby Weston, Februari 2014) untuk mendapatkan lebih banyak pandangan mengenai rujukan kaedah dalam konteks kaedah tidak statik yang terikat dan tidak terikat.

Rujukan kaedah statik

A rujukan kaedah statik merujuk kepada kaedah statik dalam kelas tertentu. Sintaksnya adalah , di mana mengenal pasti kelas dan mengenal pasti kaedah statik. Contohnya ialah . Penyenaraian 1 menunjukkan rujukan kaedah statik.className::staticMethodNameclassNamestaticMethodNameInteger::bitCount

Penyenaraian 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()Kaedah cantuman 1 menyusun sepasang susunan integer melalui kaedah java.util.Arrayskelas static void sort(int[] a), yang muncul dalam rujukan kaedah statik dan konteks ekspresi lambda yang setara. Setelah menyusun array, satu forgelung akan mencetak isi susunan yang disusun ke aliran output standard.

Sebelum kita dapat menggunakan rujukan kaedah atau lambda, ia mesti terikat pada antara muka yang berfungsi. Saya menggunakan Consumerantara muka fungsional yang telah ditentukan , yang memenuhi syarat rujukan kaedah / lambda. Yang memulakan operasi semacam dengan melepaskan keselesaan yang hendak disusun untuk Consumer's accept()kaedah.

Susun Penyenaraian 1 ( javac MRDemo.java) dan jalankan aplikasi ( java MRDemo). Anda akan melihat output berikut:

2 5 10 17 19 3 4 5 14 19 21

Rujukan kepada kaedah tidak statik terikat

A terikat bukan statik rujukan kaedah merujuk kepada satu kaedah yang tidak statik yang terikat kepada penerima objek. Sintaksnya adalah , di mana mengenal pasti penerima dan mengenal pasti kaedah contoh. Contohnya ialah . Penyenaraian 2 menunjukkan rujukan kaedah bukan statik terikat.objectName::instanceMethodNameobjectNameinstanceMethodNames::trim

Penyenaraian 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()Kaedah penyenaraian 2 memberikan rentetan kepada Stringpemboleh ubah sdan kemudian menggunakan print()kaedah kelas dengan fungsi untuk mendapatkan panjang tali ini sebagai hujah kaedah ini. print()dipanggil dalam kaedah rujukan ( s::length- length()terikat kepada s), lambda setara, dan konteks kelas tanpa nama setara.

Saya telah menetapkan print()untuk menggunakan java.util.function.Supplierantara muka fungsional yang telah ditentukan, yang get()kaedahnya mengembalikan pembekal hasil. Dalam kes ini, Suppliercontoh diteruskan untuk print()menerapkan get()kaedahnya untuk kembali s.length(); print()menghasilkan panjang ini.

s::lengthmemperkenalkan penutupan yang ditutup s. Anda dapat melihatnya dengan lebih jelas dalam contoh lambda. Oleh kerana lambda tidak mempunyai argumen, nilai shanya tersedia dari ruang lingkup lampiran. Oleh itu, badan lambda adalah penutup yang ditutup s. Contoh kelas tanpa nama menjadikan ini lebih jelas.

Susun Penyenaraian 2 dan jalankan aplikasi. Anda akan melihat output berikut:

44 44 44

Rujukan kaedah tidak statik yang tidak terikat

Yang tak terbatas bukan statik rujukan kaedah merujuk kepada satu kaedah yang tidak statik yang tidak terikat kepada objek penerima. Sintaksnya adalah , di mana mengenal pasti kelas yang menyatakan kaedah contoh dan mengenal pasti kaedah contoh. Contohnya ialah .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 pengkompil untuk mencari pembina yang mengambil Stringhujah, kerana Function's apply()Cara memerlukan tunggal (dalam konteks ini) Stringhujah. Melaksanakan function.apply("some name")hasil "some name"diwariskan MRDemo(String name).