Java開發筆記(六十四)靜態方法引用和例項方法引用

pinlantu發表於2019-02-25

前面介紹了方法引用的概念及其業務場景,雖然在所列舉的案例之中方法引用確實好用,但是顯而易見這些案例的適用場合非常狹窄,因為被引用的方法必須屬於外層匿名方法(即Lambda表示式)的資料型別,像isEmpty、contains、startsWith、endsWith、matches、compareTo、compareToIgnoreCase等等無一例外全部歸屬String字串型別,假使Lambda表示式輸入引數的資料型別並不擁有式子右邊的方法,那麼方法引用還能派上用場嗎?
當然Java8憋出方法引用這麼一個大招,絕非只想讓它走過場而已,而是要除舊革新深入應用。上一篇文章費了許多口舌介紹的案例,其實僅僅涉及到方法引用的其中一個分支——引數方法引用,該分支顧名思義被引用的方法對應於入參的資料型別。方法引用還有其它兩個分支,分別是靜態方法引用和例項方法引用,接下來依次進行詳細說明。
首先是靜態方法引用,所謂靜態表示被引用的方法乃某個工具類的靜態方法。為了逐步展開相關論述,開頭還是先定義一個專屬的計算器介面,同時該介面也是一個標準的函式式介面。下面是計算器介面的定義程式碼:

//定義一個計算器介面,給算術類使用
public interface Calculator {

	// 宣告一個名叫運算的抽象方法
	public double operate(double x, double y);
}

可見計算器介面宣告瞭一個運算方法,該方法有兩個浮點入參。之所以把運算方法當作抽象型別,是為了支援動態指定兩個數字的運算操作,例如可以對這兩個數字進行相加運算,或者相乘運算,或者求兩數的最大值,或者求兩數的最小值等等。為此還要定義一個算術工具類,在該工具類中編寫calculate方法,將計算器介面以及兩個運算元作為calculate方法的輸入引數。這個算術工具類的角色相當於陣列工具類Arrays,它的定義程式碼示例如下:

//定義一個算術類
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);

 

執行上述的計算程式碼,輸出兩個數字的各項運算結果見下:

兩數的較大值=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);
	}

 

執行如上的計算程式碼,可得到下列的計算結果日誌:

兩數之和=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”來引用對應方法。進行如此變更的唯一代價,便是把例項方法引用改成了靜態方法引用。

更多Java技術文章參見《Java開發筆記(序)章節目錄

相關文章