com.google.errorprone.annotations.CompatibleWith的文档中:

声明方法的参数必须与以下类型的类型参数之一“兼容”:
方法的封闭类,或方法本身。 “与...兼容”表示可以存在一个
“参考转换”从一种类型转换为另一种类型(JLS 5.5.1)。

例如,对Collection.contains(java.lang.Object)进行如下注释:

interface Collection<E> {
    boolean contains(@CompatibleWith("E") Object o);
}

为了指示必须向Collection.contains(java.lang.Object)的调用传递一个其类型与Collection实例的泛型类型参数兼容的参数:

这是com.google.common.cache.Cache的用法:
public interface Cache<K, V> {

    V getIfPresent(@CompatibleWith("K") Object key);

    V get(K key, Callable<? extends V> loader) throws ExecutionException;
...

使用@CompatibleWith("E") Object代替E作为参数类型有什么好处?为什么他们在Cachet的@CompatibleWith中使用getIfPresent批注,却未在Cachet的get方法中使用?

最佳答案

对于getIfPresent操作,允许“太宽”类型的对象是安全的(通过getIfPresent(42)的字符串键,您不会从缓存中得到任何东西)。另一方面,对于允许插入的假想get(Object, Callable),错误类型的对象(例如42而不是字符串"foo")会损坏基础集合,这就是为什么编译时检查不允许这样做。

话虽如此,这段代码:

Cache<String, Foo> cache = CacheBuilder.newBuilder()
// and later
Foo value = cache.getIfPresent(42);

这很可能是错误的,对于像“容易出错”这样的框架,将其表示为可能的错误是有意义的。

this old, but still relevant blog post "Why does Set.contains() take an Object, not an E?"解释了有关“在安全操作中使用对象不是通用类型”约定(不仅在Guava中使用,而且在JDK集合框架中也没有使用)约定的更详细说明,您在其中阅读:

为什么要像下面的代码那样编译?
Set<Long> set = new HashSet<Long>();
set.add(10L);
if (set.contains(10)) {
  // we won't get here!
}

我们要问集合中是否包含整数十;这是“显而易见的”
错误,但编译器无法捕获它,因为Set.contains()接受
Object。这不是愚蠢和邪恶吗?

然后回答标题中的问题:

真正的区别是add()在使用错误的类型调用时可能导致对集合的“损坏”,而contains()remove()不会。

结论也很重要:

静态分析在构建无错误的软件中扮演着极其重要的角色。

这是有道理的,因为作者Kevin Bourrillion也是Guava的首席开发人员。

10-05 22:47
查看更多