2015-06-19 83 views
2

假設您想使用Map,並且您有以下要求:您希望每個鍵映射到相同類型的值。從T到T的映射

Map<???> map = ...; 
map.put(42, 15); 
map.put("hello world", 15); // compile time error, because you cannot map from string to int. 
map.put("hello world", "foobar"); 
map.put(new Foo(), new Foo()); 
Integer i = map.get(42); 
String s = map.get("hello world"); 
Foo f = map.get(new Foo()); 

當然上面的代碼將無法編譯但訣竅是,你從映射到T其中TT沒有實例化定義。地圖只是返回與參數相同的類型。當然,它可以變得更有趣,如從TList<T>的映射。這沒有醜陋的鑄造?

斯卡拉的類型系統通常被認爲是更先進的,是否有在斯卡拉解決方案?

如果上述兩個問題的答案都是否定的,是否有任何語言支持這種類型?

注意: 上述代碼中的映射只是一個帶有兩個通用參數的類型的示例。我對地圖不是特別感興趣,但更多的是在類型系統中。

+0

我添加了撥打電話,我希望現在更清楚。 –

+0

所以......你想要一個'Map'數據結構,它包含*你喜歡的任何*作爲鍵和值? – Makoto

+4

[無形](https://github.com/milessabin/shapeless)是一個Scala庫,它提供了一種類似於這種(稱爲「'HMap'」)的類型(https:// github。 COM/milessabin /不成形/維基/功能 - 概述:-shapeless-2.0.0#異質-地圖)。 –

回答

1

將它們放入地圖時,您可以要求鍵和值的類型相同。此包裝應該工作(所有的醜陋是隱藏內部和行爲是類型安全的,但類型應該是完全平等的,避免推斷一個共同的超類型):

class HHMap { 
    val map = Map[Any, Any]() 
    def put[T, U](k: T, v: U)(implicit ev: T =:= U) = map.put(k, v).asInstanceOf[Option[T]] 
    def get[T](k: T) = map.get(k).asInstanceOf[Option[T]] 
} 

scala> val m = new HHMap 
m: HHMap = [email protected] 

scala> m.put(5,6) 
res8: Option[Int] = None 

scala> m.put(5,"a") 
<console>:13: error: Cannot prove that Int =:= String. 
       m.put(5,"a") 
       ^

scala> m.put("b","a") 
res10: Option[String] = None 

如果你想完全擺脫的asInstanceOf,看到Shapeless2 HMap

class BiMapIS[K, V] 
implicit def TtoT[T] = new BiMapIS[T, T] 
val hm = HMap[BiMapIS](23 -> 23, "bar" -> "foo") 

scala> val hm = HMap[BiMapIS](23 -> 23, "bar" -> "foo") 
hm: shapeless.HMap[BiMapIS] = [email protected] 

scala> val hm = HMap[BiMapIS](23 -> 23, "bar" -> 99) 
<console>:12: error: could not find implicit value for parameter ev1: BiMapIS[String,Int] 
    val hm = HMap[BiMapIS](23 -> 23, "bar" -> 99) 
0

是的,這是可能的一個隱含的證據參數:

trait MyMap { 
    def put[A, B](key: A, value: B)(implicit ev: A =:= B): Unit 
    def get[A](key: A): A 
} 

val map: MyMap = ... 
map.put(5, 8) // OK 
map.put(5, "h") // Compile error 
map.put("abc", "def") // OK 

A =:= B指出AB這兩種類型必須是相同的類型。

3

這不正是你所描述的東西,但在Java著名的模式是類型安全的異構容器(Effective Java Item 29),其中Class<T>映射到的T一個實例。 Guava提供了一個實現這種模式的接口ClassToInstanceMap


在Java中,你可以陪審團鑽機您所描述的類型,但它不會是漂亮。

public class TTMap extends ForwardingMap<Object, Object> { 
    private final HashMap<Object, Object> delegate = new HashMap<>(); 
    @Override 
    protected Map<Object, Object> delegate() { 
    return delegate(); 
    } 

    @SuppressWarnings("unchecked") 
    public <T> T getType(T key) { 
    return (T)delegate().get(key); 
    } 

    @SuppressWarnings("unchecked") 
    public <T> T putType(T key, T value) { 
    return (T)delegate().put(key, value); 
    } 

    @Override @Deprecated 
    public Object put(Object key, Object value) { 
    Preconditions.checkState(key == value || value.getClass().equals(key.getClass())); 
    return delegate.put(key, value); 
    } 

    @Override @Deprecated 
    public void putAll(Map<? extends Object, ? extends Object> map) { 
    standardPutAll(map); 
    } 
} 

這將使用ForwardingMap給你一個Map<Object, Object>但隨着運行時間約束鍵和值將始終是同一類型的。您需要使用getType()putType()以避免運行時錯誤; put()putAll()只能對運行時異常執行此約束。如果需要,您還可以添加putAll(TTMap)方法。

key == value檢查中put()允許null S作爲只要雙方keyvalue爲空。如果只有其中一個,你會在等級檢查中得到NullPointerException

另請注意@ Radiodef的評論 - 每種類型延伸Object,所以沒有辦法 - 用泛型 - 防止將兩種類型轉換爲Object並插入。您需要額外的運行時檢查來解決這個問題。

如果不需要執行Map,則可以非常容易地在類中遵循類似的模式(私人Map<Object, Object>和類型化獲取器和設置器)。