Java开发笔记(六十三)双冒号标记的方法引用
2019-02-25 16:10:39来源:博客园 阅读 ()
前面介绍了如何自己定义函数式接口,本文接续函数式接口的实现原理,阐述它在数组处理中的实际应用。数组工具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开发笔记(序)章节目录》
原文链接:https://www.cnblogs.com/pinlantu/p/10422370.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 国外程序员整理的Java资源大全(全部是干货) 2020-06-12
- 2020年深圳中国平安各部门Java中级面试真题合集(附答案) 2020-06-11
- 2020年java就业前景 2020-06-11
- 04.Java基础语法 2020-06-11
- Java--反射(框架设计的灵魂)案例 2020-06-11
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash