双冒号(::)运算符在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表达式更加的精简,不过需要注意的是,方法引用只能引用已经存在的方法。