1. 程式人生 > 程式設計 >Java8新特性:Lambda表示式之方法引用詳解

Java8新特性:Lambda表示式之方法引用詳解

1.方法引用簡述

方法引用是用來直接訪問類或者例項的已經存在的方法或者構造方法。方法引用提供了一種引用而不執行方法的方式,它需要由相容的函式式介面構成的目標型別上下文。計算時,方法引用會建立函式式介面的一個例項。

當Lambda表示式中只是執行一個方法呼叫時,不用Lambda表示式,直接通過方法引用的形式可讀性更高一些。方法引用是一種更簡潔易懂的Lambda表示式。

Lambda表示式全文詳情地址:http://blog.csdn.net/sun_promise/article/details/51121205

2.作用

方法引用的唯一用途是支援Lambda的簡寫。

方法引用提高了程式碼的可讀性,也使邏輯更加清晰。(優點)

3.組成

使用::操作符將方法名和物件或類的名字分隔開。

“::” 是域操作符(也可以稱作定界符、分隔符)。

eg:

Java8新特性:Lambda表示式之方法引用詳解

4.分類

1)靜態方法引用

組成語法格式:ClassName::staticMethodName

Note:

  • 靜態方法引用比較容易理解,和靜態方法呼叫相比,只是把 . 換為 ::
  • 在目標型別相容的任何地方,都可以使用靜態方法引用。

eg:

-- String::valueOf 等價於lambda表示式 (s) -> String.valueOf(s)

-- Math::pow 等價於lambda表示式 (x,y) -> Math.pow(x,y);

-- 假設需要從一個數字列表中找出最大的一個數字。

方法引用方式:

(max是一Collections裡的一個靜態方法,它需要傳入一個List型別的引數。)

Function<List<Integer>,Integer> maxFn =Collections::max;

maxFn.apply(Arrays.asList(1,10,3,5))。

上面 等價於Lambda表示式 Function<List<Integer>,Integer> maxFn = (numbers) -> Collections.max(numbers);。

-- 以字串反轉為例:

/*
* 函式式介面
* */
interface StringFunc {
 String func(String n);
}
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;
 }
}
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

分析:

在程式中,特別注意下面這行程式碼:String outStr = stringOp(MyStringOps::strReverse,inStr);

其中將對MyStringOps內宣告的靜態方法strReverse()的引用傳遞給stringOp()方法的第一個引數。可以這麼做,因為
strReverse與StringFunc函式式介面相容。因此,表示式MyStringOps::strReverse的計算結果為物件引用,其中,
strReverse提供了StringFunc的func()方法的實現。

-- 找到列表中具有最大值的物件

(找到集合中最大元素的一種方法是使用Collections類定義的max()方法。對於這裡使用的max()版本,必須傳遞一個集合引用,以及一個實現了Comparator<T>介面的物件的例項。Comparator<T>介面指定如何比較兩個物件,它只定義了抽象方法compare(),該方法接受兩個引數,其型別均為要比較的物件的型別。如果第一個引數大於第二個引數,該方法返回一個正數;如果兩個引數相等,返回0;如果第一個引數小於第二個引數,返回一個負數。

過去,要在max()方法中使用使用者定義的物件,必須首先通過一個類顯式實現Comparator<T>介面,然後建立該類的一個例項,通過這種方法獲得Comparator<T>介面的一個例項,然後,把這個例項作為比較器傳遞給max()方法。在JDK 8中,現在可以簡單地將比較方法的引用傳遞給max()方法,因為這將自動實現比較器。)

class MyClass {
 private int val;
 MyClass(int v) {
 val = v;
 }
 public int getValue() {
 return val;
 }
}
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

分析:

在程式中,注意MyClass即沒有定義自己的比較方法,也沒有實現Comparator介面。但是,通過呼叫max()方法,仍然可以獲得MyClass物件列表中的最大值,這是因為UseMethodRef定義了靜態方法compareMC(),它與Comparator定義的compare()方法相容。因此,沒喲必要顯式的實現Comparator介面並建立其例項。

