前面介紹了方法引用的概念及其業務場景,雖然在所列舉的案例之中方法引用確實好用,可是顯而易見這些案例的適用場合很是狹窄,由於被引用的方法必須屬於外層匿名方法(即Lambda表達式)的數據類型,像isEmpty、contains、startsWith、endsWith、matches、compareTo、compareToIgnoreCase等等無一例外所有歸屬String字符串類型,假使Lambda表達式輸入參數的數據類型並不擁有式子右邊的方法,那麼方法引用還能派上用場嗎?
固然Java8憋出方法引用這麼一個大招,絕非只想讓它走過場而已,而是要除舊革新深刻應用。上一篇文章費了許多口舌介紹的案例,其實僅僅涉及到方法引用的其中一個分支——參數方法引用,該分支顧名思義被引用的方法對應於入參的數據類型。方法引用還有其它兩個分支,分別是靜態方法引用和實例方法引用,接下來依次進行詳細說明。
首先是靜態方法引用,所謂靜態表示被引用的方法乃某個工具類的靜態方法。爲了逐步展開相關論述,開頭仍是先定義一個專屬的計算器接口,同時該接口也是一個標準的函數式接口。下面是計算器接口的定義代碼:html
//定義一個計算器接口,給算術類使用 public interface Calculator { // 聲明一個名叫運算的抽象方法 public double operate(double x, double y); }
可見計算器接口聲明瞭一個運算方法,該方法有兩個浮點入參。之因此把運算方法看成抽象類型,是爲了支持動態指定兩個數字的運算操做,例如能夠對這兩個數字進行相加運算,或者相乘運算,或者求兩數的最大值,或者求兩數的最小值等等。爲此還要定義一個算術工具類,在該工具類中編寫calculate方法,將計算器接口以及兩個操做數做爲calculate方法的輸入參數。這個算術工具類的角色至關於數組工具類Arrays,它的定義代碼示例以下:java
//定義一個算術類 public class Arithmetic { // 定義一個靜態的計算方法,根據傳入的計算器接口,對後面兩個數字進行運算 public static double calculate(Calculator calculator, double x, double y) { // 這裏調用了計算器接口的運算方法 return calculator.operate(x, y); } }
如今輪到外部去調用算術類Arithmetic,假若命令計算器去求兩個數字的較大值,則參照Arrays工具的sort方法格式,可編寫以下所示的運算代碼(包括匿名內部類方式與Lambda表達式):程序員
// 演示靜態方法的方法引用 private static void testStatic() { double result; // 採起匿名內部類方式對兩個操做數進行指定運算(求較大值) result = Arithmetic.calculate(new Calculator() { @Override public double operate(double x, double y) { return Math.max(x, y); } }, 3, 2); // 採起Lambda表達式對兩個操做數進行指定運算(求較大值) result = Arithmetic.calculate((x, y) -> Math.max(x, y), 3, 2); }
顯然求最大值用到的max方法屬於Math數學函數庫,不屬於x與y兩者的變量類型,而且max仍是Math工具的靜態方法而非實例方法。儘管此時max方法不符合參數方法引用,但它偏偏跟靜態方法引用對上號了,於是Lambda表達式「(x, y) -> Math.max(x, y)」容許簡寫爲「Math::max」。依此類推,經過Arithmetic工具的calculate方法求兩個數字的較小值,也能代入方法引用「Math::min」;求某個數字的n次方,可代入方法引用「Math::pow」;求兩個數字之和,可代入方法引用「Double::sum」。因而在算術工具中運用靜態方法引用的代碼變成了下面這樣:數組
// 採起雙冒號的方法引用來替換Lambda表達式中的靜態方法,求兩數的較大值 result = Arithmetic.calculate(Math::max, 3, 2); System.out.println("兩數的較大值="+result); // 被引用的方法換成了Math.min,求兩數的較小值 result = Arithmetic.calculate(Math::min, 3, 2); System.out.println("兩數的較小值="+result); // 被引用的方法換成了Math.pow,求某數的n次方 result = Arithmetic.calculate(Math::pow, 3, 2); System.out.println("兩數之乘方="+result); // 被引用的方法換成了Double.sum,求兩數之和 result = Arithmetic.calculate(Double::sum, 3, 2); System.out.println("兩數之和="+result);
運行上述的計算代碼,輸出兩個數字的各項運算結果見下:ide
兩數的較大值=3.0 兩數的較小值=2.0 兩數之乘方=9.0 兩數之和=5.0
要是接着求兩個數字之差、兩個數字之積等等,就會發現不論是Math工具,仍是包裝浮點型Double,它們都沒有可用的靜態方法了。不過這難不倒咱們,即便系統不提供,咱也能本身定義相應的計算方法唄。說時遲那時快,熟練的程序員早早準備好了包括常見運算在內的數學工具類,不但有四則運算,還有乘方和開方運算,完整的工具類代碼以下所示:函數
//定義數學工具類 public class MathUtil { // 加法運算 public double add(double x, double y) { return x+y; } // 減法運算 public double minus(double x, double y) { return x-y; } // 乘法運算 public double multiply(double x, double y) { return x*y; } // 除法運算 public double divide(double x, double y) { return x/y; } // 取餘數運算 public double remainder(double x, double y) { return x%y; } // 取兩數的較大值 public double max(double x, double y) { return Math.max(x, y); } // 取兩數的較小值 public double min(double x, double y) { return Math.min(x, y); } // 冪運算,即乘方 public double pow(double x, double y) { return Math.pow(x, y); } // 求方根運算,即開方 public double sqrt(double x, double y) { double number = x; // 須要求n次方根的數字 double root = x; // 每次迭代後的數值 double n = y; // n次方根的n // 下面利用牛頓迭代法求n次方根 for (int i=0; i<5; i++) { root = (root*(n-1)+number/Math.pow(root, n-1))/n; } return root; } }
注意到MathUtil的內部方法所有是實例方法,而非靜態方法,意味着外部若想調用這些方法,得先建立MathUtil的實例才行。好比下面這般:工具
MathUtil math = new MathUtil();
有了MathUtil類的實例以後,外部便可經過「math::add」表示相加運算的方法引用,經過「math::minus」表示相減運算的方法引用了。現今這種「實例名稱::方法名稱」的引用形式,正是方法引用的第三個分支——實例方法引用。下面的運算代碼便演示了實例方法引用的具體用法:日誌
// 演示實例方法的方法引用 private static void testInstance() { MathUtil math = new MathUtil(); double result; // 雙冒號的方法引用也適用於實例方法,求兩數之和 result = Arithmetic.calculate(math::add, 3, 2); System.out.println("兩數之和="+result); // 被引用的方法換成了該實例的minus方法,求兩數之差 result = Arithmetic.calculate(math::minus, 3, 2); System.out.println("兩數之差="+result); // 被引用的方法換成了該實例的multiply方法,求兩數之積 result = Arithmetic.calculate(math::multiply, 3, 2); System.out.println("兩數之積="+result); // 被引用的方法換成了該實例的divide方法,求兩數之商 result = Arithmetic.calculate(math::divide, 3, 2); System.out.println("兩數之商="+result); // 被引用的方法換成了該實例的remainder方法,求兩數之餘 result = Arithmetic.calculate(math::remainder, 3, 2); System.out.println("兩數之餘="+result); // 被引用的方法換成了該實例的max方法,求兩數的較大值 result = Arithmetic.calculate(math::max, 3, 2); System.out.println("兩數的較大值="+result); // 被引用的方法換成了該實例的min方法,求兩數的較小值 result = Arithmetic.calculate(math::min, 3, 2); System.out.println("兩數的較小值="+result); // 被引用的方法換成了該實例的pow方法,求某數的n次方 result = Arithmetic.calculate(math::pow, 3, 2); System.out.println("兩數之乘方="+result); // 被引用的方法換成了該實例的sqrt方法,求某數的n次方根 result = Arithmetic.calculate(math::sqrt, 3, 2); System.out.println("兩數之開方="+result); }
運行如上的計算代碼,可獲得下列的計算結果日誌:htm
兩數之和=5.0 兩數之差=1.0 兩數之積=6.0 兩數之商=1.5 兩數之餘=1.0 兩數的較大值=3.0 兩數的較小值=2.0 兩數之乘方=9.0 兩數之開方=1.7320508075688772
固然了,對於算術運算這茬事,原本就沒有必要非得去建立實例,徹底能夠將add、minus、multiply等等諸多方法聲明爲靜態方法,而後外部經過「MathUtil::add」、「MathUtil::minus」、「MathUtil::multiply」來引用對應方法。進行如此變動的惟一代價,即是把實例方法引用改爲了靜態方法引用。blog
更多Java技術文章參見《Java開發筆記(序)章節目錄》