Eclipse 空分析在泛型参数上有@NonNull

Eclipse null analysis has @NonNull on generic parameter

我在使用这段代码时遇到问题...

import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
...
StdSerializer<Object> stdSerializer;

stdSerializer = ToStringSerializer.instance;

注意:包在package-info.java.

中注解为@NonNullByDefault

上面代码的最后一行,ToStringSerializer.instance有如下错误...

Null type safety (type annotations): The expression of type 'ToStringSerializer' needs unchecked conversion to conform to 'StdSerializer<@NonNull Object>', corresponding supertype is 'StdSerializer'

ToStringSerializer.eea 文件有...

class com/fasterxml/jackson/databind/ser/std/ToStringSerializer

instance
 Lcom/fasterxml/jackson/databind/ser/std/ToStringSerializer;
 L1com/fasterxml/jackson/databind/ser/std/ToStringSerializer;
...

为什么 Eclipse 2021-03(4.19.0 build 20210312-0638)说 ToStringSerializer.instance 需要 ...<@NonNull...>? Eclipse 如何确定 Object 必须是 @NonNull?我该如何解决这个问题?

我在 Eclipse 2020-12 上发现了这个问题,它也发生在 Eclipse 2021-03 上。

why does eclipse say ToStringSerializer.instance needs to be ...<@NonNull...>?

因为你是这么说的。具体来说,您说所有类型都被假定为固有 @NonNull,除非 它们被明确标记为 @Nullable。这就是 @NonNullByDefault 所做的。这意味着:任何地方出现的类型都没有打上无效指示注释,将被假定为具有 @NonNull 注释。

问题是双重的。

  1. Nullity 是一个类型标签。它是一个单独的维度。泛型是不变的(这意味着:当需要 List<Object> 时不能使用 List<Number>;相反,当需要对象时可以使用数字。这是因为数学就是这样解决办法:否则你可以将 List<Number> 分配给类型 List<Object> 的变量,然后向其添加一些非数字,但这意味着你只是在数字列表中填充了一个非数字,whooooops 。结合这两个事实,它变得有点毛茸茸。让我们选择协变,你可以用泛型来做到这一点(List<? extends Number> List<? extends Object> 的子类型。这之所以有效,是因为您不能向 List<? extends Object> 添加任何内容,而可以向 List<Object>.
  2. 添加任何内容

这是一张 ASCII 艺术图片。 @NN表示'nonnull',@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 项中的一项,则可以将该项作为参数传递给方法,该方法的参数可以是带箭头的 'reached'。

如您所见,您无法将这张图片展开成一条线:它有两个维度。

如果你加入泛型类型本身可以修改它们的 togs 的事实,事情会变得更加复杂。

例如,假设您有这种类型:

Map<@NonNull String, @NonNull String> map;

有道理,对吧?它是一个将字符串映射到字符串的映射。它不能包含 null 键。任何键都映射到有保证的非空字符串。

Map接口的定义是:public interface Map<K, V>,其中K和V代表键值类型。在此示例中,V 绑定到 @NonNull String.

我们看一下Map的get方法:

public V get(K key);

注意:实际上是 'Object key',但其原因与此解释无关,这更容易理解。

Soo.. 如果在 Map<@NN String, @NN String> 类型的表达式上调用,得到 returns a @NN String,对吗?

错了!

可以 return null,因为如果提供的键不在映射中,get 方法的实现会这样做!

因此,get 方法定义需要使用 类型标记修饰符 进行标记。它需要说:“这个方法 returns V,但是被修改为 @Nul。如果它是未知的 nullity 状态,那么现在知道了:它肯定可以是 null,调用代码应该检查。如果它是 @NN,那是无关紧要的;通过这种方法编辑的类型 return 应该是 @Nul String。如果它已经是 @Nul,很好,不需要修改。

这让我们对使用注释来跟踪无效性有了多个重要认识:

  • 如果一个库没有任何空注释,那么从假定空注释的代码中使用它变得非常困难。即使使用泛型,您也无法摆脱这一点,因为像 map.get 这样的方法会修改标记,但 java 核心库没有无效注释。 Eclipse 在最新版本中通过允许您使用 'add-on annotations' 添加外部文件(您可以使用 quickfix 在 eclipse 中创建这些文件)来修复此问题,以便您(或其他人)可以制作一个告诉 eclipse 的模板: “类型 java.util.Mappublic V get(K key) 中的 V 应该用 @Nul' 注释。

  • 整个类型世界比 'a type can be nullable, or not nullable' 复杂得多。就像泛型有 4 种风格:ListList<Number>List<? extends Number>List<? super Number>,任何类型上的 nullity 类型标签也是如此。有遗留的、可为空的、非空的和任意的空值。如果您想编写泛型方法,则最后一个是必需的。出于同样的原因,泛型需要这 4 种模式。在 java 生态系统 除了 checker framework 之外,没有任何基于注解的无效系统具有足够的无效性。缺少全方位的无效状态意味着将存在 无法 使用注释正确键入的方法。

  • 以上两个事实结合成一个简单的结论:基于注释的检查器发出的无效警告可能是错误的

在这个具体案例中?这可能是一件简单的事情,Jackson 的 ToStringSerializer 根本没有空注释,所以 Eclipse 假定所有东西都可以为空,因此 ToStringSerializer.instance 的类型是 @Nullable StdSerializer<@Nullable Object>。换句话说:该字段可能为空。如果它不为 null,则它肯定是一个 StdSerializer。不过,它可以序列化的可能是任何对象,甚至是 null。而您的变量类型是(由于 @ByDefault 效果) 'actual StdSerializer and not a null reference. Furthermore, said serializer can serialize actual objects. It is a compiler error to even attempt to ask it to serialize a value that could potentially hold null'.

这两个概念在类型方面是不兼容的。修复可能是以下之一:

  1. 将您的变量类型设为 StdSerializer<@Nullable Object>
  2. 重新配置 eclipse 以静默忽略任何源自 'legacy' 无效信息的空问题(例如,缺少无效信息),并仔细检查 eclipse 是否意识到它不知道存在的类型是否为无效在杰克逊图书馆的签名中。
  3. 使用该外部注释系统正确 'externally annotate' jackson。
  4. 找到完成该工作并将其发布到网络上的人。然后下载并使用它。
  5. 完全禁用无效检查器系统,或将其重新配置为仅适用于项目内的交互,而不适用于您正在使用的任何项目外部 API。