Programmering

Kom godt i gang med metodereferencer i Java

Sammen med lambdas bragte Java SE 8 metodehenvisninger til Java-sproget. Denne tutorial giver en kort oversigt over metodereferencer i Java, hvorefter du kommer i gang med at bruge dem med Java-kodeeksempler. Ved afslutningen af ​​vejledningen ved du, hvordan du bruger metodereferencer til at henvise til klassens statiske metoder, bundne og ubundet ikke-statiske metoder og konstruktører, samt hvordan du bruger dem til at henvise til instansmetoder i superklasse og nuværende klasse typer. Du forstår også, hvorfor mange Java-udviklere har vedtaget lambda-udtryk og metodereferencer som et renere, enklere alternativ til anonyme klasser.

Bemærk, at kodeeksempler i denne vejledning er kompatible med JDK 12.

download Hent koden Download kildekoden for eksempel applikationer i denne vejledning. Oprettet af Jeff Friesen til JavaWorld.

Metodehenvisninger: En primer

Min tidligere Java 101-tutorial introducerede lambda-udtryk, som bruges til at definere anonyme metoder, der derefter kan behandles som forekomster af en funktionel grænseflade. Nogle gange gør et lambda-udtryk intet mere end at kalde en eksisterende metode. For eksempel bruger følgende kodefragment en lambda til at påberåbe sig System.out's ugyldige println (er) metode på lambdas eneste argument--stype er endnu ikke kendt:

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

Lambda præsenterer (s) som sin formelle parameterliste og et kodeorgan, hvis System.out.println (s) udtryk udskrives s's værdi til standard output stream. Det har ikke en eksplicit interface-type. I stedet udleder compileren fra den omgivende kontekst, hvilken funktionel grænseflade der skal instantieres. Overvej f.eks. Følgende kodefragment:

Forbrugerforbruger = (s) -> System.out.println (s);

Kompilatoren analyserer den tidligere erklæring og bestemmer, at java.util.function.Consumer foruddefinerede funktionelle grænseflader ugyldig accept (T t) metode matcher lambdas formelle parameterliste ((s)). Det bestemmer også det acceptere()'s ugyldig returneringstype matches println ()'s ugyldig returtype. Lambda er således bundet til Forbruger.

Mere specifikt er lambda bundet til Forbruger. Compileren genererer kode, så en påkaldelse af Forbruger's ugyldig accept (streng s) metode resulterer i strengargumentet sendt til s bliver videregivet til System.out's ugyldig println (streng s) metode. Denne påkaldelse er vist nedenfor:

forbruger.accept ("Hej"); // Giv "Hello" til lambda-kroppen. Udskriv Hej til standardoutput.

For at gemme tastetryk kan du erstatte lambda med en metodehenvisning, som er en kompakt henvisning til en eksisterende metode. For eksempel erstatter følgende kodefragment (Streng (er)) -> System.out.println (s) med System.out :: println, hvor :: betyder det System.out's ugyldig println (streng s) der henvises til metode:

Forbrugerforbruger2 = System.out :: println; // Metodehenvisningen er kortere. forbruger2.accept ("Hej"); // Giv "Hej" til lambdakroppen. Udskriv Hej til standardoutput.

Det er ikke nødvendigt at angive en formel parameterliste til den tidligere metodehenvisning, fordi compileren kan udlede denne liste baseret på Forbruger Denne parametriserede type er java.lang.Streng faktiske type argument erstatter T i ugyldig accept (T t), og er også typen af ​​den enkelte parameter i lambdakroppen System.out.println () metodeopkald.

Metodhenvisninger i dybden

EN metodehenvisning er en syntaktisk genvej til oprettelse af en lambda fra en eksisterende metode. I stedet for at tilvejebringe et implementeringsorgan henviser en metodehenvisning til en eksisterende klasses eller objekts metode. Som med en lambda kræver en metodehenvisning en måltype.

Du kan bruge metodereferencer til at henvise til klassens statiske metoder, bundne og ikke-bundne ikke-statiske metoder og konstruktører. Du kan også bruge metodereferencer til at henvise til instansmetoder i superklasse og aktuelle klassetyper. Jeg introducerer dig til hver af disse metodereferencekategorier og viser, hvordan de bruges i en lille demo.

Lær mere om metodereferencer

Efter at have læst dette afsnit, tjek Metodehenvisninger i Java 8 (Toby Weston, februar 2014) for mere indsigt i metodereferencer i bundne og ubundne ikke-statiske metodesammenhænge.

Henvisninger til statiske metoder

