1、概述java
在學習lambda表達式以後,咱們一般使用lambda表達式來建立匿名方法。然而,有時候咱們僅僅是調用了一個已存在的方法。以下:數組
Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2));
在Java8中,咱們能夠直接經過方法引用來簡寫lambda表達式中已經存在的方法。app
Arrays.sort(stringsArray, String::compareToIgnoreCase);
這種特性就叫作方法引用(Method Reference)。ide
2、什麼是方法引用函數
方法引用是用來直接訪問類或者實例的已經存在的方法或者構造方法。方法引用提供了一種引用而不執行方法的方式,它須要由兼容的函數式接口構成的目標類型上下文。計算時,方法引用會建立函數式接口的一個實例。學習
當Lambda表達式中只是執行一個方法調用時,不用Lambda表達式,直接經過方法引用的形式可讀性更高一些。方法引用是一種更簡潔易懂的Lambda表達式。this
注意方法引用是一個Lambda表達式,其中方法引用的操做符是雙冒號"::"。spa
============================================================================指針
簡單地說,就是一個Lambda表達式。在Java 8中,咱們會使用Lambda表達式建立匿名方法,可是有時候,咱們的Lambda表達式可能僅僅調用一個已存在的方法,而不作任何其它事,對於這種狀況,經過一個方法名字來引用這個已存在的方法會更加清晰,Java 8的方法引用容許咱們這樣作。方法引用是一個更加緊湊,易讀的Lambda表達式,注意方法引用是一個Lambda表達式,其中方法引用的操做符是雙冒號"::"。code
3、方法引用例子
先看一個例子,首先定義一個Person類,以下:
package com.demo.model; import java.time.LocalDate; public class Person { public Person(String name, LocalDate birthday) { this.name = name; this.birthday = birthday; } String name; LocalDate birthday; public LocalDate getBirthday() { return birthday; } public static int compareByAge(Person a, Person b) { return a.birthday.compareTo(b.birthday); } @Override public String toString() { return this.name; } }
假設咱們有一個Person數組,而且想對它進行排序,這時候,咱們可能會這樣寫:
原始寫法,使用匿名類:
package com.demo; import java.time.LocalDate; import java.util.Arrays; import java.util.Comparator; import org.junit.Test; import com.demo.model.Person; public class testMethodReference { @Test public void test() { Person[] pArr = new Person[]{ new Person("003", LocalDate.of(2016,9,1)), new Person("001", LocalDate.of(2016,2,1)), new Person("002", LocalDate.of(2016,3,1)), new Person("004", LocalDate.of(2016,12,1))}; // 使用匿名類 Arrays.sort(pArr, new Comparator<Person>() { @Override public int compare(Person a, Person b) { return a.getBirthday().compareTo(b.getBirthday()); } }); System.out.println(Arrays.asList(pArr)); } }
其中,Arrays類的sort方法定義以下:
public static <T> void sort(T[] a, Comparator<? super T> c)
這裏,咱們首先要注意Comparator
接口是一個函數式接口,所以咱們可使用Lambda表達式,而不須要定義一個實現Comparator
接口的類,並建立它的實例對象,傳給sort方法。
使用Lambda表達式,咱們能夠這樣寫:
改進一,使用Lambda表達式,未調用已存在的方法
@Test public void test1() { Person[] pArr = new Person[]{ new Person("003", LocalDate.of(2016,9,1)), new Person("001", LocalDate.of(2016,2,1)), new Person("002", LocalDate.of(2016,3,1)), new Person("004", LocalDate.of(2016,12,1))}; //使用lambda表達式 Arrays.sort(pArr, (Person a, Person b) -> { return a.getBirthday().compareTo(b.getBirthday()); }); System.out.println(Arrays.asList(pArr)); }
然而,在以上代碼中,關於兩我的生日的比較方法在Person類中已經定義了,所以,咱們能夠直接使用已存在的Person.compareByAge方法。
改進二,使用Lambda表達式,調用已存在的方法
@Test public void test2() { Person[] pArr = new Person[]{ new Person("003", LocalDate.of(2016,9,1)), new Person("001", LocalDate.of(2016,2,1)), new Person("002", LocalDate.of(2016,3,1)), new Person("004", LocalDate.of(2016,12,1))}; //使用lambda表達式和類的靜態方法 Arrays.sort(pArr, (a ,b) -> Person.compareByAge(a, b)); System.out.println(Arrays.asList(pArr)); }
由於這個Lambda表達式調用了一個已存在的方法,所以,咱們能夠直接使用方法引用來替代這個Lambda表達式。
改進三,使用方法引用
@Test public void test3() { Person[] pArr = new Person[]{ new Person("003", LocalDate.of(2016,9,1)), new Person("001", LocalDate.of(2016,2,1)), new Person("002", LocalDate.of(2016,3,1)), new Person("004", LocalDate.of(2016,12,1))}; //使用方法引用,引用的是類的靜態方法 Arrays.sort(pArr, Person::compareByAge); System.out.println(Arrays.asList(pArr)); }
運行結果:
[001, 002, 003, 004]
在以上代碼中,方法引用Person::compareByAge在語義上與Lambda表達式 (a, b) -> Person.compareByAge(a, b) 是等同的,都有以下特性:
4、四種方法引用類型
方法引用的標準形式是:類名::方法名
。(注意:只須要寫方法名,不須要寫括號)
有如下四種形式的方法引用:
類型 | 示例 |
引用靜態方法 | ContainingClass::staticMethodName |
引用某個對象的實例方法 | containingObject::instanceMethodName |
引用某個類型的任意對象的實例方法 | ContainingType::methodName |
引用構造方法 | ClassName::new |
下面咱們經過一個小Demo來分別學習這幾種形式的方法引用:
一、靜態方法引用
組成語法格式:ClassName::staticMethodName
咱們前面舉的例子Person::compareByAge就是一個靜態方法引用。
注意:
例子:
String::valueOf 等價於lambda表達式 (s) -> String.valueOf(s)
Math::pow 等價於lambda表達式 (x, y) -> Math.pow(x, y);
字符串反轉的例子:
package com.demo; /** * 函數式接口 */ public interface StringFunc { String func(String n); }
package com.demo; public class MyStringOps { //靜態方法: 反轉字符串 public static String strReverse(String str) { String result = ""; for (int i = str.length() - 1; i >= 0; i--) { result += str.charAt(i); } return result; } }
package com.demo; public class MethodRefDemo { public static String stringOp(StringFunc sf, String s) { return sf.func(s); } public static void main(String[] args) { String inStr = "lambda add power to Java"; //MyStringOps::strReverse 至關於實現了接口方法func() // 並在接口方法func()中做了MyStringOps.strReverse()操做 String outStr = stringOp(MyStringOps::strReverse, inStr); System.out.println("Original string: " + inStr); System.out.println("String reserved: " + outStr); } }
輸出結果:
Original string: lambda add power to Java
String reserved: avaJ ot rewop dda adbmal
表達式MyStringOps::strReverse的計算結果爲對象引用,其中,strReverse提供了StringFunc的func()方法的實現。
找到列表中具備最大值的對象
package com.demo; public class MyClass { private int val; MyClass(int v) { val = v; } public int getValue() { return val; } }
package com.demo; import java.util.ArrayList; import java.util.Collections; public class UseMethodRef { public static int compareMC(MyClass a, MyClass b) { return a.getValue() - b.getValue(); } public static void main(String[] args) { ArrayList<MyClass> a1 = new ArrayList<MyClass>(); a1.add(new MyClass(1)); a1.add(new MyClass(4)); a1.add(new MyClass(2)); a1.add(new MyClass(9)); a1.add(new MyClass(3)); a1.add(new MyClass(7)); //UseMethodRef::compareMC生成了抽象接口Comparator定義的compare()方法的實例。 MyClass maxValObj = Collections.max(a1, UseMethodRef::compareMC); System.out.println("Maximum value is: " + maxValObj.getValue()); } }
輸出結果:
Maximum value is: 9
UseMethodRef定義了靜態方法compareMC(),它與Comparator定義的compare()方法兼容。所以,沒有必要顯式的實現Comparator接口並建立其實例。
二、特定實例對象的方法引用
這種語法與用於靜態方法的語法相似,只不過這裏使用對象引用而不是類名。實例方法引用又分如下三種類型:
a.實例上的實例方法引用
組成語法格式:instanceReference::methodName
以下示例,引用的方法是myComparisonProvider 對象的compareByName方法。
class ComparisonProvider{ public int compareByName(Person a, Person b){ return a.getName().compareTo(b.getName()); } public int compareByAge(Person a, Person b){ return a.getBirthday().compareTo(b.getBirthday()); } } ComparisonProvider myComparisonProvider = new ComparisonProvider(); Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
例子:反轉字符串
package com.demo; /** * 函數式接口 */ public interface StringFunc { String func(String n); }
package com.demo; public class MyStringOps { //普通方法: 反轉字符串 public String strReverse1(String str) { String result = ""; for (int i = str.length() - 1; i >= 0; i--) { result += str.charAt(i); } return result; } }
package com.demo; public class MethodRefDemo2 { public static String stringOp(StringFunc sf, String s) { return sf.func(s); } public static void main(String[] args) { String inStr = "lambda add power to Java"; MyStringOps strOps = new MyStringOps();//實例對象 //strOps::strReverse1 至關於實現了接口方法func() String outStr = stringOp(strOps::strReverse1, inStr); System.out.println("Original string: " + inStr); System.out.println("String reserved: " + outStr); } }
輸出結果:
Original string: lambda add power to Java
String reserved: avaJ ot rewop dda adbmal
b.超類上的實例方法引用
組成語法格式:super::methodName
方法的名稱由methodName指定,經過使用super,能夠引用方法的超類版本。
例子:
還能夠捕獲this 指針,this :: equals 等價於lambda表達式 x -> this.equals(x);
c.類型上的實例方法引用
組成語法格式:ClassName::methodName
注意:
若類型的實例方法是泛型的,就須要在::分隔符前提供類型參數,或者(多數狀況下)利用目標類型推導出其類型。
靜態方法引用和類型上的實例方法引用擁有同樣的語法。編譯器會根據實際狀況作出決定。通常咱們不須要指定方法引用中的參數類型,由於編譯器每每能夠推導出結果,但若是須要咱們也能夠顯式在::分隔符以前提供參數類型信息。
例子:
String::toString 等價於lambda表達式 (s) -> s.toString()
這裏不太容易理解,實例方法要經過對象來調用,方法引用對應Lambda,Lambda的第一個參數會成爲調用實例方法的對象。
在泛型類或泛型方法中,也可使用方法引用。
package com.demo; public interface MyFunc<T> { int func(T[] als, T v); }
package com.demo; public class MyArrayOps { public static <T> int countMatching(T[] vals, T v) { int count = 0; for (int i = 0; i < vals.length; i++) { if (vals[i] == v) count++; } return count; } }
package com.demo; public class GenericMethodRefDemo { public static <T> int myOp(MyFunc<T> f, T[] vals, T v) { return f.func(vals, v); } public static void main(String[] args){ Integer[] vals = {1, 2, 3, 4, 2, 3, 4, 4, 5}; String[] strs = {"One", "Two", "Three", "Two"}; int count; count=myOp(MyArrayOps::<Integer>countMatching, vals, 4); System.out.println("vals contains "+count+" 4s"); count=myOp(MyArrayOps::<String>countMatching, strs, "Two"); System.out.println("strs contains "+count+" Twos"); } }
輸出結果:
vals contains 3 4s
strs contains 2 Twos
分析:
在程序中,MyArrayOps是非泛型類,包含泛型方法countMatching()。該方法返回數組中與指定值匹配的元素的個數。注意這裏如何指定泛型類型參數。例如,在main()方法中,對countMatching()方法的第一次調用以下所示:count = myOp(MyArrayOps::<Integer>countMatching,vals,4); 這裏傳遞了類型參數Integer。
注意,參數傳遞發生在::的後面。這種語法能夠推廣。當把泛型方法指定爲方法引用時,類型參數出如今::以後、方法名以前。可是,須要指出的是,在這種狀況(和其它許多狀況)下,並不是必須顯示指定類型參數,由於類型參數會被自動推斷得出。對於指定泛型類的狀況,類型參數位於類名的後面::的前面。
三、任意對象(屬於同一個類)的實例方法引用
以下示例,這裏引用的是字符串數組中任意一個對象的compareToIgnoreCase方法。
String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
四、構造方法引用
構造方法引用又分構造方法引用和數組構造方法引用。
a.構造方法引用(也能夠稱做構造器引用)
組成語法格式:Class::new
構造函數本質上是靜態方法,只是方法名字比較特殊,使用的是new 關鍵字。
例子:
String::new, 等價於lambda表達式 () -> new String()
package com.demo; public interface MyFunc1 { MyClass func(int n); }
package com.demo; public class MyClass { private int val; MyClass(int v) { val = v; } MyClass(){ val = 0; } public int getValue() { return val; } }
package com.demo; public class ConstructorRefDemo { public static void main(String[] args) { MyFunc1 myClassCons = MyClass :: new; MyClass mc = myClassCons.func(100); System.out.println("val in mc is: " + mc.getValue()); } }
輸出結果:
val in mc is: 100
b.數組構造方法引用
組成語法格式:TypeName[]::new
例子:
int[]::new 是一個含有一個參數的構造器引用,這個參數就是數組的長度。等價於lambda表達式 x -> new int[x]。
假想存在一個接收int參數的數組構造方法
IntFunction<int[]> arrayMaker = int[]::new; int[] array = arrayMaker.apply(10) // 建立數組 int[10]