התחל בעבודה עם הפניות לשיטות ב- Java

יחד עם lambdas, Java SE 8 הביא הפניות לשיטה לשפת Java. הדרכה זו מציעה סקירה קצרה של הפניות לשיטות ב- Java, ואז תתחיל להשתמש בהן עם דוגמאות קוד Java. בסוף ההדרכה תוכלו לדעת כיצד להשתמש בהפניות לשיטות בכדי להתייחס לשיטות סטטיות של כיתה, לשיטות לא סטטיות מאוגדות ובלתי מאוגדות, ולבנות, וכן כיצד להשתמש בהן כדי להתייחס לשיטות מופע בסופר-קלאס ובמחלקה הנוכחית. סוגים. תוכלו גם להבין מדוע מפתחי ג'אווה רבים אימצו ביטויים למבדה והפניות לשיטה כאלטרנטיבה נקייה ופשוטה יותר לשיעורים אנונימיים.

שים לב כי דוגמאות קוד במדריך זה תואמות ל- JDK 12.

הורד קבל את הקוד הורד את קוד המקור למשל יישומים במדריך זה. נוצר על ידי ג'ף פרייזן עבור JavaWorld.

הפניות לשיטה: פריימר

ההדרכה הקודמת שלי ב- Java 101 הציגה ביטויים של lambda, המשמשים להגדרת שיטות אנונימיות שניתן להתייחס אליהן כאל מקרים של ממשק פונקציונלי. לפעמים, ביטוי למבדה לא עושה יותר מאשר לקרוא לשיטה קיימת. לדוגמא, שבר הקוד הבא משתמש בלמדה כדי להפעיל System.outאת void println(s)השיטה על הטיעון היחיד של הלמדה s- הסוג עדיין לא ידוע:

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

הלמבדה מציגה (s)את רשימת הפרמטרים הרשמית שלה וגוף קוד System.out.println(s)שהביטוי שלו מדפיס sאת הערך לזרם הפלט הרגיל. אין לו ממשק מפורש. במקום זאת, המהדר מסיק מההקשר שמסביב איזה ממשק פונקציונלי להתקין. לדוגמה, שקול את קטע הקוד הבא:

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

המהדר מנתח את ההצהרה הקודמת וקובע כי שיטת java.util.function.Consumerהממשק הפונקציונלי שהוגדר מראש void accept(T t)תואמת לרשימת הפרמטרים הפורמלית של הלמדה ( (s)). כמו כן קובע כי accept()"s voidגפרורי סוג החזרה println()של" voidטיפוס החזרה. למבדה מחויבים לפיכך ל Consumer.

באופן ספציפי יותר, הלמבה חייבת Consumer. המהדר מייצר קוד כך תמסכו של Consumerשל void accept(String s)תוצאות שיטת טיעון המחרוזת עבר sמועבר System.outשל void println(String s)השיטה. קריאה זו מוצגת להלן:

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

כדי לשמור הקשות, אתה יכול להחליף את הלמבה בהפניה לשיטה , שהיא התייחסות קומפקטית לשיטה קיימת. לדוגמה, שבר הקוד הבא מתחלף (String s) -> System.out.println(s)ב- System.out::println, כאשר ::מסמל System.outאת void println(String s)הפניה לשיטה זו:

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

אין צורך לציין רשימת פרמטרים רשמית להתייחסות לשיטה הקודמת מכיוון שהמהדר יכול להסיק רשימה זו בהתבסס על ארגומנט הסוג הממשי Consumerשל פרמטר זה java.lang.Stringהמחליף Tב void accept(T t), והוא גם סוג הפרמטר היחיד System.out.println()בקריאת השיטה של גוף הלמדה .

הפניות לעומק השיטה

פניית שיטה היא קיצור דרך תחבירית ליצירת למבדה מתוך שיטה קיימת. במקום לספק גוף יישום, התייחסות לשיטה מתייחסת לשיטת מחלקה או אובייקט קיימת. כמו בממדה, הפניה לשיטה דורשת סוג יעד.

אתה יכול להשתמש בהפניות לשיטות כדי להתייחס לשיטות סטטיות של כיתה, שיטות לא סטטיות מאוגדות ובלתי מאוגדות, ובונים. אתה יכול גם להשתמש בהפניות לשיטות כדי להתייחס לשיטות מופע בסוגי מחלקה-על וסוגי מחלקות נוכחיים. אציג בפניך את כל אחת מקטגוריות ההפניה לשיטות אלה ואראה כיצד הן משמשות בהדגמה קטנה.

