2015-06-19 86 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>和类型化获取器和设置器)。