【Java 8】方法引用
一、概述
在学习lambda表达式之后,我们通常使用lambda表达式来创建匿名方法。然而,有时候我们仅仅是调用了一个已存在的方法。如下:
Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2));
在Java8中,我们可以直接通过方法引用来简写lambda表达式中已经存在的方法。
Arrays.sort(stringsArray, String::compareToIgnoreCase);
这种特性就叫做方法引用(Method Reference)。
二、什么是方法引用
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。
注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。
============================================================================
简单地说,就是一个Lambda表达式。在Java 8中,我们会使用Lambda表达式创建匿名方法,但是有时候,我们的Lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰,Java 8的方法引用允许我们这样做。方法引用是一个更加紧凑,易读的Lambda表达式,注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。
三、方法引用例子
先看一个例子,首先定义一个Person类,如下:
package com.demo.model;
import java.time.LocalDate;
public class Person {
public Person(String name, LocalDate birthday) {
this.name = name;
this.birthday = birthday;
}
String name;
LocalDate birthday;
public LocalDate getBirthday() {
return birthday;
}
public static int compareByAge(Person a, Person b) {
return a.birthday.compareTo(b.birthday);
}
@Override
public String toString() {
return this.name;
}
}
假设我们有一个Person数组,并且想对它进行排序,这时候,我们可能会这样写:
原始写法,使用匿名类:
package com.demo;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Comparator;
import org.junit.Test;
import com.demo.model.Person;
public class testMethodReference {
@Test
public void test() {
Person[] pArr = new Person[]{
new Person("003", LocalDate.of(2016,9,1)),
new Person("001", LocalDate.of(2016,2,1)),
new Person("002", LocalDate.of(2016,3,1)),
new Person("004", LocalDate.of(2016,12,1))};
// 使用匿名类
Arrays.sort(pArr, new Comparator<Person>() {
@Override
public int compare(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
});
System.out.println(Arrays.asList(pArr));
}
}
其中,Arrays类的sort方法定义如下:
public static <T> void sort(T[] a, Comparator<? super T> c)
这里,我们首先要注意Comparator接口是一个函数式接口,因此我们可以使用Lambda表达式,而不需要定义一个实现Comparator接口的类,并创建它的实例对象,传给sort方法。
使用Lambda表达式,我们可以这样写:
改进一,使用Lambda表达式,未调用已存在的方法
@Test
public void test1() {
Person[] pArr = new Person[]{
new Person("003", LocalDate.of(2016,9,1)),
new Person("001", LocalDate.of(2016,2,1)),
new Person("002", LocalDate.of(2016,3,1)),
new Person("004", LocalDate.of(2016,12,1))};
//使用lambda表达式
Arrays.sort(pArr, (Person a, Person b) -> {
return a.getBirthday().compareTo(b.getBirthday());
});
System.out.println(Arrays.asList(pArr));
}
然而,在以上代码中,关于两个人生日的比较方法在Person类中已经定义了,因此,我们可以直接使用已存在的Person.compareByAge方法。
改进二,使用Lambda表达式,调用已存在的方法
@Test
public void test2() {
Person[] pArr = new Person[]{
new Person("003", LocalDate.of(2016,9,1)),
new Person("001", LocalDate.of(2016,2,1)),
new Person("002", LocalDate.of(2016,3,1)),
new Person("004", LocalDate.of(2016,12,1))};
//使用lambda表达式和类的静态方法
Arrays.sort(pArr, (a ,b) -> Person.compareByAge(a, b));
System.out.println(Arrays.asList(pArr));
}
因为这个Lambda表达式调用了一个已存在的方法,因此,我们可以直接使用方法引用来替代这个Lambda表达式。
改进三,使用方法引用
@Test
public void test3() {
Person[] pArr = new Person[]{
new Person("003", LocalDate.of(2016,9,1)),
new Person("001", LocalDate.of(2016,2,1)),
new Person("002", LocalDate.of(2016,3,1)),
new Person("004", LocalDate.of(2016,12,1))};
//使用方法引用,引用的是类的静态方法
Arrays.sort(pArr, Person::compareByAge);
System.out.println(Arrays.asList(pArr));
}
运行结果:
[001, 002, 003, 004]
在以上代码中,方法引用Person::compareByAge在语义上与Lambda表达式 (a, b) -> Person.compareByAge(a, b) 是等同的,都有如下特性:
- 真实的参数是拷贝自Comparator.compare方法,即(Person, Person);
- 表达式体调用Person.compareByAge方法。
四、四种方法引用类型
方法引用的标准形式是:类名::方法名。(注意:只需要写方法名,不需要写括号)
有以下四种形式的方法引用:
| 类型 | 示例 |
|---|---|
| 引用静态方法 | ContainingClass::staticMethodName |
| 引用某个对象的实例方法 | containingObject::instanceMethodName |
| 引用某个类型的任意对象的实例方法 | ContainingType::methodName |
| 引用构造方法 | ClassName::new |
下面我们通过一个小Demo来分别学习这几种形式的方法引用:
1、静态方法引用
组成语法格式:ClassName*:staticMethodName
我们前面举的例子Person::compareByAge就是一个静态方法引用。
注意:
- 静态方法引用比较容易理解,和静态方法调用相比,只是把 . 换为 ::
- 在目标类型兼容的任何地方,都可以使用静态方法引用。
例子:
String::valueOf 等价于lambda表达式 (s) -> String.valueOf(s)
Math::pow 等价于lambda表达式 (x, y) -> Math.pow(x, y);
字符串反转的例子:
package com.demo;
/**
* 函数式接口
*/
public interface StringFunc {
String func(String n);
}
package com.demo;
public class MyStringOps {
//静态方法: 反转字符串
public static String strReverse(String str) {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i);
}
return result;
}
}
package com.demo;
public class MethodRefDemo {
public static String stringOp(StringFunc sf, String s) {
return sf.func(s);
}
public static void main(String[] args) {
String inStr = "lambda add power to Java";
//MyStringOps::strReverse 相当于实现了接口方法func()
// 并在接口方法func()中作了MyStringOps.strReverse()操作
String outStr = stringOp(MyStringOps::strReverse, inStr);
System.out.println("Original string: " + inStr);
System.out.println("String reserved: " + outStr);
}
}
输出结果:
Original string: lambda add power to Java
String reserved: avaJ ot rewop dda adbmal
表达式MyStringOps::strReverse的计算结果为对象引用,其中,strReverse提供了StringFunc的func()方法的实现。
找到列表中具有最大值的对象
package com.demo;
public class MyClass {
private int val;
MyClass(int v) {
val = v;
}
public int getValue() {
return val;
}
}
package com.demo;
import java.util.ArrayList;
import java.util.Collections;
public class UseMethodRef {
public static int compareMC(MyClass a, MyClass b) {
return a.getValue() - b.getValue();
}
public static void main(String[] args) {
ArrayList<MyClass> a1 = new ArrayList<MyClass>();
a1.add(new MyClass(1));
a1.add(new MyClass(4));
a1.add(new MyClass(2));
a1.add(new MyClass(9));
a1.add(new MyClass(3));
a1.add(new MyClass(7));
//UseMethodRef::compareMC生成了抽象接口Comparator定义的compare()方法的实例。
MyClass maxValObj = Collections.max(a1, UseMethodRef::compareMC);
System.out.println("Maximum value is: " + maxValObj.getValue());
}
}
输出结果:
Maximum value is: 9
UseMethodRef定义了静态方法compareMC(),它与Comparator定义的compare()方法兼容。因此,没有必要显式的实现Comparator接口并创建其实例。
2、特定实例对象的方法引用
这种语法与用于静态方法的语法类似,只不过这里使用对象引用而不是类名。****实例方法引用又分以下三种类型:
a.实例上的实例方法引用
组成语法格式:instanceReference::methodName
如下示例,引用的方法是myComparisonProvider 对象的compareByName方法。
class ComparisonProvider{
public int compareByName(Person a, Person b){
return a.getName().compareTo(b.getName());
}
public int compareByAge(Person a, Person b){
return a.getBirthday().compareTo(b.getBirthday());
}
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
例子:反转字符串
package com.demo;
/**
* 函数式接口
*/
public interface StringFunc {
String func(String n);
}
package com.demo;
public class MyStringOps {
//普通方法: 反转字符串
public String strReverse1(String str) {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i);
}
return result;
}
}
package com.demo;
public class MethodRefDemo2 {
public static String stringOp(StringFunc sf, String s) {
return sf.func(s);
}
public static void main(String[] args) {
String inStr = "lambda add power to Java";
MyStringOps strOps = new MyStringOps();//实例对象
//strOps::strReverse1 相当于实现了接口方法func()
String outStr = stringOp(strOps::strReverse1, inStr);
System.out.println("Original string: " + inStr);
System.out.println("String reserved: " + outStr);
}
}
输出结果:
Original string: lambda add power to Java
String reserved: avaJ ot rewop dda adbmal
b.超类上的实例方法引用
组成语法格式:super::methodName
方法的名称由methodName指定,通过使用super,可以引用方法的超类版本。
例子:
还可以捕获this 指针,this :: equals 等价于lambda表达式 x -> this.equals(x);
c.类型上的实例方法引用
组成语法格式:ClassName::methodName
注意:
若类型的实例方法是泛型的,就需要在::分隔符前提供类型参数,或者(多数情况下)利用目标类型推导出其类型。
静态方法引用和类型上的实例方法引用拥有一样的语法。编译器会根据实际情况做出决定。一般我们不需要指定方法引用中的参数类型,因为编译器往往可以推导出结果,但如果需要我们也可以显式在::分隔符之前提供参数类型信息。
例子:
String::toString 等价于lambda表达式 (s) -> s.toString()
这里不太容易理解,实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。
在泛型类或泛型方法中,也可以使用方法引用。
package com.demo;
public interface MyFunc<T> {
int func(T[] als, T v);
}
package com.demo;
public class MyArrayOps {
public static <T> int countMatching(T[] vals, T v) {
int count = 0;
for (int i = 0; i < vals.length; i++) {
if (vals[i] == v) count++;
}
return count;
}
}
package com.demo;
public class GenericMethodRefDemo {
public static <T> int myOp(MyFunc<T> f, T[] vals, T v) {
return f.func(vals, v);
}
public static void main(String[] args){
Integer[] vals = {1, 2, 3, 4, 2, 3, 4, 4, 5};
String[] strs = {"One", "Two", "Three", "Two"};
int count;
count=myOp(MyArrayOps::<Integer>countMatching, vals, 4);
System.out.println("vals contains "+count+" 4s");
count=myOp(MyArrayOps::<String>countMatching, strs, "Two");
System.out.println("strs contains "+count+" Twos");
}
}
输出结果:
vals contains 3 4s
strs contains 2 Twos
分析:
在程序中,MyArrayOps是非泛型类,包含泛型方法countMatching()。该方法返回数组中与指定值匹配的元素的个数。注意这里如何指定泛型类型参数。例如,在main()方法中,对countMatching()方法的第一次调用如下所示:count = myOp(MyArrayOps::countMatching,vals,4); 这里传递了类型参数Integer。
注意,参数传递发生在::的后面。这种语法可以推广。当把泛型方法指定为方法引用时,类型参数出现在::之后、方法名之前。但是,需要指出的是,在这种情况(和其它许多情况)下,并非必须显示指定类型参数,因为类型参数会被自动推断得出。对于指定泛型类的情况,类型参数位于类名的后面::的前面。
3、任意对象(属于同一个类)的实例方法引用
如下示例,这里引用的是字符串数组中任意一个对象的compareToIgnoreCase方法。
String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
4、构造方法引用
构造方法引用又分构造方法引用和数组构造方法引用。
a.构造方法引用(也可以称作构造器引用)
组成语法格式:Class::new
构造函数本质上是静态方法,只是方法名字比较特殊,使用的是new 关键字。
例子:
String::new, 等价于lambda表达式 () -> new String()
package com.demo;
public interface MyFunc1 {
MyClass func(int n);
}
package com.demo;
public class MyClass {
private int val;
MyClass(int v) {
val = v;
}
MyClass(){
val = 0;
}
public int getValue() {
return val;
}
}
package com.demo;
public class ConstructorRefDemo {
public static void main(String[] args) {
MyFunc1 myClassCons = MyClass :: new;
MyClass mc = myClassCons.func(100);
System.out.println("val in mc is: " + mc.getValue());
}
}
输出结果:
val in mc is: 100
b.数组构造方法引用
组成语法格式:TypeName[]::new
例子:
int[]::new 是一个含有一个参数的构造器引用,这个参数就是数组的长度。等价于lambda表达式 x -> new int[x]。
假想存在一个接收int参数的数组构造方法
IntFunction<int[]> arrayMaker = int[]::new;
int[] array = arrayMaker.apply(10) // 创建数组 int[10]
【Java 8】方法引用的更多相关文章
- Java 8 新特性-菜鸟教程 (2) -Java 8 方法引用
Java 8 方法引用 方法引用通过方法的名字来指向一个方法. 方法引用可以使语言的构造更紧凑简洁,减少冗余代码. 方法引用使用一对冒号 :: . 下面,我们在 Car 类中定义了 4 个方法作为例子 ...
- JAVA 8 方法引用 - Method References
什么是方法引用 简单地说,就是一个Lambda表达式.在Java 8中,我们会使用Lambda表达式创建匿名方法,但是有时候,我们的Lambda表达式可能仅仅调用一个已存在的方法,而不做任何其它事,对 ...
- Java 8 方法引用
转自:https://www.runoob.com/java/java8-method-references.html 方法引用通过方法的名字来指向一个方法. 方法引用可以使语言的构造更紧凑简洁,减少 ...
- Java 8方法引用使用指南
[编者按]本文作者为拥有15年 Java 开发经验的资深程序员 Per-Åke Minborg,主要介绍如何灵活地解析 Java 中的方法引用.文章系国内 ITOM 管理平台 OneAPM 编译呈现. ...
- Java 之 方法引用
方法引用 一.冗余的Lambda场景 来看一个简单的函数式接口以应用Lambda表达式: @FunctionalInterface public interface Printable { void ...
- Java(43)JDK新特性之方法引用
作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15228461.html 博客主页:https://www.cnblogs.com/testero ...
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...
- Java 8函数编程轻松入门(四)方法引用
C#中系统提供了许多IEnumerable的扩展方法.同样在Java 8中新引入了Collector类. 1.方法引用 定义: 简而言之:就是一个Lambda表达式.在Java 8中,我们我们会使用L ...
- Java笔记——Java8特性之Lambda、方法引用和Streams
Java8已经推出了好一段时间了,而掌握Java8的新特性也是必要的,如果要进行Spring开发,那么可以发现Spring的官网已经全部使用Java8来编写示例代码了,所以,不学就看不懂. 这里涉及三 ...
- Java:方法的参数是传值还是传引用
Java中方法的参数总是采用传值的方式. 下列方法欲实现对象的交换,但实际上是不能实现的. public void swap(simpleClass a,simpleClass b){ simpleC ...
随机推荐
- springboot使用之请求参数与基本注解
@PathVariable 作用:@PathVariable是spring3.0的一个新功能:接收请求路径中占位符的值,将URL中占位符参数{xxx}绑定到处理器类的方法形参中@PathVariabl ...
- 在代码生成工具Database2Sharp中增加Vue&Element 工作流页面的快速生成
在我们基于框架开发系统的时候,往往对一些应用场景的页面对进行了归纳总结,因此对大多数情况下的页面呈现逻辑都做了清晰的分析,因此在我们基于框架的基础上,增量式开发业务功能的时候,能够事半功倍.代码生成工 ...
- [前端随笔][Vue] 多级菜单实现思路——组件嵌套
说在前面 本篇记录学习了vue-element-admin中的多级菜单的实现 [传送门] @vue/cli 4.2.2:vuex:scss:组件嵌套 正文 创建项目 npm create 项目名 // ...
- [源码解析] PyTorch分布式(6) -------- DistributedDataParallel -- 初始化&store
[源码解析] PyTorch分布式(6) ---DistributedDataParallel -- 初始化&store 目录 [源码解析] PyTorch分布式(6) ---Distribu ...
- [cf1261F]Xor-Set
构造一棵权值范围恰为$[0,2^{60})$的权值线段树,考虑其中从下往上第$h$层($0\le h\le 60$)中的一个区间,假设其左端点为$l$,即$[l,l+2^{h})$ 这样的一个区间具有 ...
- 华为9.8笔试题C++
问题 给出一颗二叉树,每个节点有一个编号和一个值,该值可能为负数,请你找出一个最优节点(除根节点外),使得在该节点将树分成两棵树后(原来的树移除这个节点及其子节点,新的树以该节点为根节点),分成的两棵 ...
- jenkins cron
1. Jenkins cron syntax Jenkins Cron 语法遵循Cron实用程序的语法(略有不同)具体来说,每行包含由TAB或SPACE分隔的5个字段(分时日月周): 分钟(Minut ...
- P2066 机器分配 解析
小日记: 1.今天新学的字体颜色,尽管不熟悉,但玩的666,卡星(开心) ╰( ̄▽ ̄)╮╰( ̄▽ ̄)╮╰( ̄▽ ̄)╮╰( ̄▽ ̄)╮╰( ̄▽ ̄)╮╰( ̄▽ ̄)╮ 2.今天油腔滑调,谅解亿下 P2066 ...
- IEnumerator vs Iterator
IEnumerator vs Iterator IEnumerator 不考虑Reset方法和原始列表的可变性,NoveNext和Current的只读向前用法: Initially, the enum ...
- 洛谷 P6199 - [EER1]河童重工(点分治+虚树)
洛谷题面传送门 神仙题. 首先看到这样两棵树的题目,我们肯定会往动态树分治的方向考虑.考虑每次找出 \(T_2\) 的重心进行点分治.然后考虑跨过分治中心的点对之间的连边情况.由于连边边权与两棵树都有 ...