JAVA8新特性之:方法引用

什麼是方法引用

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

當Lambda表達式中只是執行一個方法調用時,不用Lambda表達式,直接經過方法引用的形式可讀性更高一些。方法引用是一種更簡潔易懂的Lambda表達式。數組

注意方法引用是一個Lambda表達式,其中方法引用的操做符是雙冒號"::"。app

方法引用例子

 先看一個例子ide

首先定義一個Person類,以下:函數

package methodreferences;

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數組,而且想對它進行排序,這時候,咱們可能會這樣寫:this

原始寫法

package methodreferences;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.Comparator;

public class Main
{

    static class PersonAgeComparator implements Comparator<Person> {
        public int compare(Person a, Person b) {
            return a.getBirthday().compareTo(b.getBirthday());
        }
    }
    
    public static void main(String[] args)
    {
        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 PersonAgeComparator());
        
        System.out.println(Arrays.asList(pArr));
    }
}

其中,Arrays類的sort方法定義以下:spa

public static <T> void sort(T[] a, Comparator<? super T> c)

這裏,咱們首先要注意Comparator接口是一個函數式接口,所以咱們可使用Lambda表達式,而不須要定義一個實現Comparator接口的類,並建立它的實例對象,傳給sort方法。指針

使用Lambda表達式,咱們能夠這樣寫:code

改進一,使用Lambda表達式,未調用已存在的方法

package methodreferences;

import java.time.LocalDate;
import java.util.Arrays;

public class Main
{

    public static void main(String[] args)
    {
        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 a, Person b) -> {
            return a.getBirthday().compareTo(b.getBirthday());
        });
        
        System.out.println(Arrays.asList(pArr));
    }
}

然而,在以上代碼中,關於兩我的生日的比較方法在Person類中已經定義了,所以,咱們能夠直接使用已存在的Person.compareByAge方法。對象

改進二,使用Lambda表達式,調用已存在的方法

package methodreferences;

import java.time.LocalDate;
import java.util.Arrays;

public class Main
{

    public static void main(String[] args)
    {
        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, (a, b) -> Person.compareByAge(a, b));
        
        System.out.println(Arrays.asList(pArr));
    }
}

由於這個Lambda表達式調用了一個已存在的方法,所以,咱們能夠直接使用方法引用來替代這個Lambda表達式,

改進三,使用方法引用

package methodreferences;

import java.time.LocalDate;
import java.util.Arrays;

public class Main
{

    public static void main(String[] args)
    {
        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));
    }
}

在以上代碼中,方法引用Person::compareByAge在語義上與Lambda表達式 (a, b) -> Person.compareByAge(a, b) 是等同的,都有以下特性:

  • 真實的參數是拷貝自Comparator<Person>.compare方法,即(Person, Person);
  • 表達式體調用Person.compareByAge方法;

四種方法引用類型 

1)靜態方法引用

      組成語法格式:ClassName::staticMethodName

      注意:

  • 靜態方法引用比較容易理解,和靜態方法調用相比,只是把 換爲 ::
  • 在目標類型兼容的任何地方,均可以使用靜態方法引用。

     例子:

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

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

    字符串反轉的例子:

/*
* 函數式接口
* */
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

表達式MyStringOps::strReverse的計算結果爲對象引用,其中,strReverse提供了StringFunc的func()方法的實現。

找到列表中具備最大值的對象

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

UseMethodRef定義了靜態方法compareMC(),它與Comparator定義的compare()方法兼容。所以,沒喲必要顯式的實現Comparator接口並建立其實例。

2)實例方法引用

這種語法與用於靜態方法的語法相似,只不過這裏使用對象引用而不是類名。

實例方法引用又分如下三種類型

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

組成語法格式:instanceReference::methodName

例子:

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

這裏使用了類的名稱,而不是具體的對象,儘管指定的是實例方法。使用這種形式時,函數式接口的第一個參數匹配調用對象,第二個參數匹配方法指定的參數。

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

組成語法格式:super::methodName

方法的名稱由methodName指定

經過使用super,能夠引用方法的超類版本。

例子:

還能夠捕獲this 指針

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

c.類型上的實例方法引用

組成語法格式:ClassName::methodName

注意:

若類型的實例方法是泛型的,就須要在::分隔符前提供類型參數,或者(多數狀況下)利用目標類型推導出其類型。

靜態方法引用和類型上的實例方法引用擁有同樣的語法。編譯器會根據實際狀況作出決定。

通常咱們不須要指定方法引用中的參數類型,由於編譯器每每能夠推導出結果,但若是須要咱們也能夠顯式在::分隔符以前提供參數類型信息。

例子:

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, 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。

注意,參數傳遞發生在::的後面。這種語法能夠推廣。當把泛型方法指定爲方法引用時,類型參數出如今::以後、方法名以前。可是,須要指出的是,在這種狀況(和其它許多狀況)下,並不是必須顯示指定類型參數,由於類型參數會被自動推斷得出。對於指定泛型類的狀況,類型參數位於類名的後面::的前面。

3)構造方法引用

構造方法引用又分構造方法引用和數組構造方法引用。

a.構造方法引用 (也能夠稱做構造器引用) 

組成語法格式:Class::new

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

例子:

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

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

例子:

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

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

假想存在一個接收int參數的數組構造方法

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

4)任意對象(屬於同一個類)的實例方法引用

以下示例,這裏引用的是字符串數組中任意一個對象的compareToIgnoreCase方法。

String[] stringArray = { "Barbara", "James", "Mary", "Linda" };
 Arrays.sort(stringArray, String::compareToIgnoreCase);
相關文章
相關標籤/搜索