2)例項方法引用

這種語法與用於靜態方法的語法類似,只不過這裡使用物件引用而不是類名。

例項方法引用又分以下三種類型

a.例項上的例項方法引用

組成語法格式:instanceReference::methodName

Note:

對於具體(或者任意)物件的例項方法引用,在例項方法名稱和其所屬型別名稱間加上分隔符 :

與引用靜態方法引用相比,都換為例項物件的而已。

eg:

-- Function<String,String> upper = String::toUpperCase;

--

/*
* 函式式介面
* */
interface StringFunc {
 String func(String n);
}
class MyStringOps {
 //普通方法: 反轉字串
 public String strReverse(String str) {
 String result = "";
 for (int i = str.length() - 1; i >= 0; i--) {
  result += str.charAt(i);
 }
 return result;
 }
}
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();//例項物件
 //MyStringOps::strReverse 相當於實現了介面方法func() ,並在介面方法func()中作了MyStringOps.strReverse()操作
 String outStr = stringOp(strOps::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

分析:

這裡使用了類的名稱,而不是具體的物件,儘管指定的是例項方法。使用這種形式時,函式式介面的第一個引數匹配呼叫物件,第二個引數匹配方法指定的引數。

-- 定義了一個方法counter(),用於統計某個陣列中,滿足函式式介面MyFunc的fun()方法定義的條件的物件個數。本例中,統計HighTemp類的例項個數。

interface MyFunc<T> {
 boolean func(T v1,T v2);
}
class HighTemp {
 private int hTemp;
 HighTemp(int ht) {
 hTemp = ht;
 }
 public boolean sameTemp(HighTemp ht2) {
 return hTemp == ht2.hTemp;
 }
 public boolean lessThanTemp(HighTemp ht2) {
 return hTemp < ht2.hTemp;
 }
}
class InstanceMethWithObjectRefDemo {
 public static <T> int counter(T[] vals,MyFunc<T> f,T v) {
 int count = 0;
 
 for (int i = 0; i < vals.length; i++) {
  if (f.func(vals[i],v)) count++;
 }
 return count;
 }
 public static void main(String[] args) {
 int count;
 HighTemp[] weekDayHighs = {
  new HighTemp(89),new HighTemp(82),new HighTemp(90),new HighTemp(89),new HighTemp(91),new HighTemp(84),new HighTemp(83)};
 //HighTemp::sameTemp 為例項方法引用
 count = counter(weekDayHighs,HighTemp::sameTemp,new HighTemp(89));
 System.out.println(count + " days had a high of 89");
 HighTemp[] weekDayHighs2 = {
  new HighTemp(31),new HighTemp(12),new HighTemp(24),new HighTemp(19),new HighTemp(18),new HighTemp(-1),new HighTemp(13)};
 
 count = counter(weekDayHighs2,new HighTemp(12));
 System.out.println(count + " days had a high of 12");
 
 count = counter(weekDayHighs,HighTemp::lessThanTemp,new HighTemp(89));
 System.out.println(count + " days had a high less than 89");
 
 count = counter(weekDayHighs2,new HighTemp(19));
 System.out.println(count + " days had a high of less than 19");
 }
}

輸出結果:

3 days had a high of 89
2 days had a high of 12
3 days had a high less than 89
5 days had a hign less than 19

分析:

注意HighTemp有兩個例項方法:someTemp()和lessThanTemp()。如果兩個HighTemp物件包含相同的溫度,

sameTemp()方法返回true。如果呼叫物件的溫度小於被傳遞的物件的溫度,lessThanTemp()方法返回true。這兩個方法都有一個HighTemp型別的引數,並且都返回布林結果。因此,這兩個方法都與MyFunc函式式介面相容,因為呼叫物件型別可以對映到func()的第一個引數,傳遞的實參可以對映到func()的第二個引數。因此,這個表示式:HighTemp::sameTemp

被傳遞給counter()方法時,會建立函式式介面的一個例項,其中第一個引數的引數型別就是例項方法的呼叫物件的型別,也就是HighTemp。第二個引數的型別也是HighTemp,因為這是sameTemp()方法的引數。對於lessThanTemp(),這也是成立的。

Note:

上面程式中函式式介面中的函式boolean func(T v1,T v2)中含有兩個引數,而HighTemp中函式sameTemp(HighTemp ht2)含有一個引數,但是能夠相容的原因是:

其實HighTemp類中的sameTemp(HighTemp ht2)其實包含兩個引數,預設隱藏呼叫這個函式的引用this。

故,當使用類的例項方法作為方法引用時,函式式介面的第一個引數匹配類的例項方法的呼叫物件,第二個引數才匹配方法指定的引數。

b.超類上的例項方法引用

組成語法格式:super::methodName

方法的名稱由methodName指定

通過使用super,可以引用方法的超類版本。

eg: super::name

Note:還可以捕獲this 指標

this :: equals 等價於lambda表示式 x -> this.equals(x);

c.型別上的例項方法引用
組成語法格式:ClassName::methodName

Note:

若型別的例項方法是泛型的,就需要在::分隔符前提供型別引數,或者(多數情況下)利用目標型別推匯出其型別。

靜態方法引用和型別上的例項方法引用擁有一樣的語法。編譯器會根據實際情況做出決定。

一般我們不需要指定方法引用中的引數型別 ,因為編譯器往往可以推匯出結果,但如果需要我們也可以顯式在::分隔符之前提供引數型別資訊。

eg:

String::toString 等價於lambda表示式 (s) -> s.toString()

這裡不太容易理解,例項方法要通過物件來呼叫,方法引用對應Lambda,Lambda的第一個引數會成為呼叫例項方法的物件。

在泛型類或泛型方法中,也可以使用方法引用。

interface MyFunc<T> {
 int func(T[] als,T v);
}
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;
 }
}
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,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,4);
這裡傳遞了型別引數Integer。

