本文爲個人理解,不保證完全正確。
官方文檔中將雙冒號的用法分爲4類,按照我的個人理解可以分成2類來使用。
官方文檔
官方文檔中將雙冒號的用法分爲了以下4類:
用法 | 舉例 |
---|---|
引用靜態方法 | ContainingClass::staticMethodName |
引用特定對象的實例方法 | containingObject::instanceMethodName |
引用特定類型的任意對象的實例方法 | ContainingType::methodName |
引用構造函數 | ClassName::new |
以下是我的理解
個人理解
雙冒號的作用
在使用雙冒號前我們要先搞清楚一個問題:爲什麼要使用雙冒號?也就是雙冒號的作用是什麼。
雙冒號的設計初衷是爲了化簡Lambda表達式,不熟悉Lambda表達式的同學可以先了解一下。
Lambda表達式的形式有兩種:
- 包含單獨表達式 :parameters -> an expression
list.forEach(item -> System.out.println(item));
- 包含代碼塊:parameters -> { expressions }
list.forEach(item -> {
int numA = item.getNumA();
int numB = item.getNumB();
System.out.println(numA + numB);
});
使用雙冒號可以省略第一種Lambda表達式中的參數部分,即item ->
和調用方法的參數這兩部分。
例如:
//不使用雙冒號
list.forEach(item -> System.out.println(item));
//使用雙冒號
list.forEach(System.out::println);
雙冒號的使用條件
使用雙冒號有兩個條件:
條件1
條件1爲必要條件,必須要滿足這個條件才能使用雙冒號。
Lambda表達式內部只有一條表達式(第一種Lambda表達式),並且這個表達式只是調用已經存在的方法,不做其他的操作。
條件2
由於雙冒號是爲了省略item ->
這一部分,所以條件2是需要滿足不需要寫參數item也知道如何使用item的情況。
有兩種情況可以滿足這個要求,這就是我將雙冒號的使用分爲2類的依據。
情況 | 舉例 |
---|---|
Lambda表達式的參數與調用函數的參數完全一致 | list.forEach(item -> System.out.println(item)) |
調用的函數是參數item對象的方法且沒有參數 | list.stream().map(item -> item.getId()) |
一些栗子
Lambda表達式的參數與調用函數的參數完全一致時
靜態方法調用
//化簡前
list.forEach(item -> System.out.println(item));
//化簡後
list.forEach(System.out::println);
非靜態方法調用
StringBuilder stringBuilder = new StringBuilder();
//化簡前
IntStream.range(1, 101).forEach(item -> stringBuilder.append(item));
//化簡後
IntStream.range(1, 101).forEach(stringBuilder::append);
調用構造方法
官方給出的例子
先定義一個方法,這個方法的作用是將一個集合的內容複製到另一個集合
public <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
DEST transferElements(SOURCE sourceCollection, Supplier<DEST> collectionFactory) {
DEST result = collectionFactory.get();
result.addAll(sourceCollection);
return result;
}
調用這個方法
//化簡前
Set<Person> rosterSetLambda = transferElements(roster, () -> new HashSet<>());
//化簡後
Set<Person> rosterSet = transferElements(roster, HashSet::new);
稍微解釋一下:
調用時傳入的Lambda表達式相當於是對Supplier
的繼承,並重寫Supplier
的get()
方法,下面是Supplier的源碼:
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
在transferElements()
方法中調用collectionFactory.get()
時相當於調用重寫後的方法{return new HashSet<>();}
我自己寫的一個例子
第一個類:
@Data
public class ModelA {
private String id;
public ModelA(String id) {
this.id = id;
}
public ModelA() {
}
}
第二個類
class ClassB {
private final List<ModelA> list = new ArrayList<>();
public void add(String string, Function<String, ModelA> function) {
list.add(function.apply(string));
}
}
測試代碼
ClassB classB = new ClassB();d
//化簡前
classB.add("ddd", item -> new ModelA(item));
//化簡後
classB.add("ddd", ModelA::new);
調用的函數是參數item對象的方法且沒有參數時
//化簡前
List<String> stringList = list.stream().map(item -> item.getId()).collect(Collectors.toList());
//化簡後
List<String> stringList = list.stream().map(ModelA::getId).collect(Collectors.toList());
一種特殊情況
除了上述兩種情況可以使用雙冒號化簡Lambda表達式外,還存在一種特殊情況也可以使用雙冒號。
當Lambda表達式的參數有兩個(形如(a,b) -> an expression
)時,調用a的方法參數爲b時,例如:
String[] stringArray = {"Barbara", "James", "Mary", "John"};
//化簡前
Arrays.sort(stringArray, (a,b) -> a.compareToIgnoreCase(b));
//化簡後
Arrays.sort(stringArray, String::compareToIgnoreCase);