EN statisk metodehenvisning henviser til en statisk metode i en bestemt klasse. Dens syntaks er className::staticMethodName, hvor className identificerer klassen og staticMethodName identificerer den statiske metode. Et eksempel er Heltal :: bitCount. Liste 1 viser en statisk metodehenvisning.

Liste 1. MRDemo.java (version 1)

importere java.util.Arrays; importere java.util.function.Consumer; public class MRDemo {public static void main (String [] args) {int [] array = {10, 2, 19, 5, 17}; Forbrugerforbrugere = Arrays :: sort; forbruger.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}; Forbrugerforbruger2 = (a) -> Arrays.sort (a); forbruger2.accept (array2); for (int i = 0; i <array2.length; i ++) System.out.println (array2 [i]); }}

Liste 1's hoved () metoden sorterer et par heltal arrays via java.util.Arrays klassens statisk tomrums sort (int [] a) metode, som vises i statisk metodehenvisning og ækvivalente lambda-ekspressionskontekster. Efter sortering af en matrix, a til loop udskriver det sorterede matrixindhold til standardoutputstrømmen.

Før vi kan bruge en metodereference eller en lambda, skal den være bundet til en funktionel grænseflade. Jeg bruger det foruddefinerede Forbruger funktionel grænseflade, der opfylder metodereferencen / lambda-kravene. Sorteringsoperationen begynder ved at overføre det array, der skal sorteres til Forbruger's acceptere() metode.

Kompilere lister 1 (javac MRDemo.java) og kør applikationen (java MRDemo). Du observerer følgende output:

2 5 10 17 19 3 4 5 14 19 21

Henvisninger til bundne ikke-statiske metoder

EN bundet ikke-statisk metodehenvisning henviser til en ikke-statisk metode, der er bundet til en modtager objekt. Dens syntaks er objektnavn::instansMetodenavn, hvor objektnavn identificerer modtageren og instansMetodenavn identificerer instansmetoden. Et eksempel er s :: trim. Liste 2 viser en bundet ikke-statisk metodehenvisning.

Liste 2. MRDemo.java (version 2)

