概述双冒号(::)是 Java 8 引入 Lambda 表达式后的一种用法,表示方法引用(method reference),可以更加简洁的实例化接口
双冒号表达式返回的是一个 函数式接口对象(用 @FunctionalInterface 注解的 interface 类型)的实例,如下: 1
2
3
4
5
6
7
8
9
10
11
12
13@Test
public void test0(){
List list = Arrays.asList(1, 2, 3);
Consumer consumer = System.out::println;
list.forEach(consumer);
}
// java.util.function.Consumer
@FunctionalInterface
public interface Consumer{
void accept(T t);
}
方法引用 Method Reference 具体使用双冒号(::)运算符在 Java 8 中被用作方法引用(method reference),方法引用是与 lambda 表达式相关的一个重要特性。
它提供了一种不执行方法的方法:双冒号的方式只是指明方法引用,具体执行还是传统的方式。
方法引用需要兼容 函数式接口 组成的目标类型上下文:也就是说被引用的方法的参数和 函数式接口 的参数类型必须一致。
具体使用方式有以下几种静态方法引用(Reference to a static method)语法:ContainingClass::staticMethodName
例如:Person::getAge
对象的实例方法引用(Reference to an instance method of a particular object)语法:containingObject::instanceMethodName
例如:System.out::println
特定类型的任意对象实例的方法(Reference to an instance method of an arbitrary object of a particular type)语法:(ContainingType::methodName)
例如:String::compareToIgnoreCase
类构造器引用语法(Reference to a constructor):语法:ClassName::new
例如:ArrayList::new
1 静态方法引用(Reference to a static method)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class StringUtils{
public static void toUpperCase(String str){
System.out.println(str.toUpperCase());
}
public static void toInt(Long data){
System.out.println(data.intValue());
}
}
@Test
public void test(){
List list = Arrays.asList("aaaa", "bbbb", "cccc");
// 参数是 String, 正确
list.forEach(StringUtils::toUpperCase);
// 参数是 Long, 报错
//list.forEach(StringUtils::toInt);
}
2 对象的实例方法引用(Reference to an instance method of a particular object)1
2
3
4
5
6
7
8
9
10
11
12public class StringUtils{
public void toLowerCase(String str){
System.out.println(str.toLowerCase());
}
}
@Test
public void test2(){
List list = Arrays.asList("AAAA", "BBBB", "CCCC");
// 参数是 String, 正确
list.forEach(new StringUtils()::toLowerCase);
}
3 特定类型的任意对象实例的方法(Reference to an instance method of an arbitrary object of a particular type)先看下官方的例子
1
2
3
4
5
6
7
8@Test
public void test3(){
String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
for (int i = 0; i < stringArray.length; i++) {
System.out.println(stringArray[i]);
}
}
String::compareToIgnoreCase:看起来 compareToIgnoreCase 方法应该是 String 类的 static 方法,实际上不是,这怎么理解呢?下面来一一分解:
这里的 particular type 是指 String 类型
arbitrary object 是指 String 对象
instance method 是指 String 对象的 compareToIgnoreCase
Arrays.sort(stringArray, String::compareToIgnoreCase); 用 lambda 可以写成 Arrays.sort(stringArray, (o1, o2) -> o1.compareToIgnoreCase(o2));
可见 Arrays.sort(T[] a, Comparator super T> c) 中的 Comparator 实例第一个参数 o1 为 String 对象本身,o2 为 compareToIgnoreCase 方法的第一个参数,如果还有 o3 那就是第二参数
基于上面的分析写个例子:
1
2
3
4@FunctionalInterface
public interface Append{
String append(StringAppend a, StringAppend b, StringAppend c);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34public class StringAppend{
private String data;
public String getData(){
return data;
}
public StringAppend(){
}
public StringAppend(String data){
this.data = data;
}
public String append(StringAppend a, StringAppend b){
return this.getData() + a.getData() + b.getData();
}
public static String doAppend(String[] arr, Append append){
String result = "";
for (int i = 0; i < arr.length; i++) {
if (i+2 <= arr.length-1) {
result += append.append(new StringAppend(arr[i]), new StringAppend(arr[i+1]), new StringAppend(arr[i+2]));
i += 2;
} else {
result += append.append(new StringAppend(arr[i]), new StringAppend(""), new StringAppend(""));
}
}
return result;
}
}
1
2
3
4
5
6
7
8@Test
public void test5(){
String[] stringArray = { "Barbara", "James", "Mary", "John", "Mike" };
String str1 = StringAppend.doAppend(stringArray, (t1,t2,t3) -> t1.getData()+t2.getData()+t3.getData());
String str2 = StringAppend.doAppend(stringArray, StringAppend::append);
Assert.assertTrue(str1.equals(str2));
System.out.println(str1);
}
最终输出为: BarbaraJamesMaryJohnMike
4 类构造函数用法(Reference to a constructor)
下面的 ITool 和 JSONTool 用于将字符串转成 JSON 字符串
接口定义是传入一个字符串 name, 返回一个 JSONTool 对象
1
2
3public interface ITool{
JSONTool create(String name);
}
JSONTool 类的构造函数:public JSONTool(String name)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import com.alibaba.fastjson.JSONObject;
public class JSONTool{
private String name;
public JSONTool(String name){
this.name = name;
}
public String getName(){
return name;
}
private static JSONObject parseJSON(JSONTool jsonTool){
JSONObject jsonObject = new JSONObject();
jsonObject.put(jsonTool.getName(), jsonTool.getName());
return jsonObject;
}
public static String getJSONString(String name, ITool iTool){
return parseJSON(iTool.create(name)).toJSONString();
}
}
测试如下
1
2
3
4@Test
public void test4(){
System.out.println(JSONTool.getJSONString("a", JSONTool::new));
}
输出如下
1{"a":"a"}
上面的例子分析如下:
从字面上看 JSONTool::new 返回的是 ITool 接口对象的实例
因此通过在 getJSONString 方法中调用 ITool 接口对象的实例的 create 方法返回一个 JSONTool 对象
通过 lambda 也可以返回一个 ITool 接口对象实例:t -> new JSONTool(t),因此测试代码写成下面的方式也可以
1
2
3
4
5@Test
public void test4(){
//System.out.println(JSONTool.getJSONString("a", JSONTool::new));
System.out.println(JSONTool.getJSONString("a", t -> new JSONTool(t)));
}
通过 Java 1.8 以前的匿名类方法如下
1
2
3
4
5
6
7
8
9
10
11@Test
public void test4(){
//System.out.println(JSONTool.getJSONString("a", JSONTool::new));
//System.out.println(JSONTool.getJSONString("a", t -> new JSONTool(t)));
System.out.println(JSONTool.getJSONString("a", new ITool() {
@Override
public JSONTool create(String name){
return new JSONTool(name);
}
}));
}
总结双冒号相比 Lambda 表达式更加简洁
将方法作为接口的实例来使用
参考