前面介绍了方法引用的概念及其业务场景,虽然在所列举的案例之中方法引用确实好用,但是显而易见这些案例的适用场合非常狭窄,因为被引用的方法必须属于外层匿名方法(即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开发笔记(序)章节目录

Java开发笔记(六十四)静态方法引用和实例方法引用的更多相关文章

  1. Java开发笔记(十四)几种运算符的优先级顺序

    到目前为止,我们已经学习了Java语言的好几种运算符,包括算术运算符.赋值运算符.逻辑运算符.关系运算符等基础运算符,并且在书写赋值语句时都没添加圆括号,显然是默认了先完成算术.逻辑.关系等运算,最后 ...

  2. Java开发笔记(九十四)文件通道的性能优势

    前面介绍了字节缓存的一堆概念,可能有的朋友还来不及消化,虽然文件通道的用法比起传统I/O有所简化,可是平白多了个操控繁琐的字节缓存,分明比较传统I/O更加复杂了.尽管字节缓存享有缓存方面的性能优势,但 ...

  3. 树莓派开发笔记(十四):入手研华ADVANTECH工控树莓派UNO-220套件(三):使用研发自带系统测试rtc、gpio、232和485套件接口

    前言   上一篇说明了必须要使用研华自带的8G卡的系统,通过沟通拿到了相关的系统,购买的时候会带8GB的卡,请自行备份一份镜像.本篇对uno-220套件的相关研华配套的额外接口做测试,篇幅较长,重点讲 ...

  4. Java开发学习(二十四)----SpringMVC设置请求映射路径

    一.环境准备 创建一个Web的Maven项目 参考Java开发学习(二十三)----SpringMVC入门案例.工作流程解析及设置bean加载控制中环境准备 pom.xml添加Spring依赖 < ...

  5. Java开发笔记(十六)非此即彼的条件分支

    前面花了大量篇幅介绍布尔类型及相应的关系运算和逻辑运算,那可不仅仅是为了求真值或假值,更是为了通过布尔值控制流程的走向.在现实生活中,常常需要在岔路口抉择走去何方,往南还是往北,向东还是向西?在Jav ...

  6. 【Java学习笔记之三十四】超详解Java多线程基础

    前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的 ...

  7. 【Java学习笔记之十四】Java中this用法小节

    用类名定义一个变量的时候,定义的只是一个引用,外面可以通过这个引用来访问这个类里面的属性和方法. 那们类里面是够也应该有一个引用来访问自己的属性和方法纳? 呵呵,JAVA提供了一个很好的东西,就是 t ...

  8. Java开发笔记(十九)规律变化的for循环

    前面介绍while循环时,有个名叫year的整型变量频繁出现,并且它是控制循环进出的关键要素.不管哪一种while写法,都存在三处与year有关的操作,分别是“year = 0”.“year<l ...

  9. Java学习笔记二十四:Java中的Object类

    Java中的Object类 一:什么是Object类: Object类是所有类的父类,相当于所有类的老祖宗,如果一个类没有使用extends关键字明确标识继承另外一个类,那么这个类默认继承Object ...

  10. Java学习笔记(十四)——Java静态工厂

    [前面的话] 每天过的还行,对我来说,只要让自己充实,生活就会是好的. 学习Java工场方法的原因是最近在使用Spring框架做一个系统,其中有一个注入的方法是使用静态工场方法注入,所以学习一下,基础 ...

随机推荐

  1. 关于js的页面高度和滚动条高度还有元素高度

    window.innerHeight    这是浏览器里面内容的高度,直接就是值,不需要其它操作; window.pageYOffset 这是滚动条到浏览器顶端的距离; $(元素).offset(). ...

  2. ssh 报error: kex protocol error: type 30 seq 1

    由于近期服务器升级了openssl,在使用navicat连接数据库报 查看日志 sshd[1990]: error: kex protocol error: type 30 seq 1 [preaut ...

  3. Springboot 集成jpa使用

    实体类 dao层 上面的查询 ,方法名友好命名的话,可以不写注解查询  findByXXXX MetadataSchemePO findBySchemeName(String schemeName); ...

  4. js-day06-jQuery事件和DOM操作-练习题

    jQuery事件绑定 js中绑定事件,三种方式: 方式1: 直接在元素上,增加onXxx事件属性. <button onclick="alert(1);">点我< ...

  5. Linux入门总结——虚拟机安装配置以及vim简单操作

    安装配置ubuntu 安装准备 vittualbox-5.2.22版本(win10) ubuntu-12.04 安装VirtualBox 1.双击VirtualBox-5.2.2-119230-Win ...

  6. 《C#与.NET程序员面试宝典》学习札记

    第2章 .NET概述 2.1-6~ .Net Framework / CLR / IL / Assembly IL:中间语言代码,不同语言(如C#,VB)的基于CLR的编译器编译生成的中间语言字节码, ...

  7. JNI实战(二):Java 调用 C

    1. JNI Env 和 Java VM 关系说明 JNIEnv 是 Java的本地化环境,是Java与C的交互的重要桥梁. 在Android上,一个进程对应一个JavaVM,也就是一个app对应一个 ...

  8. [Swift]LeetCode203. 移除链表元素 | Remove Linked List Elements

    Remove all elements from a linked list of integers that have value val. Example: Input: 1->2-> ...

  9. 使用jquery日期选择器flatpickr.js,使用js动态创建input元素时插件失效

    最近写页面时需要用到,日期选择器,网上搜索了一些插件,最后使用了flatpickr.js.我是从npm 上拉下的依赖  npm install flatpickr --save 随后在页面中引入css ...

  10. vue + hbuilder 开发备忘录

    踩过的坑: axios 安卓低版本兼容性处理 阻止表单中,button默认事件,出现刷新问题. 设置滚动条的位置 vue 数据和对象数据变化 dom结构不变 android低版本 白屏问题 你是不是用 ...