一、概述

在学习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<Person>.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::<Integer>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]

Java8之方法引用的更多相关文章

  1. 30分钟入门Java8之方法引用

    30分钟入门Java8之方法引用 前言 之前两篇文章分别介绍了Java8的lambda表达式和默认方法和静态接口方法.今天我们继续学习Java8的新语言特性--方法引用(Method Referenc ...

  2. java8,方法引用

    1:方法引用,https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html 当我们使用lambda表达式去创建一个 ...

  3. 使用java8的方法引用替换硬编码

    背景 想必大家在项目中都有遇到把一个列表的多个字段累加求和的情况,也就是一个列表的总计.有的童鞋问,这个不是给前端做的吗?后端不是只需要把列表返回就行了嘛...没错,我也是这样想的,但是在一场和前端的 ...

  4. java8 lambda方法引用

    注意引用方法的参数列表与返回值类型要与函数式接口中的抽象方法的参数列表与返回值类型保持一致 主要有三种语法格式: * * 对象::实例方法名 * * 类::静态方法名 * * 类::实例方法名 pub ...

  5. Java笔记——Java8特性之Lambda、方法引用和Streams

    Java8已经推出了好一段时间了,而掌握Java8的新特性也是必要的,如果要进行Spring开发,那么可以发现Spring的官网已经全部使用Java8来编写示例代码了,所以,不学就看不懂. 这里涉及三 ...

  6. Java8新特性之二:方法引用

    上一节介绍了Java8新特性中的Lambda表达式,本小节继续讲解Java8的新特性之二:方法引用.方法引用其实也离不开Lambda表达式. 1.方法引用的使用场景 我们用Lambda表达式来实现匿名 ...

  7. Java8 方法引用

    概述 方法引用是用来直接访问类或实例阴茎存在的方法或者构造方法.它需要由兼容的函数式接口(lambda表达式中用到的接口)构成的目标类型上下文. 有时候, 当我们想要实现一个函数式接口的方法, 但是已 ...

  8. JAVA8之Lambda表达式与方法引用表达式

    一.Lambda表达式 基本语法: lambdaParameters->lambdaBody lambdaParameters传递参数,lambdaBody用于编写逻辑,lambda表达式会生成 ...

  9. Java8特性之Lambda、方法引用和Streams

    这里涉及三个重要特性: Lambda 方法引用 Streams ① Lambda 最早了解Lambda是在C#中,而从Java8开始,Lambda也成为了新的特性,而这个新的特性的目的,就是为了消除单 ...

随机推荐

  1. 小米8 探索版 屏幕指纹版超简单卡刷开发版获取Root权限的教程

    小米的手机不同手机型号通常情况下miui官网都提供两个不同的系统,分别是稳定版和开发版,稳定版没有提供ROOT超级权限管理,开发版中就开启了ROOT超级权限,在很多工作的时候我们需要使用的一些功能强大 ...

  2. windows下QT打包

    1.找到对应的MinGW命令,打开 2.进入exe目录 3.执行windeployqt XX.exe

  3. Spring Boot 相关

    SpringBoot工程 参数解析 HTTP Method Request / Response / Session Error/重定向 Logger IoC AOP/Aspect   1:Sprin ...

  4. Python之--paramiko实例

    一.基于SFTPClient类连接sshd服务器: 特点: 一般用于实现对远程服务器的上传, 下载和对远程目录文件的操作 import pramiko hostname = '172.24.0.110 ...

  5. Redis事务涉及的watch、multi等命令

    Redis Watch 命令 作用: 用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断. 用法: redis 127.0.0.1:6379 ...

  6. MySQL服务器的安装和配置,MySQL Workbench 8.0.12安装,MySQL的基本使用

    一 MySQL服务器的安装和配置 二 MySQL Workbench 8.0.12安装 三 MySQL的基本使用 一MySQL服务器的安装和配置 MySQL是目前最为流行的开放源码的数据库,是完全网络 ...

  7. Centos7安装搭建NTP服务器和NTP客户端同步时间

    NTP简介: NTP是网络时间协议(Network Time Protocol),它是用来同步网络中各个计算机的时间的协议. 在计算机的世界里,时间非常地重要 例如:对于火箭发射这种科研活动,对时间的 ...

  8. Ubuntu 16.04 使用校园网客户端上网

    对于使用校园网的学生来说,安装好Ubuntu之后,很多人需要用 DrClient 客户端来上网,那么怎么操作呢, 这里介绍 DrClient 客户端在Ubuntu上的使用方法, 首先下载 对应版本的软 ...

  9. LeetCode算法题-Relative Ranks(Java实现)

    这是悦乐书的第248次更新,第261篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第115题(顺位题号是506).根据N名运动员的得分,找到他们的相对等级和得分最高的三个 ...

  10. phoenix API服务发布

    概述 Elixir 的 Phoenix 框架对于开发 Web 应用非常方便,不仅有 RoR 的便利,还有 Erlang 的性能和高并发优势. 但是应用的发布涉及到 Erlang 和 Elixir 环境 ...