从T到T的地图

时间:2015-06-19 16:48:02

标签: java scala generics

假设您要使用Map并且您有以下要求:您希望每个键映射到相同类型的值。

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映射到T,其中T未在实例化时定义。地图只返回与参数相同的类型。当然,它可以变得更有趣,例如从TList<T>的映射。这没有丑陋的演员吗?

Scala的类型系统通常被认为是更先进的,Scala中是否有解决方案?

如果上述两个问题的答案都是否定的,是否有支持此类输入的语言?

注意: 上面代码中的Map只是一个带有两个通用参数的类型的示例。我对Map尤其不感兴趣,但在类型系统中更多。

3 个答案:

答案 0 :(得分:3)

这并不是你所描述的,但Java中一个众所周知的模式是类型安全的异构容器(Effective Java Item 29),它将Class<T>映射到{T的实例。 1}}。 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 == valueput()都为空,null签入key即可value次。如果只有一个或另一个,你将在班级平等检查中获得NullPointerException

另请注意@ Radiodef的评论 - 每种类型都延伸Object,因此无法使用泛型来防止将两种类型转换为Object并插入。您需要额外的运行时检查来解决这个问题。

如果不需要实现Map,则可以非常轻松地在新类上遵循类似的模式(私有Map<Object, Object>并键入getter和setter)。

答案 1 :(得分:1)

将它们放入Map时,您可以要求键和值具有相同的类型。这个包装器应该工作(所有丑陋都隐藏在内部,行为是类型安全的,但类型应该完全相同,以避免推断一个常见的超类型):

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 = HHMap@101f4db4

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] = shapeless.HMap@764f8d21

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)

答案 2 :(得分: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这两种类型的类型必须相同。