2015-04-01 52 views
9

雖然新的Java 8 Stream API我開始納悶,爲什麼不打轉轉:爲什麼地圖<K,V>不能擴展功能<K,V>?

public interface Map<K,V> extends Function<K, V> 

甚至:

public interface Map<K,V> extends Function<K, V>, Predicate<K> 

這將是相當容易的Mapdefault方法來實現interface

@Override default boolean test(K k) { 
    return containsKey(k); 
} 

@Override default V apply(K k) { 
    return get(k); 
} 

而且它將允許使用Mapmap方法:

final MyMagicMap<String, Integer> map = new MyMagicHashMap<>(); 
map.put("A", 1); 
map.put("B", 2); 
map.put("C", 3); 
map.put("D", 4); 

final Stream<String> strings = Arrays.stream(new String[]{"A", "B", "C", "D"}); 
final Stream<Integer> remapped = strings.map(map); 

或者在一個filter方法作爲Predicate

我發現一個Map我的用例中的很大一部分正好是構造或類似構造 - 作爲重新映射/查找Function

那麼,爲什麼JDK設計者在重新設計Java 8的時候沒有決定將這個功能添加到Map

回答

12

的JDK團隊當然知道作爲一個映射函數java.util.Map作爲數據結構和java.util.function.Function之間的數學關係。畢竟,在早期的JDK 8 prototype builds中,Function被命名爲Mapper。調用每個流元素上的函數的流操作稱爲Stream.map

由於轉換函數和Map數據結構之間可能存在混淆,甚至還有關於可能將Stream.map重命名爲transform等其他內容的討論。 (對不起,無法找到鏈接。)此提案被拒絕,理由是概念上的相似性(爲此,map是常用)。

主要問題是,如果java.util.Mapjava.util.function.Function的子類型將會獲得什麼? comments關於子類型是否意味着「是-a」關係進行了一些討論。分型是少談「是一個」對象的關係 - 因爲我們正在談論的接口,而不是類 - 但它確實意味着可替代性。所以,如果Map人的Function一個亞型,人們將能夠做到這一點:

Map<K,V> m = ... ; 
source.stream().map(m).collect(...); 

馬上我們在現在Function.apply什麼是現有Map方法中的一種行爲面臨烘烤。可能唯一明智的是Map.get,如果密鑰不存在,則返回null。坦率地說,這些語義有點糟糕。實際應用中很可能將不得不寫自己的方法反正提供關鍵缺失的政策,因此似乎是能夠編寫

map(m) 

,而不是

map(m::get) 

很少的優勢或

map(x -> m.getOrDefault(x, def)) 
+2

我認爲關於'map :: get'中烘焙的觀點,而不是'getOrDefault'或'computeIfAbsent'或者其他任何如果迄今爲止最引人注目的論證。我可以看到'null'會成爲一個問題,尤其是'Stream'會像'NONNULL'一樣開始。感謝您對設計決策的寶貴意見! – 2015-04-01 16:43:08

4

因爲地圖不是功能。繼承是A是B的關係。不適用於A可以是各種B關係的主題。

爲了有一個功能轉換的關鍵它的價值,你只需要

Function<K, V> f = map::get; 

爲了有一個謂詞測試一個對象是否包含在地圖上,你只需要

Predicate<Object> p = map::contains; 

那比您的建議更清晰和更具可讀性。

+1

Scala中閒逛可見其['Map'不混入一個'PartialFunction'](http://www.scala-lang.org/api/2.11.4/index.html#scala.collection .MapLike)。我想這只是一個不同的設計決定,但它表明,也許它不是那麼清晰...... – 2015-04-01 10:41:38

+2

地圖如何不是一個功能?我認爲將Map定義爲一個函數是完全正確的,例如,如果x不在域中,則映射中由鍵定義的域上的f(k)= v'和'f(x)= null'。 – user2336315 2015-04-01 10:48:48

+0

@ user2336315地圖不是數學意義上的函數。使用相同密鑰的多個呼叫可能會產生不同的結果。 – zeroflagL 2015-04-01 11:03:10

8

的問題是「爲什麼要延長Function?」

您使用strings.map(map)並不真正說明改變的類型繼承(暗示添加方法的Map接口)的想法,例如給予差別不大到strings.map(map::get)。並不清楚使用Map作爲Function是否真的很常見,以至於它應該得到特殊的治療,使用map::remove作爲Function或使用的map::get作爲ToIntFunctionmap::getMap<T,T>作爲BinaryOperator

這在Predicate的情況下更有問題;應該map::containsKey真的得到特殊待遇相比map::containsValue

這也值得注意的方法的類型簽名。Map.getObject → V功能的簽名,而你建議Map<K,V>應該(或只是通過查看型)延長Function<K,V>這是從地圖的概念圖可以理解的,但它表明,有兩個相互矛盾的預期,這取決於你是否看該方法或類型。最好的解決方案不是修復功能類型。然後,你可以指定map::get要麼Function<Object,V>Function<K,V>大家都快樂......

+0

我認爲這是一個比[JB Nizet的答案](http://stackoverflow.com/a/29388651/2071828)更好的說法,我可以在這裏看到邏輯。關鍵的一點似乎是'Map extends Function '會導致Java原語('ToXXXFunction')的問題,並且更重要的是,不能很好地處理(get的)(有點冒險的)簽名 - 那些主要考慮?在這種情況下的基本答案似乎是「遺留問題」...... – 2015-04-01 12:55:44

+1

@蜘蛛蜘蛛:我無法看到開發人員的頭腦。也許他們實際上從來沒有想過這件事。特別是因爲根本沒有試圖將現有類型改裝成功能,例如,讓'Comparator'擴展'ToIntBiFunction'等。也許更簡單的答案是函數和其他類型通常被認爲是不同的。使用'Map'擴展'Function'意味着您可以創建像調用'firstMap.compose(otherMap)'這樣的寶石... ... – Holger 2015-04-01 13:06:08

+5

指責它「遺留」的問題太簡單了。 Holger說得對;正確的問題是「爲什麼要這樣」,而不是「爲什麼不這樣」。並沒有令人信服的理由,特別是考慮將map :: get或list :: contains轉換爲Function或Predicate有多簡單。 – 2015-04-01 15:20:06

相關問題