双冒号(::)运算符在Java 8中被用作方法引用(method reference),方法引用是与lambda表达式相关的一个重要特性。它提供了一种不执行方法的方法。为此,方法引用需要由兼容的函数接口组成的目标类型上下文。

        方法引用可以理解为是让指定的方法去重写接口的抽象方法,到时候调用接口的抽象方法就是调用传递的这个方法

例如:我们可以将方法getSum传递给sunIntArr,当consumer执行accept的时候,就相当于是执行了我们传递的方法getSum

package com.bjc.jdk8.objRef;

import java.util.function.Consumer;

public class Demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,4,5,6,2,3};
        sumIntArr(Demo1::getSum,arr);
    }

    // 定义一个方法用于对数组求和
    public static void getSum(int[] arr){
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        System.out.println(sum);
    }

    public static void sumIntArr(Consumer<int[]> consumer,int[] arr){
        consumer.accept(arr);
    }
}

通过上面的例子,我们可以知道方法引用用到了双冒号::写法,这种被称之为“方法引用”,是一种新的语法

1. 方法引用的格式

符号表示:双冒号::

符号说明:双冒号方法引用运算符,而它所在的表达式被称为方法引用

应用场景:如果Lambda所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用

2. 常见的引用方式

方法引用在JDK8中相当灵活,有以下几种形式:

2.1 对象名::引用成员方法名(instanceName::methodName)

这是最常见的一种用法,如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法。

需要注意的是:

1)被引用的方法,参数要和抽象接口中抽象方法的参数一样

2)当接口抽象方法有返回值时,被引用的方法也必须有返回值

例如:

@Test
public void test01(){
	Supplier<Long> supplier = () -> {
	    return new Date().getTime();
	};
	Long aLong = supplier.get();
	System.out.println(aLong);
}

Supplier函数式接口中有一个get方法,没有参数,有一个返回值,Supplier的泛型类型就是get的返回值,Lambda表达式就相当于是对get方法的一个重写 

可以改成如下引用:

@Test
public void test01(){
	Supplier<Long> supplier = new Date()::getTime;
	Long aLong = supplier.get();
	System.out.println(aLong);
}

2.2.2 类名::引用静态方法名(ClassName::staticMethodName)

        方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!

例如:引用System类的静态方法currentTimeMillis

@Test
public void test02(){
	Supplier<Long> supplier = System::currentTimeMillis;
	Long aLong = supplier.get();
	System.out.println(aLong);
}

2.2.3 类名::引用方法名(ClassName::methodName)

        java面向对象中,类名只能调用静态方法,类名引用实例方法是有前提的,若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName

例如:

@Test
public void test03(){
	Function<String,Integer> func = String::length;
	Integer apply = func.apply("hello");
	System.out.println(apply);

	BiFunction<String,String,String[]> func2 = String::split;
	String[] apply2 = func2.apply("hello",",");
	System.out.println(apply2);
}

在第一个例子中,Function接收的参数有2个,第一个是调用者,第二个返回值,所以使用类名::引用方法名的时候,要求引用方法名不能带参数,且其返回值与Function接口的第二个泛型类型相同

在第二个例子中,BiFunction接收三个参数,第三个参数是返回值,其抽象方法apply接收三个参数,所以,使用类名::引用方法名的时候,要求引用方法名只能带一个参数,且参数类型与BiFunction接口的第二个泛型类型相同,且方法返回值与BiFunction接口的第三个泛型类型相同。

第二个例子就相当于是

BiFunction<String,String,String[]> func3 = (str1,str2) -> {
     return str1.split(str2);
};
System.out.println(func3.apply("hellow",","));

2.2.4 类名::new引用构造器(ClassName::new)

由于构造器的名称与类名完全一样,所以构造器引用使用  类名称::new  的格式表示

@Test
public void test04(){
        Supplier<Person> supplier = Person::new;
	Person person = supplier.get();
	person.setName("张三");
	System.out.println(person);

	Function<String,Person> func = Person::new;
	Person person1 = func.apply("张三1");
	System.out.println(person1);
}

上面的两个例子,当调用无参的构造器的时候,使用supplier的get,有一个参数的构造器就使用Function接口的apply,如果两个参数了?可以使用BiFunction接口了。也就是说构造器的参数列表,需要与函数式接口中参数列表保持一致!

2.2.5 引用数组构造器(TypeName[]::new)

数组也是Object的子类对象,所以同样具有构造器,只是语法稍有不同。

语法:类型[]::new

例如:

public void test8(){
	Function<Integer, String[]> fun = (args) -> new String[args];
	String[] strs = fun.apply(10);
	System.out.println(strs.length);
	
	System.out.println("--------------------------");
	
	Function<Integer, Employee[]> fun2 = Employee[] :: new;
	Employee[] emps = fun2.apply(20);
	System.out.println(emps.length);
}

2.2.6 对象的超类方法引用(super::methodname)

public class Example extends BaseExample{

	@Test
	public void test() {
		List<String> list = Arrays.asList("aaaa", "bbbb", "cccc");
		
		//对象的超类方法语法: super::methodName 
		list.forEach(super::print);
	}
}

class BaseExample {
	public void print(String content){
		System.out.println(content);
	}
}

小结:方法引用是对特定情况下的Lambda表达式的一个简写,它使得Lambda表达式更加的精简,不过需要注意的是,方法引用只能引用已经存在的方法。