למידע נוסף על הפניות לשיטות

לאחר קריאת סעיף זה, עיין בהפניות לשיטות ב- Java 8 (טובי ווסטון, פברואר 2014) לקבלת תובנות נוספות לגבי הפניות לשיטות בהקשרים של שיטות לא סטטיות מאוגדות ולא מאוגדות.

הפניות לשיטות סטטיות

התייחסות שיטה סטטית מתייחסת שיטה סטטית בכיתה מסוימת. התחביר שלו הוא , היכן שמזהה את המחלקה ומזהה את השיטה הסטטית. דוגמה היא . רישום 1 מדגים התייחסות לשיטה סטטית.className::staticMethodNameclassNamestaticMethodNameInteger::bitCount

רישום 1. MRDemo.java (גרסה 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()שיטת הרישום 1 ממיינת זוג מערכים שלמים באמצעות שיטת java.util.Arraysהכיתה static void sort(int[] a), המופיעה בהתייחסות לשיטה סטטית ובהקשרים של ביטוי למבדה שווה ערך. לאחר מיון מערך, forלולאה מדפיסה את תוכן המערך הממוין לזרם הפלט הסטנדרטי.

לפני שנוכל להשתמש בהתייחסות לשיטה או למבדה, עליה להיות קשורה לממשק פונקציונלי. אני משתמש Consumerבממשק הפונקציונלי המוגדר מראש , העונה על דרישות ההתייחסות / למבדה. מתחילה פעולת מיון על ידי העברת המערך להיות מסודר כדי Consumers" accept()שיטה.

הידור רישום 1 ( javac MRDemo.java) והפעל את היישום ( java MRDemo). תצפה על הפלט הבא:

2 5 10 17 19 3 4 5 14 19 21

הפניות לשיטות לא סטטיות מאוגדות

פניית שיטה הלא-סטטי כבול מתייחסת לשיטה הלא-סטטי זה מאוגד מקלט אובייקט. התחביר שלו הוא , היכן שמזהה את המקלט ומזהה את שיטת המופע. דוגמה היא . רישום 2 מדגים התייחסות לשיטה שאינה סטטית.objectName::instanceMethodNameobjectNameinstanceMethodNames::trim

רישום 2. MRDemo.java (גרסה 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()שיטת הרישום 2 מקצה מחרוזת Stringלמשתנה sואז קוראת print()לשיטת המחלקה עם פונקציונליות כדי לקבל את אורך המחרוזת כארגומנט של שיטה זו. print()מופעל בהתייחסות לשיטה ( s::length- length()מחויב s), למבדה שווה ערך, ולהקשרים מחלקתיים אנונימיים מקבילים.

הגדרתי print()להשתמש java.util.function.Supplierבממשק הפונקציונלי שהוגדר מראש, ששיטתו get()מחזירה ספק תוצאות. במקרה זה, Supplierהמופע שהועבר print()ליישם את get()שיטתו לחזור s.length(); print()פלט אורך זה.

s::lengthמציג סגירה שנסגרת s. אתה יכול לראות את זה בצורה ברורה יותר בדוגמא למבדה. מכיוון שאין למבדה טענות, הערך של sזמין רק מההיקף הסוגר. לכן, גוף הלמבדה הוא סגירה שנסגרת s. דוגמת הכיתה האנונימית מבהירה זאת עוד יותר.

הידור רישום 2 והפעל את היישום. תצפה על הפלט הבא:

44 44 44

הפניות לשיטות לא סטטיות בלתי מאוגדות

פניית שיטה הלא-סטטי מאוגדת מתייחסת לשיטה הלא-סטטי זה לא קשור לאובייקט מקבל. התחביר שלה הוא , היכן שמזהה את המחלקה שמצהירה על שיטת המופע ומזהה את שיטת המופע. דוגמה היא .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;גורם למהדר לחפש בנאי שלוקח Stringטיעון, כי Functionים" apply()שיטה מחייבת יחיד (בהקשר הזה) Stringהטיעון. ביצוע function.apply("some name")התוצאות במעבר "some name"ל MRDemo(String name).