假设您要使用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
未在实例化时定义。地图只返回与参数相同的类型。当然,它可以变得更有趣,例如从T
到List<T>
的映射。这没有丑陋的演员吗?
Scala的类型系统通常被认为是更先进的,Scala中是否有解决方案?
如果上述两个问题的答案都是否定的,是否有支持此类输入的语言?
注意: 上面代码中的Map只是一个带有两个通用参数的类型的示例。我对Map尤其不感兴趣,但在类型系统中更多。
答案 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 == value
和put()
都为空,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
声明A
和B
这两种类型的类型必须相同。