importere java.util.function.Supplier; public class MRDemo {public static void main (String [] args) {String s = "Den hurtige brune ræv sprang over den dovne hund"; print (s :: længde); udskrive (() -> s.længde ()); udskriv (ny leverandør () {@ Override public Integer get () {return s.length (); // lukker over s}}); } offentlig statisk ugyldig udskrift (leverandørleverandør) {System.out.println (provider.get ()); }}

Annonce 2'er hoved () metoden tildeler en streng til Snor variabel s og derefter påberåber sig Print() klassemetode med funktionalitet for at opnå denne strenglængde som denne metodes argument. Print() påberåbes i metodehenvisning (s :: længde -- længde () er bundet til s), ækvivalent lambda og tilsvarende anonyme klassekontekster.

Jeg har defineret Print() at bruge java.util.function.Supplier foruddefineret funktionel grænseflade, hvis få() metode returnerer en leverandør af resultater. I dette tilfælde er Leverandør instans videregivet til Print() implementerer dens få() metode til at vende tilbage s.længde (); Print() output denne længde.

s :: længde introducerer en lukning, der lukker over s. Du kan se dette mere tydeligt i lambda-eksemplet. Fordi lambda ikke har nogen argumenter, værdien af s er kun tilgængelig fra det vedlagte anvendelsesområde. Derfor er lambdakroppen en lukning, der lukker over s. Det anonyme klasseeksempel gør dette endnu tydeligere.

Kompilér liste 2 og kør applikationen. Du observerer følgende output:

44 44 44

Henvisninger til ikke-bundne ikke-statiske metoder

En ubundet ikke-statisk metodehenvisning henviser til en ikke-statisk metode, der ikke er bundet til et modtagerobjekt. Dens syntaks er className::instansMetodenavn, hvor className identificerer den klasse, der erklærer instansmetoden og instansMetodenavn identificerer instansmetoden. Et eksempel er String :: toLowerCase.

String :: toLowerCase er en ubundet ikke-statisk metodehenvisning, der identificerer den ikke-statiske String toLowerCase () metode til Snor klasse. Men fordi en ikke-statisk metode stadig kræver et modtagerobjekt (i dette eksempel a Snor objekt, som bruges til at påberåbe sig toLowerCase () via metodehenvisningen) oprettes modtagerobjektet af den virtuelle maskine. toLowerCase () vil blive påberåbt sig på dette objekt. String :: toLowerCase angiver en metode, der tager en enkelt Snor argument, som er modtagerobjektet, og returnerer a Snor resultat. String :: toLowerCase () svarer til lambda (Streng s) -> {returner s.toLowerCase (); }.

Liste 3 demonstrerer denne ubundet ikke-statisk metodehenvisning.

Liste 3. MRDemo.java (version 3)

importere 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"); udskriv (ny funktion () {@ Override public String gælder (String s) // modtager argument i parameter s; {// behøver ikke at lukke over s returnere s.toLowerCase ();}}, "STRING TO LOWERCASE" ); } offentlig statisk tomrumsudskrivning (Funktionsfunktion, String s) {System.out.println (function.apply (s)); }}

Liste 3's hoved () metode påberåber sig Print() klassemetode med funktionalitet til at konvertere en streng til små bogstaver og den streng, der skal konverteres som metodens argumenter. Print() påberåbes i metodehenvisning (String :: toLowerCase, hvor toLowerCase () er ikke bundet til et brugerdefineret objekt) og tilsvarende lambda og anonyme klassekontekster.

Jeg har defineret Print() at bruge java.util.function.Function foruddefineret funktionel grænseflade, som repræsenterer en funktion, der accepterer et argument og producerer et resultat. I dette tilfælde er Fungere instans videregivet til Print() implementerer dens R gælder (T t) metode til at vende tilbage s.toLowerCase (); Print() output denne streng.

Selvom Snor del af String :: toLowerCase får det til at ligne, at der refereres til en klasse, kun der henvises til en forekomst af denne klasse. Det anonyme klasseeksempel gør dette mere indlysende. Bemærk, at lambda modtager et argument i det anonyme klasseeksempel; det lukker ikke over parameteren s (dvs. det er ikke en lukning).

Kompilér liste 3, og kør applikationen. Du observerer følgende output:

streng til små bogstaver streng til små bogstaver til små bogstaver

Henvisninger til konstruktører

Du kan bruge en metodereference til at henvise til en konstruktør uden at instantiere den navngivne klasse. Denne form for metodehenvisning er kendt som en konstruktionsreference. Dens syntaks er className::ny. className skal understøtte oprettelse af objekter det kan ikke navngive en abstrakt klasse eller grænseflade. Nøgleord ny navngiver den refererede konstruktør. Her er nogle eksempler:

  • Tegn :: nyt: svarer til lambda (Character ch) -> new Character (ch)
  • Lang :: ny: svarer til lambda (lang værdi) -> ny Lang (værdi) eller (String s) -> nye Long (s)
  • ArrayList :: nyt: svarer til lambda () -> ny ArrayList ()
  • flyde [] :: ny: svarer til lambda (int størrelse) -> ny float [størrelse]

Det sidste konstruktionsreferenceeksempel angiver en matrixtype i stedet for en klassetype, men princippet er det samme. Eksemplet viser en array konstruktør reference til "konstruktøren" af en matrixtype.

Angiv for at oprette en konstruktionsreference ny uden konstruktør. Når en klasse som f.eks java.lang. lang erklærer flere konstruktører, sammenligner compileren den funktionelle grænseflades type med alle konstruktørerne og vælger det bedste match. Liste 4 viser en konstruktionsreference.

Liste 4. MRDemo.java (version 4)

importere java.util.function.Supplier; public class MRDemo {public static void main (String [] args) {Leverandørleverandør = MRDemo :: ny; System.out.println (leverandør.get ()); }}

Liste 4's MRDemo :: nyt konstruktorreference svarer til lambda () -> ny MRDemo (). Udtryk provider.get () udfører denne lambda, som påberåber sig MRDemo's standardkonstruktør uden argument og returnerer MRDemo objekt, der sendes til System.out.println (). Denne metode konverterer objektet til en streng, som det udskriver.

Antag nu, at du har en klasse med en konstruktør uden argument og en konstruktør, der tager et argument, og at du vil kalde den konstruktør, der tager et argument. Du kan udføre denne opgave ved at vælge en anden funktionel grænseflade, såsom den foruddefinerede Fungere interface vist i Listing 5.

Liste 5. MRDemo.java (version 5)

importere java.util.function.Function; offentlig klasse MRDemo {privat strengnavn; MRDemo () {name = ""; } MRDemo (strengnavn) {this.name = navn; System.out.printf ("MRDemo (strengnavn) kaldet med% s% n", navn); } offentlig statisk ugyldig hoved (String [] args) {Funktionsfunktion = MRDemo :: ny; System.out.println (function.apply ("noget navn")); }}

Funktionsfunktion = MRDemo :: ny; får compileren til at lede efter en konstruktør, der tager en Snor argument, fordi Fungere's ansøge() metode kræver en enkelt (i denne sammenhæng) Snor argument. Udfører function.apply ("noget navn") resulterer i "noget navn" bliver overført til MRDemo (strengnavn).