Upgrading to Java 8——第二章 Method References(方法引用)
概述
很多java 方法会使用函数式接口作为参数。例如,java.util.Arrays类中的一个sort方法,就接受一个Comparator接口,它就是一个函数式接口,sort方法的签名如下:
public static T[] sort(T[] array, Comparator<? super T> comparator)
相对于传递一个Compartor的实例给sort方法,不如传递一个Lambda表达式。
进一步,我们可以传递一个方法引用来代替Lambda表达式,一个简单的方法引用就是一个类名或是实例名后面紧跟着::符号,最后面是方法名。
为什么想用方法引用?主要有两个原因:
1.方法引用比Lambda表达式有更短的语义,因为方法引用不像Lambda表达式那样包含定义,方法引用的主体已经在别的地方定义了。
2.实现代码复用。
你可以使用引用给静态方法,实例方法甚至构造方法,在java8 中使用心得标识符“::”,使类名/实例引用和方法名/构造方法名分开,类封装了引用实例但并没有函数式接口的实现。
方法引用的语法有下面几种定义:
ClassName::staticMethodNameContainingType::instanceMethodobjectReference::methodNameClassName::new
静态方法引用
我们可以传递一个静态方法引用给一个函数接口的抽象方法,如果该抽象方法的参数和返回类型能够兼容静态方法引用。
请看下面的例子:
import java.util.Arrays;
import java.util.List; public class NoMethodRef { @FunctionalInterface
interface StringListFormatter {
String format(String delimiter, List<String> list);
} public static void formatAndPrint(StringListFormatter formatter, String delimiter, List<String> list) {
String formatted = formatter.format(delimiter, list);
System.out.println(formatted);
} public static void main(String[] args) {
List<String> names = Arrays.asList("Don", "King", "Kong"); StringListFormatter formatter = (delimiter, list) -> {
StringBuilder sb = new StringBuilder(100);
int size = list.size();
for (int i = 0; i < size; i++) {
sb.append(list.get(i));
if (i < size - 1) {
sb.append(delimiter);
}
}
return sb.toString();
};
formatAndPrint(formatter, ", ", names);
}
}
NoMethodRef类定义了一个接口StringListFormatter用来传入一个字符串类型list ,根据分隔符格式化字符串。
运行结果:Don, King, Kong
花了20分钟编写这代码,其实在JDK 1.8 以后,String类中添加了join方法做个刚才我们编码的工作,join方法签名如下:
public static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
比较我们自己写的format方法的签名:
public String format(java.langString delimiter, java.util.List<String> list);
因为List继承了Iterable类,String实现了CharSequence接口,所以join兼容format方法。
静态引用方法允许你复用已经实现好的方法,下面的代码使用了join方法:
import java.util.Arrays;
import java.util.List; public class MethodReferenceDemo1 { @FunctionalInterface
interface StringListFormatter {
String format(String delimiter, List<String> list);
} public static void formatAndPrint(StringListFormatter formatter, String delimiter, List<String> list) {
String formatted = formatter.format(delimiter, list);
System.out.println(formatted);
} public static void main(String[] args) {
List<String> names = Arrays.asList("Don", "King", "Kong");
formatAndPrint(String::join, ", ", names);
}
}
还有一点, StringListFormatter 接口的抽象方法有两个参数并有一个返回值,BiFunction这是它绝佳的替代者,下面的例子用BiFunction来改进。
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction; public class WithBiFunction { public static void formatAndPrint(BiFunction<String, List<String>, String> formatter, String delimiter,
List<String> list) {
String formatted = formatter.apply(delimiter, list);
System.out.println(formatted);
} public static void main(String[] args) {
List<String> names = Arrays.asList("Don", "King", "Kong");
formatAndPrint(String::join, ", ", names);
}
}
在对象可用地方的实例方法引用
实例方法引用的兼容规则与静态方法引用的规则是一样的。
举例,在 JDK1.8的java.lang.Iterable 接口中有个默认方法 forEach 接受Consumer函数接口。
default void forEach(java.util.function.Consumer<? super T> action)
foreach执行元素的遍历。这个方法已经被List继承。
import java.util.Arrays;
import java.util.List; public class MethodReferenceDemo2 {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("Apple", "Banana");
// with lambda expression
fruits.forEach((name) -> System.out.println(name)); // with method reference
fruits.forEach(System.out::println);
}
}
MethodReferencDemo2 类有一个水果的list需要打印。可以执行forEach方法传递一个Comsumer函数接口使用Lambda表达式。
fruits.forEach((name) -> System.out.println(name));
另一种方法,因为System.out 是个已经存在的对象,系统已经为你创建好了,你可以使用System.out类的println方法的对象引用。
fruits.forEach(System.out::println);
在没有对象引用的地方使用实例方法的引用
你可以传递一个实例方法的应用作为方法的参数去代替一个函数接口。这种情况下,你不必明确的去创建包含类的实例。实例方法的引用的语法与前两种的引用不同。在前两种引用中,参数的个数必须与期望的函数接口的抽象方法要相同,但当使用没有对象引用的实例方法引用,它要比期望的接口函数的抽象方法少一个参数。因此,当一个函数接口的抽象方法有四个参数,那么没有对象引用的实例方法引用只能有三个参数,它必须兼容抽象方法的后三个参数的类型。此外,抽象方法的第一个参数的类型必须兼容包含实例方法的类。
这类引用的兼容规则如下描述,第一行是一个函数接口的抽象方法的签名,第二行是没有实例引用的方法引用签名:
returnType abstractMethod(type-1, type-2, type-3, type-4)returnType instanceMethod(type-2, type-3, type-4)
在这,type-1必须兼容包含实例方法的类因为等类被初始化和实例话后要与其他的参数一起传递给抽象方法。
举个例子,就能清楚这个引用的使用了。
import java.util.Arrays;
public class MethodReferenceDemo3 {
public static void main(String[] args) {
String[] names = { "Alexis", "anna", "Kyleen" };
Arrays.sort(names, String::compareToIgnoreCase);
for (String name : names) {
System.out.println(name);
}
}
}
这个例子展示了如何传递一个实例方法引用给Arrays.sort方法来代替Comparator接口。这个类包含一个String类型的数组,它里面有三个不区分大小写的字符串的元素,如果你只使用包含一个参数的Arrays.sort方法给数组排序,结果是这样的:
Alexis, Kyleen, anna
这不是我们想要的结果,所以我们需要使用有Comparator参数的Arrays.sort方法。
public static <T> void sort(T[] array, Comparator<? super T> c)
你可以使用String.compareIgnoreCase的实例方法的引用去代替Compator接口。下面是String.compareIgnoreCase 的签名:
public int compareToIgnoreCase(String str)
它比Comparator.compare少了一个参数:
int compare(String str1, String str2)
这就是极好的第二种的方法引用。
代码运行结构:
AlexisannaKyleen
构造方法引用
第四种方法引用使用构造方法。他的语法格式如下:
ClassName::new
也许你有个方法实现将一个Integer类型数组转换成Collection,但你需要决定做回返回值的Collection是一个List还是Set,为了这个目的,你可以在下面的例子中创建arrayToCollection方法。
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.function.Supplier; public class MethodReferenceDemo4 { public static Collection<Integer> arrayToCollection(Supplier<Collection<Integer>> supplier, Integer[] numbers) {
Collection<Integer> collection = supplier.get();
for (int i : numbers) {
collection.add(i);
}
return collection;
} public static void main(String[] args) {
Integer[] array = { 1, 8, 5 };
Collection<Integer> col1 = arrayToCollection(ArrayList<Integer>::new, array);
System.out.println("Natural order");
col1.forEach(System.out::println);
System.out.println("=======================");
System.out.println("Ascending order");
Collection<Integer> col2 = arrayToCollection(HashSet<Integer>::new, array);
col2.forEach(System.out::println);
}
}
代替传递Lambda表达式给方法的第一个参数:() -> new ArrayList<Integer>()
你可以简单地传递ArrayList 构造方法的引用:ArrayList<Integer>::new
使用HashSet<Integer>::new 来代替() -> new HashSet<Integer>()。
代码执行的结果:
Natural order185=======================Ascending order158
Upgrading to Java 8——第二章 Method References(方法引用)的更多相关文章
- “全栈2019”Java多线程第二章:创建多线程之继承Thread类
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java异常第二章:如何处理异常?
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...
- “全栈2019”Java第九十七章:在方法中访问局部内部类成员详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- JAVA基础第二章-java三大特性:封装、继承、多态
业内经常说的一句话是不要重复造轮子,但是有时候,只有自己造一个轮子了,才会深刻明白什么样的轮子适合山路,什么样的轮子适合平地! 我将会持续更新java基础知识,欢迎关注. 往期章节: JAVA基础第一 ...
- JAVA 入门第二章 (面对对象)
本渣渣鸽了一个月终于有时间更新.因为有c++基础,学起来这章还是比较简单的,本章我觉得是程序猿质变课程,理解面向对象的思想,掌握面向对象的基本原则以及 Java 面向对象编程基本实现原理,熟练使用封装 ...
- 深入理解java虚拟机-第二章:java内存区域与内存泄露异常
2.1概述: java将内存的管理(主要是回收工作),交由jvm管理,确实很省事,但是一点jvm因内存出现问题,排查起来将会很困难,为了能够成为独当一面的大牛呢,自然要了解vm是怎么去使用内存的. 2 ...
- 深入理解java虚拟机-第二章
第2章 Java内存区域与内存溢出异常 运行数据区域 1.程序计数器(Program Counter Register) 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器. 2.J ...
- Upgrading to Java 8——第一章 Lambda表达式
第一章 Lambda表达式 Lamada 表达式是Java SE 8中最重要的新特性,长期以来被认为是在Java中缺失的特性,它的出现使整个java 语言变得完整.至少到目前,在这节中你将学习到什么是 ...
- <<深入Java虚拟机>>-第二章-Java内存区域-学习笔记
Java运行时内存区域 Java虚拟机在运行Java程序的时候会将它所管理的内存区域划分为多个不同的区域.每个区域都有自己的用途,创建以及销毁的时间.有的随着虚拟机的启动而存在,有的则是依赖用户线程来 ...
随机推荐
- crontab的使用说明
网上瞎转载的,仅供参考 名称 : crontab 使用权限 : 所有使用者 使用方式 : crontab file [-u user]-用指定的文件替代目前的crontab. crontab-[-u ...
- 03-图片浏览器(plist的简单应用)
ViewController.h文件中: @interface ViewController : UIViewController - (IBAction)sliderValueChange:(UIS ...
- Silverlight DataGrid标题行居中
1.引用命名空间 xmlns:Primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Win ...
- Silverlight学习之初始化参数
首先需要在Silverlight的宿主页面添加上initParams,如 <param name="initParams" value="key1=jerry,ke ...
- mac brew install redis
在mac 下安装redis 执行brew install redis ==> Downloading http://download.redis.io/releases/redis-2.8.19 ...
- shopnc二次开发(二)
一般来说二次开发,多数就是修改界面和增加功能这两个需求 先说修改界面 mvc 架构的程序,在界面这里,基本就是调用数据. 常见的界面数据构架有三种 1.是业务端或者是控制端数据驱动界面,基本上是后台输 ...
- wordpress学习-themes-001
这一篇主要是来记录wordpress theme的内容.关于为什么要自己编写wordpress theme的理由,相信大家都有各自的体会.想让自己的博客变的更加突出?更加个性话?wordpress t ...
- shell脚本入门
什么是Shell脚本 示例 看个例子吧: #!/bin/sh cd ~ mkdir shell_tut cd shell_tut for ((i=0; i<10; i++)); do touch ...
- input中如何输入逆写的中文句子
<input style="text-align:right" /><input type="text" dir="rtl" ...
- 第二节:AppDomain
CLR COM服务器初始化时,会创建一个AppDomain.AppDomain是一组程序集的逻辑容器.CLR初始化时创建的第一个AppDomain称为默认的AppDomain,这个默认的AppDoma ...