这无法完全解决,因为您要放入所有可能类型的Future,因此一旦您忘记了确切类型一次,就永远无法 100% 编译时保证您可以恢复它。您必须自己转换类型并希望它匹配。
我看到了解决这个问题的两种方法:
- 在密钥中编码类型(不要忘记在密钥中!),以便您知道可以安全地转换为哪种类型
- 使用已擦除类型获取结果并尝试强制转换并处理可能的失败
第一个可以完成,例如像这样:
trait CacheKey {
type Out
}
object CacheKey {
type Aux[O] = CacheKey { type Out = O }
}
case object GlobalSetting extends CacheKey { type Out = String }
case class UserSetting(userID: UUID) extends CacheKey { type Out = Int }
class Cache() {
private val underlying =
scala.collection.mutable.Map.empty[CacheKey, Future[_]]
def getOrPut[O](
key: CacheKey.Aux[O],
value: => Future[O] // should be by-name!
): Future[O] = underlying.get(key) match {
case Some(storedVal) =>
storedVal.asInstanceOf[Future[O]]
case None =>
underlying.update(key, val)
value
}
}
使用String,您可以尝试这样做:
cache.getOrPut("foo", intFuture)
cache.getOrPut("foo", stringFuture)
第一个调用会设置缓存,第二个调用会返回一个结果类型无效的 Future,所以如果你只是强制转换,你会在 Future 的某个随机点收到ClassCastException。
另一种方法是始终获取结果,然后检查它是否有效(这很难做到防弹):
class Cache() {
private val underlying =
scala.collection.mutable.Map.empty[CacheKey, (ClassTag[_], Future[_])]
def getOrPut[O: ClassTag](
key: String,
value: => Future[O] // should be by-name!
): Future[O] = underlying.get(key) match {
case Some((originalTag, storedVal)) =>
if (classTag[O] == originalTag)
storedVal.asInstanceOf[Future[O]]
else
throw new Exception("Mismatched cache types")
case None =>
underlying.update(key, val)
value
}
}
这种方法的问题是ClassTag 没有区分具有不同类型参数的泛型,例如classTag[List[Int]] == classTag[List[String]] 返回 true。但是,对于任何基于运行时反射的解决方案,无论您选择什么,都会遇到这个或类似的问题。
使用编译时反射,您可以完全避免该问题,但在任何时候您都必须知道密钥的类型,因此您不能只传递CacheKey - 因为那样您只会知道结果是Future[key.Out] 类型 - 您必须将 CacheKey.Aux[O] 与 O 作为类型参数一起传递。视情况而定,这可能是非问题或交易破坏者。
顺便说一句,您应该在此处为 Future 使用别名参数 - Future 在您创建它的那一刻开始评估,因此使用您的语法 Scala 将开始评估您的 Future 然后它会检查缓存如果在同一个键下还有一些其他的未来。如果它更早地开始和结束,可能会有一些好处,但你可能仍然会遇到一个错误的逻辑,因为新的Future 将继续执行。