概述

很多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::staticMethodName
ContainingType::instanceMethod
objectReference::methodName
ClassName::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 delimiterjava.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)

这就是极好的第二种的方法引用。

代码运行结构:

Alexis
anna
Kyleen

构造方法引用

第四种方法引用使用构造方法。他的语法格式如下:

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 order
1
8
5
=======================
Ascending order
1
5
8

Upgrading to Java 8——第二章 Method References(方法引用)的更多相关文章

  1. “全栈2019”Java多线程第二章:创建多线程之继承Thread类

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  2. “全栈2019”Java异常第二章:如何处理异常?

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...

  3. “全栈2019”Java第九十七章:在方法中访问局部内部类成员详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  4. JAVA基础第二章-java三大特性:封装、继承、多态

    业内经常说的一句话是不要重复造轮子,但是有时候,只有自己造一个轮子了,才会深刻明白什么样的轮子适合山路,什么样的轮子适合平地! 我将会持续更新java基础知识,欢迎关注. 往期章节: JAVA基础第一 ...

  5. JAVA 入门第二章 (面对对象)

    本渣渣鸽了一个月终于有时间更新.因为有c++基础,学起来这章还是比较简单的,本章我觉得是程序猿质变课程,理解面向对象的思想,掌握面向对象的基本原则以及 Java 面向对象编程基本实现原理,熟练使用封装 ...

  6. 深入理解java虚拟机-第二章:java内存区域与内存泄露异常

    2.1概述: java将内存的管理(主要是回收工作),交由jvm管理,确实很省事,但是一点jvm因内存出现问题,排查起来将会很困难,为了能够成为独当一面的大牛呢,自然要了解vm是怎么去使用内存的. 2 ...

  7. 深入理解java虚拟机-第二章

    第2章 Java内存区域与内存溢出异常 运行数据区域 1.程序计数器(Program Counter Register) 是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器. 2.J ...

  8. Upgrading to Java 8——第一章 Lambda表达式

    第一章 Lambda表达式 Lamada 表达式是Java SE 8中最重要的新特性,长期以来被认为是在Java中缺失的特性,它的出现使整个java 语言变得完整.至少到目前,在这节中你将学习到什么是 ...

  9. <<深入Java虚拟机>>-第二章-Java内存区域-学习笔记

    Java运行时内存区域 Java虚拟机在运行Java程序的时候会将它所管理的内存区域划分为多个不同的区域.每个区域都有自己的用途,创建以及销毁的时间.有的随着虚拟机的启动而存在,有的则是依赖用户线程来 ...

随机推荐

  1. Javascript 模块模式

    模块模式(Module Pattern)提供了一种代码封装的方式,可以优雅地创建非耦合的代码块. 它是利用即时函数为对象创建私有变量和特权方法.严格来说,Javascript中没有私有成员的概念,所有 ...

  2. hadoop-2.7.1基于QMJ高可用安装配置

    1.修改主机名及hosts文件 10.205.22.185 nn1 (主)作用namenode,resourcemanager,datanode,JournalNode,zk,zkfc(hive,sq ...

  3. linux下最大文件数

    系统级:系统级设置对所有用户有效.可通过两种方式查看系统最大文件限制1 cat /proc/sys/fs/file-max 2 sysctl -a 查看结果中fs.file-max这项的配置数量如果需 ...

  4. 《第一行代码--Android》阅读笔记之广播

    广播接收器 1.注册方式 动态注册:在程序中注册,如在Activity里的onCreate()方法中注册 静态注册:在AndroidManifest.xml中注册   2.可接收哪些广播 接收系统消息 ...

  5. HBase从hdfs导入数据

    需求:将HDFS上的文件中的数据导入到hbase中 实现上面的需求也有两种办法,一种是自定义mr,一种是使用hbase提供好的import工具 一.hdfs中的数据是这样的 每一行的数据是这样的id ...

  6. 【MySql】5.6.14版本的安装和测试

    当前状态:apache2.4.6和php5.5.6已经安装成功: mysql的安装和测试: 一.安装mysql5.6.14,参考http://wenku.baidu.com/link?url=_0jk ...

  7. ContactsContract.CommonDataKinds【Translated By KillerLegend】

    http://developer.android.com/reference/android/provider/ContactsContract.CommonDataKinds.html interf ...

  8. DMA直接内存存取20160525

    说一下工作中接触到的DMA1)在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题.即DMA传输前,CPU要把 总线控制权交给DMA控制器,而在结束DMA传输后,DMA ...

  9. CentOS6.0/RedHat Server 6.4安装配置过程 详细图解!

    1.准备安装 1.1 系统简介 CentOS 是什么? CentOS是一个基于Red Hat 企业级 Linux 提供的可自由使用的源代码企业级的 Linux 发行版本.每个版本的 CentOS 都会 ...

  10. 【javascript】html5中使用canvas编写头像上传截取功能

    [javascript]html5中使用canvas编写头像上传截取功能 本人对canvas很是喜欢,于是想仿照新浪微博头像上传功能(前端使用canvas) 本程序目前在谷歌浏览器和火狐浏览器测试可用 ...