1. 程式人生 > 程式設計 >淺談對Java雙冒號::的理解

淺談對Java雙冒號::的理解

本文為個人理解,不保證完全正確。
官方文件中將雙冒號的用法分為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);

到此這篇關於淺談對Java雙冒號::的理解的文章就介紹到這了,更多相關Java雙冒號::內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!