为什么日食说ToStringSerializer.instance
需要...<@NonNull...>
?
因为你这么说。具体来说,您说所有类型都被假定为固有@NonNull
的,除非它们被明确标记为@Nullable
。就是@NonNullByDefault
这样。这意味着:在任何出现的没有指示无效注释的类型上,都将被假定为具有@NonNull
注释。
问题是双重的。
- Nullity 是一个类型标签。这是一个单独的维度。泛型是不变的(这意味着:在需要 a
List<Number>
时不能使用 a List<Object>
;与仅在需要 Object 时可以使用的数字相反。这是因为这就是数学的结果:否则您可以将 a 分配List<Number>
给 a类型的变量List<Object>
,然后向它添加一些非数字,但这意味着您只是在数字列表中填充了一个非数字,哇哦。结合这两个事实,它会变得有点毛茸茸。让我们选择协方差,你可以使用泛型 (List<? extends Number>
是的子类型List<? extends Object>
。这是有效的,因为您不能向 a 添加任何内容List<? extends Object>
,而您可以向 a 添加任何内容List<Object>
。
这是一张小小的 ASCII 艺术图片。@NN
表示“非空”,@Nul
表示可以为空:
List<? extends @Nul Integer> ➞ List<? extends @Nul Number> ➞ List<? extends @Nul Object>
↑ ↑ ↑
List<? extends @NN Integer> ➞ List<? extends @NN Number> ➞ List<? extends @NN Object>
在此图像中,如果您有这 6 个项目之一,那么您可以将该项目作为参数传递给一个方法,该方法的参数可以用箭头“到达”。
如您所见,您无法将这张图片展开成一条线:它有两个维度。
如果你把泛型类型本身可以修改它们的 togs 的事实堆积起来,事情就会变得更加复杂。
例如,假设您有这种类型:
Map<@NonNull String, @NonNull String> map;
有道理,对吧?这是一个将字符串映射到字符串的映射。它不能包含null
密钥。任何键都映射到有保证的非空字符串。
Map 接口的定义是:public interface Map<K, V>
,其中 K 和 V 代表键值类型。这个,在这个例子中,V 必然是@NonNull String
。
我们看一下get
Map的方法:
public V get(K key);
注意:它实际上是“对象键”,但其原因与此解释无关,这更容易理解。
Soo.. get 返回一个@NN String
if 在 type 的表达式上调用Map<@NN String, @NN String>
,对吗?
错误的!
它可以返回 null,因为如果提供的键不在地图中,get 方法的实现就会这样做!
因此,get
方法定义需要使用类型标记修饰符进行标记。它需要说:“这个方法返回V,但修改为它@Nul
。如果它是未知的nullity状态,那么现在知道了:它肯定可以为null,调用代码应该检查。如果是@NN
,那是无关紧要的; 这个方法返回的类型应该是@Nul String
. 如果已经是@Nul
, 很好, 不需要修改.
这让我们对使用注释来跟踪无效性有了多个重要的认识:
如果一个库没有任何 nullity annotations,那么从假设 nullity annotations 的代码中使用它变得非常困难。即使使用泛型,您也无法摆脱这一点,因为诸如map.get
修改标签之类的方法,但 java 核心库没有无效注释。Eclipse 在最新版本中修复了这个问题,允许您添加一个带有“附加注释”的外部文件(您可以在 eclipse 中使用 quickfix 制作这些文件),以便您(或其他人)可以制作一个告诉 eclipse 的模板:“public V get(K key)
类型中的Vjava.util.Map
应该用@Nul
'注释。
完整的类型世界远比“一个类型可以为空或不可为空”复杂得多。就像泛型有 4 种风格一样:List
, List<Number>
, List<? extends Number>
, List<? super Number>
, 任何类型上的 nullity 类型标记也是如此。有遗留的、可为空的、非空的和任意的空值。如果您想编写泛型方法,则需要最后一个。出于同样的原因,泛型需要这 4 种模式。Java 生态系统中常用的基于注解的 nullity 系统,除了 checker framework之外,没有足够的 nullity。缺乏完整范围的无效状态意味着将存在无法使用注释正确键入的方法。
上述两个事实结合成一个简单的结论:基于注释的检查器发出的无效警告可能是错误的。
在这种特定情况下?这可能是一件简单的事情,Jackson 的 ToStringSerializer 根本没有 null 注释,所以 eclipse 假定所有东西都是可以为 null 的,因此ToStringSerializer.instance
' 的类型是@Nullable StdSerializer<@Nullable Object>
. 换句话说:该字段可能为空。如果不为null,则肯定是StdSerializer。但是,它可以序列化的可能是任何对象,甚至是 null。而您的变量类型(由于@ByDefault
效果)是“实际的 StdSerializer 而不是空引用。此外,所述序列化器可以序列化实际对象。甚至试图要求它序列化一个可能持有 null 的值都是编译器错误。
这两个概念在类型方面是不兼容的。修复可能是其中之一:
- 使你的变量类型
StdSerializer<@Nullable Object>
。
- 重新配置 eclipse 以静默地忽略任何源自“遗留”无效信息的 null 问题(如缺少无效信息),并仔细检查 eclipse 是否意识到它不知道杰克逊库的签名中存在的类型的无效性.
- 使用该外部注释系统正确地“外部注释”杰克逊。
- 寻找完成这项工作并将其发布在网络上的人。然后下载并使用它。
- 完全禁用无效检查器系统,或将其重新配置为仅适用于项目内的交互,而不适用于您正在使用的任何项目外部 API。