2017-06-07 26 views
9

Collectors.toMap()期間找到重複密鑰條目時,將調用合併功能(o1, o2)如何在Collectors.toMap合併函數中獲取密鑰?

問:我怎樣才能引起重複的關鍵?

String keyvalp = "test=one\ntest2=two\ntest2=three"; 

Pattern.compile("\n") 
    .splitAsStream(keyval) 
    .map(entry -> entry.split("=")) 
    .collect(Collectors.toMap(
     split -> split[0], 
     split -> split[1], 
     (o1, o2) -> { 
      //TODO how to access the key that caused the duplicate? o1 and o2 are the values only 
      //split[0]; //which is the key, cannot be accessed here 
     }, 
    HashMap::new)); 

裏面的合併功能我想在此基礎上,如果我取消映射關鍵來決定,或繼續採取這些值的。

+0

你可以過濾合併後的值,真的需要在合併時過濾它們嗎? –

+0

你能舉個例子嗎?在合併過程中,我必須決定採用哪個值(o1或o2)。必須在**鍵**上做出決定。但關鍵並不總是出現兩次。只有在某些情況下,合併必須決定。 – membersound

+1

好吧,我明白了。您可以創建單獨的Map並運行'.map(entry - > entry.split(「=」))。forEach()'並檢查每個條目是否值已在Map中。如果沒有 - 添加,否則 - 檢查決定是否替換或否。 –

回答

5

您需要使用自定義收集器或使用不同的方法。

Map<String, String> map = new Hashmap<>(); 
Pattern.compile("\n") 
    .splitAsStream(keyval) 
    .map(entry -> entry.split("=")) 
    .forEach(arr -> map.merge(arr[0], arr[1], (o1, o2) -> /* use arr[0])); 

編寫自定義收集器相當複雜。你需要一個TriConsumer(key和兩個值)類似的東西,它不在JDK中,這就是爲什麼我很確定沒有內置的函數。 ;)

4

當您省略合併函數時,合併函數沒有機會獲得密鑰,這是內置函數具有的相同問題。

的解決方案是使用不同的toMap實現,它不依賴於Map.merge

public static <T, K, V> Collector<T, ?, Map<K,V>> 
    toMap(Function<? super T, ? extends K> keyMapper, 
      Function<? super T, ? extends V> valueMapper) { 
    return Collector.of(HashMap::new, 
     (m, t) -> { 
      K k = keyMapper.apply(t); 
      V v = Objects.requireNonNull(valueMapper.apply(t)); 
      if(m.putIfAbsent(k, v) != null) throw duplicateKey(k, m.get(k), v); 
     }, 
     (m1, m2) -> { 
      m2.forEach((k,v) -> { 
       if(m1.putIfAbsent(k, v)!=null) throw duplicateKey(k, m1.get(k), v); 
      }); 
      return m1; 
     }); 
} 
private static IllegalStateException duplicateKey(Object k, Object v1, Object v2) { 
    return new IllegalStateException("Duplicate key "+k+" (values "+v1+" and "+v2+')'); 
} 

(這基本上就是Java的9的實施toMap沒有合併功能將做)

因此,您需要在代碼中執行的操作是重定向toMap調用並省略合併功能:

String keyvalp = "test=one\ntest2=two\ntest2=three"; 

Map<String, String> map = Pattern.compile("\n") 
     .splitAsStream(keyvalp) 
     .map(entry -> entry.split("=")) 
     .collect(toMap(split -> split[0], split -> split[1])); 

(或ContainingClass.toMap如果既沒有在同一個班,也不是一成不變進口)< \ SUP>

的收集器支持像原來toMap收集器的並行處理,雖然它不太可能在這裏得到並行處理的好處即使有更多的元素需要處理。

如果,如果我讓你正確,你只需要任意選取,舊或新的價值,基於實際鍵合併功能,您可以用鑰匙Predicate這樣

public static <T, K, V> Collector<T, ?, Map<K,V>> 
    toMap(Function<? super T, ? extends K> keyMapper, 
      Function<? super T, ? extends V> valueMapper, 
      Predicate<? super K> useOlder) { 
    return Collector.of(HashMap::new, 
     (m, t) -> { 
      K k = keyMapper.apply(t); 
      m.merge(k, valueMapper.apply(t), (a,b) -> useOlder.test(k)? a: b); 
     }, 
     (m1, m2) -> { 
      m2.forEach((k,v) -> m1.merge(k, v, (a,b) -> useOlder.test(k)? a: b)); 
      return m1; 
     }); 
} 
Map<String, String> map = Pattern.compile("\n") 
     .splitAsStream(keyvalp) 
     .map(entry -> entry.split("=")) 
     .collect(toMap(split -> split[0], split -> split[1], key -> condition)); 

有幾種方式來定製這個收集器...

+1

我不知道你聽到這一天有多少次,但是謝謝你... – Eugene

0

有,當然,簡單的和瑣碎的絕招 - 保存在「鍵映射」功能鍵和拿到鑰匙在「合併」功能。因此,代碼可能如下所示(假設密鑰爲整數):

final AtomicInteger key = new AtomicInteger(); 
...collect(Collectors.toMap( 
    item -> { key.set(item.getKey()); return item.getKey(); }, // key mapper 
    item -> ..., // value mapper 
    (v1, v2) -> { log(key.get(), v1, v2); return v1; } // merge function 
); 

注意:這不適合並行處理。