Java开发笔记(六十三)双冒号标记的方法引用
前面介绍了如何自己定义函数式接口,本文接续函数式接口的实现原理,阐述它在数组处理中的实际应用。数组工具Arrays提供了sort方法用于数组元素排序,可是并未提供更丰富的数组加工操作,比如从某个字符串数组中挑选符合条件的字符串并形成新的数组。现在就让我们从零开始,利用函数式接口实现数组元素筛选的功能。
首先要定义一个字符串的过滤器接口,该接口内部声明了一个用于字符串匹配的抽象方法,由此构成了如下所示的函数式接口代码:
- //定义字符串的过滤接口
- public interface StringFilter {
- // 声明一个输入参数只有源字符串的抽象方法
- public boolean isMatch(String str);
- }
接着编写一个字符串处理工具类,在工具类里面定义一个字符串数组的筛选方法select,该方法的输入参数包括原始数组和过滤器实例,方法内部根据过滤器的isMatch函数判断每个字符串是否符合筛选条件,并把所有符合条件的字符串重新生成新数组。按此思路实现的工具类代码如下所示:
- //定义字符串工具类
- public class StringUtil {
- // 根据过滤器StringFilter从字符串数组挑选符合条件的元素,并重组成新数组返回。
- // 其中StringFilter只对字符串元素自身进行校验。
- public static String[] select(String[] originArray, StringFilter filter) {
- int count = 0;
- String[] resultArray = new String[0];
- for (String str : originArray) { // 遍历所有字符串
- if (filter.isMatch(str)) { // 符合过滤条件
- count++;
- // 数组容量增大一个
- resultArray = Arrays.copyOf(resultArray, count);
- // 往数组末尾填入刚才找到的字符串
- resultArray[count-1] = str;
- }
- }
- return resultArray;
- }
- }
然后在外部构建原始的字符串数组,并通过StringUtil工具的select方法对其进行数据挑选。为了能看清过滤器实例的完整面貌,一开始还是以匿名内部类形式声明,这样外部的调用代码示例如下:
- // 在挑选符合条件的数组元素时,可采取方法引用
- private static void testSelect() {
- // 原始的字符串数组
- String[] strArray = { "Hello", "world", "What", "is", "The", "Wether", "today", "" };
- // 筛选后的字符串数组
- String[] resultArray;
- // 采取匿名内部类方式筛选字符串数组
- resultArray = StringUtil.select(strArray, new StringFilter() {
- @Override
- public boolean isMatch(String str) {
- return str.contains("e"); // 是否包含字母e
- }
- });
- }
显然匿名内部类太过啰嗦,仅仅是挑选包含字母“e”的字符串,就得写上好几行代码。俗话说“一回生二回熟”,前面用了许多次Lambda表达式,现在闭着眼睛就能信手拈来字符串筛选的Lambda代码,请看以下改写后的调用代码:
- // 采取Lambda表达式来筛选字符串数组
- resultArray = StringUtil.select(strArray, (str) -> str.contains("e"));
- resultArray = StringUtil.select(strArray, (str) -> str.indexOf("e")>0);
- resultArray = StringUtil.select(strArray, (str) -> str.isEmpty());
没想到俺也把Lambda表达式运用得如此炉火纯青了,正所谓“道高一尺魔高一丈”,Lambda表达式固然精炼,但是Java又设计了另一种更加简约的写法,它的大名叫做“方法引用”。之前介绍函数式接口之时,提到Java的输入参数只能是基本变量类型、某个类、某个接口,总之不能是某个方法,故而一定要通过接口将某个方法包装起来才行。然而分明仅需某个方法的动作,结果硬要塞给它一个接口对象,实在是强人所难。为此Java专门提供了“方法引用”,只要符合一定的规则,即可将方法名称作为输入参数传进去。以上述的字符串筛选为例,其中的“(str) -> str.isEmpty()”便满足方法引用的规定,则该Lambda表达式可进一步简化成“String::isEmpty”,就像下面代码这样:
- // 采取双冒号的方法引用来筛选字符串数组。只挑选空串
- resultArray = StringUtil.select(strArray, String::isEmpty);
可见采取了方法引用的参数格式为“变量类型::该变量调用的方法名称”,其中变量类型和方法名称之间用双冒号隔开。之所以挑选空串允许写成方法引用,是因为表达式“(str) -> str.isEmpty()”满足了下列三个条件:
1、里面的str为字符串String类型,并且式子右边调用的isEmpty正好属于字符串变量的方法;
2、式子左边有且仅有一个String类型的参数,同时式子右边有且仅有一行字符串变量的方法调用;
3、isEmpty的返回值为boolean布尔类型,Lambda表达式对应的匿名方法的返回值也是布尔类型;
既然表达式“(str) -> str.isEmpty()”支持通过方法引用改写,那么前两个式子“(str) -> str.contains("e")”和“(str) -> str.indexOf("e")>0”能否也如法炮制改写成方法引用呢?可惜的是,这两个式子里的方法有别于isEmpty方法,因为isEmpty方法不带输入参数,而不管contains方法还是indexOf方法都存在输入参数,要是在select方法中填写“String::contains”或“String::indexOf”,它俩的输入参数"e"该往哪里放?所以必须另外想办法。就式子“(str) -> str.contains("e")”而言,匿名方法内部的contains仅仅比isEmpty多了个匹配串,可否考虑把这个匹配串单独拎出来另外定义输入参数?如此一来,需要修改原先的过滤器接口,给校验方法isMatch添加一个匹配串参数。于是重新定义的过滤器接口代码如下所示:
- //定义字符串的过滤接口2
- public interface StringFilter2 {
- // 声明一个输入参数包括源字符串和标记串的抽象方法
- public boolean isMatch(String str, String sign);
- }
眼瞅着isMatch增加了新参数,工具类StringUtil也得补充对应的挑选方法select2,该方法不但在调用isMatch之时传入匹配串,而且自身的输入参数列表也要添加这个匹配串,否则编译器怎知该匹配串来自何方?下面便是新增的挑选方法代码例子:
- // 根据过滤器StringFilter2从字符串数组挑选符合条件的元素,并重组成新数组返回。
- // 其中StringFilter2根据标记串对字符串元素进行校验。
- public static String[] select2(String[] originArray, StringFilter2 filter, String sign) {
- int count = 0;
- String[] resultArray = new String[0];
- for (String str : originArray) { // 遍历所有字符串
- if (filter.isMatch(str, sign)) { // 符合过滤条件
- count++;
- // 数组容量增大一个
- resultArray = Arrays.copyOf(resultArray, count);
- // 往数组末尾填入刚才找到的字符串
- resultArray[count-1] = str;
- }
- }
- return resultArray;
- }
现在回到外部筛选字符串数组的地方,此时外部调用StringUtil工具的select2方法,终于可以将方法引用“String::contains”堂而皇之传进去了,同时select2方法的第三个参数填写contains所需的匹配串。推而广之,不单单是contains方法,String类型的startsWith方法和endsWith方法也支持采取方法引用的形式,这三个方法的引用代码示例如下:
- // 被引用的方法存在输入参数,则将该参数挪到挑选方法select2的后面。只挑选包含字母o的串
- resultArray = StringUtil.select2(strArray, String::contains, "o");
- print(resultArray, "contains方法");
- // 被引用的方法换成了startsWith。只挑选以字母W开头的串
- resultArray = StringUtil.select2(strArray, String::startsWith, "W");
- print(resultArray, "startsWith方法");
- // 被引用的方法换成了endsWith。只挑选以字母y结尾的串
- resultArray = StringUtil.select2(strArray, String::endsWith, "y");
- print(resultArray, "endsWith方法");
运行上述包含方法引用的测试代码,观察到以下的日志信息,可见字符串筛选方法运行正常:
- contains方法的挑选结果为:Hello, world, today,
- startsWith方法的挑选结果为:What, Wether,
- endsWith方法的挑选结果为:today,
不料indexOf方法并不适用于方法引用,缘于式子“(str) -> str.indexOf("e")>0”多了个“>0”的判断,要知道方法引用的条件非常严格,符合条件的表达式只能有方法自身,不允许出现其它额外的逻辑运算。被引用方法的输入参数尚能通过给过滤器添加参数来实现,多出来的逻辑运算可就无能为力了。不过对于字符串的筛选过程来说,更复杂的条件判断完全能够交给正则匹配方法matches,只要给定待筛选的字符串格式规则,那么matches方法就可以自动校验某个字符串是否符合正则条件了。假如要挑选首字母为w或者W的字符串数组,则采取方法引用的matches调用代码如下所示:
- // 如需对字符串进行更复杂的条件筛选,可利用matches方法通过正则表达式来校验
- resultArray = StringUtil.select2(strArray, String::matches, "[wW][a-zA-Z]*");
- print(resultArray, "matches方法");
再来运行上面的测试代码,日志结果显示字符串筛选的结果符合预期:
- matches方法的挑选结果为:world, What, Wether,
除了字符串数组的过滤功能,方法引用还能用于字符串数组的排序操作,正如大家熟悉的比较器接口Comparator。Arrays工具的sort方法,在判断两个字符串的先后顺序之时,默认通过它们的首字母进行比较,也就是调用字符串类型的compareTo方法。使用sort方法给字符串数组排序,用到的比较器既支持以匿名内部类方式书写,又支持以Lambda表达式书写,合并了两种方式的排序代码见下:
- // 在对字符串数组排序时,也可采取方法引用
- private static void testCompare() {
- String[] strArray = { "Hello", "world", "What", "is", "The", "Wether", "today" };
- // 采取匿名内部类方式对字符串数组进行默认的排序操作
- Arrays.sort(strArray, new Comparator<String>() {
- @Override
- public int compare(String o1, String o2) {
- return o1.compareTo(o2);
- }
- });
- // 采取Lambda表达式对字符串数组进行默认的排序操作
- Arrays.sort(strArray, (o1, o2) -> o1.compareTo(o2));
- print(strArray, "字符串数组按首字母不区分大小写");
- }
从上面排序方法用到的Lambda表达式可知,该式子对应的匿名方法有o1和o2两个输入参数,它们的数据类型都是String。相比之下,之前介绍字符串数组的挑选功能时,采用的过滤器内部方法isMatch只有一个字符串参数。过滤器和比较器的共同点在于,不管是只有一个入参,还是有两个入参,它们的处理方法内部都用到了唯一的字符串方法,前者是contains方法,而后者是compareTo方法。因此,比较器的匿名方法也允许改写成方法引用,反正编译器晓得该怎么办就行,于是修改之后的方法引用代码如下所示:
- // 因为compareTo前后的两个变量都是数组的字符串元素,
- // 所以可直接简写为该方法的引用形式,反正编译器晓得该怎么调用
- Arrays.sort(strArray, String::compareTo);
- print(strArray, "字符串数组按首字母拼写顺序");
运行以上的排序代码,得到下面的日志结果,可见compareTo方法会把首字母大写的字符串排在前面,把首字母小写的字符串排在后面:
- 字符串数组按首字母拼写顺序的挑选结果为:Hello, The, Wether, What, is, today, world,
与compareTo相似的方法还有compareToIgnoreCase,不过该方法在比较字符串首字母时忽略了大小写。利用compareToIgnoreCase进行排序的方法引用代码示例如下:
- //Arrays.sort(strArray, (s1,s2) -> s1.compareToIgnoreCase(s2));
- // 把compareTo方法换成compareToIgnoreCase方法,表示首字母不区分大小写
- Arrays.sort(strArray, String::compareToIgnoreCase);
- print(strArray, "字符串数组按首字母不区分大小写");
再次运行新写的排序代码,从输入的日志信息可知,compareToIgnoreCase比较首字母时的确忽略了大小写的区别:
- 字符串数组按首字母不区分大小写的挑选结果为:Hello, is, The, today, Wether, What, world,
更多Java技术文章参见《Java开发笔记(序)章节目录》
Java开发笔记(六十三)双冒号标记的方法引用的更多相关文章
- Java开发笔记(十三)利用关系运算符比较大小
前面在<Java开发笔记(九)赋值运算符及其演化>中提到,Java编程中的等号“=”表示赋值操作,并非数学上的等式涵义.Java通过等式符号“==”表示左右两边相等,对应数学的等号“=”: ...
- Java开发笔记(序)章节目录
现将本博客的Java学习文章整理成以下笔记目录,方便查阅. 第一章 初识JavaJava开发笔记(一)第一个Java程序Java开发笔记(二)Java工程的帝国区划Java开发笔记(三)Java帝国的 ...
- Java开发笔记(八十六)通过缓冲区读写文件
前面介绍了利用文件写入器和文件读取器来读写文件,因为FileWriter与FileReader读写的数据以字符为单位,所以这种读写文件的方式被称作“字符流I/O”,其中字母I代表输入Input,字母O ...
- Java开发笔记(二十三)数组工具Arrays
数组作为一种组合形式的数据类型,必然要求提供一些处理数组的简便办法,包括数组比较.数组复制.数组排序等等.为此Java专门设计了Arrays工具,该工具包含了几个常用方法,方便程序员对数组进行加工操作 ...
- Java开发笔记(六十四)静态方法引用和实例方法引用
前面介绍了方法引用的概念及其业务场景,虽然在所列举的案例之中方法引用确实好用,但是显而易见这些案例的适用场合非常狭窄,因为被引用的方法必须属于外层匿名方法(即Lambda表达式)的数据类型,像isEm ...
- Java开发笔记(六十一)Lambda表达式
前面介绍了匿名内部类的简单用法,通过在sort方法中运用匿名内部类,不但能够简化代码数量,还能保持业务代码的连续性.只是匿名内部类的结构仍显啰嗦,虽然它省去了内部类的名称,但是花括号里面的方法定义代码 ...
- Java开发笔记(六十七)清单:ArrayList和LinkedList
前面介绍了集合与映射两类容器,它们的共同特点是每个元素都是唯一的,并且采用二叉树方式的类型还自带有序性.然而这两个特点也存在弊端:其一,为啥内部元素必须是唯一的呢?像手机店卖出了两部Mate20,虽然 ...
- Java开发笔记(七十三)常见的程序异常
一个程序开发出来之后,无论是用户还是程序员,都希望它稳定地运行,然而程序毕竟是人写的,人无完人哪能不犯点错误呢?就算事先考虑得天衣无缝,揣着一笔巨款跑去岛国买了栋抗震性能良好的海边别墅,谁料人算不如天 ...
- Java开发笔记(九十六)线程的基本用法
每启动一个程序,操作系统的内存中通常会驻留该程序的一个进程,进程包含了程序的完整代码逻辑.一旦程序退出,进程也就随之结束:反之,一旦强行结束进程,程序也会跟着退出.普通的程序代码是从上往下执行的,遇到 ...
随机推荐
- Consider defining a bean named 'entityManagerFactory' in your configuration解决办法
错误信息: *************************** APPLICATION FAILED TO START *************************** Descriptio ...
- 最小生成树 kruskal算法&prim算法
(先更新到这,后面有时间再补,嘤嘤嘤) 今天给大家简单的讲一下最小生成树的问题吧!(ps:本人目前还比较菜,所以最小生成树最后的结果只能输出最小的权值,不能打印最小生成树的路径) 本Tianc在刚学的 ...
- python语法_函数
---恢复内容开始--- 函数: 1 减少重复代码 2 定义一个功能,需要直接调用 3 保持代码一致性 def funcation_name(参数s): 功能代码块0 参数可以为多个,传入时按照前后 ...
- Golang Go Go Go part3:数据类型及操作
五.Go 基本类型 1.基本类型种类 布尔值: bool 长度 1字节 取值范围 true, false注意事项:不可用数字代表 true 或 false 整型: int/uint 根据运行平台可能为 ...
- No Spring WebApplicationInitializer types detected on classpath 问题的一种解决办法
今天在idea中编译部署工程,tomcat报了这个错误: No Spring WebApplicationInitializer types detected on classpath 导致前端页面访 ...
- 熟悉HBase基本操作
1. ssh localhost start-dfs.sh start-hbase.sh hbase shell create 'Student', 'S_No', 'S_Name', 'S_Sex' ...
- [Swift]LeetCode69. x 的平方根 | Sqrt(x)
Implement int sqrt(int x). Compute and return the square root of x, where x is guaranteed to be a no ...
- Mysql、Hbuilder、Idea快捷键
MyEclipse 快捷键 ↑ ↓ ← →多 1.方法抽取,Alt+Shift+M 2.多行注释:Ctrl+Shift+/ 3.对象.方法; Ctrl+2 + ↓+回车 ,自动生成返回类型和变量 (非 ...
- CoCos2dx开发:更换导出的app名称和图标
要处理的文件路径如下: 1.更换图标: drawable-hdpi.drawable-ldpi.drawable-mdpi三个文件夹分别代表大.小.中三个不同宽高的图片,为了应对手机的不同分辨率,来采 ...
- Java面向对象特征之封装
package practice;/** * @功能 创建动物类,对动物的属性进行封装 * @author square 凉 * */public class Animal { /** * 动物姓名 ...