注意,引數傳遞發生在::的後面。這種語法可以推廣。當把泛型方法指定為方法引用時,型別引數出現在::之後、方法名之前。但是,需要指出的是,在這種情況(和其它許多情況)下,並非必須顯示指定型別引數,因為型別引數會被自動推斷得出。對於指定泛型類的情況,型別引數位於類名的後面::的前面。

3)構造方法引用

構造方法引用又分構造方法引用和陣列構造方法引用。

a.構造方法引用 (也可以稱作構造器引用)

組成語法格式:Class::new

建構函式本質上是靜態方法,只是方法名字比較特殊,使用的是new 關鍵字。

eg:

-- String::new, 等價於lambda表示式 () -> new String()

--

List<String> strings = new ArrayList<String>();
strings.add("a");
strings.add("b");
Stream<Button> stream = strings.stream().map(Button::new);
List<Button> buttons = stream.collect(Collectors.toList());

-- 可以把這個引用賦值給定義的方法與建構函式相容的任何函式式介面的引用

interface MyFunc {
 MyClass func(int n);
}
class MyClass {
 private int val;
 MyClass(int v) {
  val = v;
 }
 MyClass() {
  val = 0;
 }
 public int getValue() {
  return val;
 }
}
class ConstructorRefDemo {
 public static void main(String[] args) {
  MyFunc 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

eg:

-- int[]::new 是一個含有一個引數的構造器引用,這個引數就是陣列的長度。

等價於lambda表示式 x -> new int[x]。

-- 假想存在一個接收int引數的陣列構造方法

IntFunction<int[]> arrayMaker = int[]::new;
int[] array = arrayMaker.apply(10) // 建立陣列 int[10]

到此這篇關於Java8新特性:Lambda表示式之方法引用詳解的文章就介紹到這了,更多相關Java8 Lambda表示式之方法引用內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!