概述

很多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. 为什么for in循环不适合用于数组

    首先一点无关的,使用(var i in a) 而不是( i in a),除非你想创建全局变量. 第二点,for in 循环会忽略空的数组 var a = []; a[5] = 5; // Perfec ...

  2. Oracle一些常用的查询命令总结(持续更新)

    更新于:2015年1月28日 17:08:13 -------------------------表空间 --------------------------------------- ----- 查 ...

  3. js设计模式(12)---职责链模式

    0.前言 老实讲,看设计模式真得很痛苦,一则阅读过的代码太少:二则从来或者从没意识到使用过这些东西.所以我采用了看书(<js设计模式>)和阅读博客(大叔.alloyteam.聂微东)相结合 ...

  4. javascript oo实现(转)

    javascript oo实现 By purplebamboo 7月 13 2014 更新日期:8月 21 2014 文章目录 1. 原始时代最简单的oo实现 2. 石器时代的oo实现 3. 工业时代 ...

  5. Python核心编程--学习笔记--9--文件和输入输出

    本章将深入介绍Python的文件处理和相关输入输出能力,包括:文件对象(以及它的内建函数.内建方法和属性),标准文件,文件系统的访问方法,文件执行,最后简要涉及持久存储和标准库中与文件有关的模块. 1 ...

  6. [div+css布局]命名规则

    //首页可能碰到的 页头:header登录条:loginBar标志:logo侧栏:sideBar广告:banner导航:nav子导航:subNav菜单:menu子菜单:subMenu搜索:search ...

  7. 刀哥多线程之调度组gcd-12-group

    调度组 常规用法 - (void)group1 { // 1. 调度组 dispatch_group_t group = dispatch_group_create(); // 2. 队列 dispa ...

  8. wordpress 开发日志及技巧收集

    搜索结果数量提示 <?php /* Search Count */ $allsearch = &new WP_Query("s=$s&showposts=-1" ...

  9. kettle插入/更新

    1.数据库环境 --------------------实时表 ),Info )); ,'张启山','长沙'); ,'尹新月','长沙'); ,'二月红','长沙'); --------------- ...

  10. Java通过SpyMemcached来缓存数据

    配置好Magent+memcached后,很明显数据之间的输入与输出都是通过代理服务器的,magent是做代理服务器的很明显java在memecached的调用驱动在magent同样适用. 这里选择S ...