【问题标题】:Java Map w @NonNull Value Type and @Nullable get() method result?Java Map w @NonNull 值类型和@Nullable get() 方法结果?
【发布时间】:2014-10-11 20:22:00
【问题描述】:

我有一个返回自定义 Map 实现的方法,其条目完全由非空键和值组成,所以我想添加类型注释以指示客户端可以迭代 Map.Entry 的而不需要检查它们用于空值: Map

问题在于 Map.get 方法 API 指定任何尝试检索 Map 中不存在的键的值的尝试都返回 null,并注释我的 get 方法实现以返回 @Nullable 字符串生成编译器警告返回类型与 Map 指定的 @NonNull 返回不兼容。

我知道今天创建的 Map.get API 可能会返回 java.util.Optional 结果或抛出 NoSuchElementException,但是,得益于现有的 Collections API,是否有可能保持与 get 方法规范和还指定我的 Map 仅包含 @NonNull 值?

非常感谢。

【问题讨论】:

  • 这不是很清楚。禁止空值与从不尝试查找不在映射中的键是一个单独的问题。你在说哪个?
  • 我想指定 Map 中不允许使用空值。但是,如果我这样做,我显然也在暗示 get 方法不会返回 null,事实并非如此。

标签: java null annotations nullable


【解决方案1】:

不幸的是,地图 API 与空注释并不真正兼容。 Map.get 返回泛型 V,即使您将 V 定义为 @NonNull,这违反了 API,因为 null 必须是允许的返回值。

这是一个空注释的known limitation,可能只有在实现nullity profiles for libraries 时才能解决。在那之前,唯一的解决方法是在获取值之前检查Map.containsKey,而不是在之后检查值是否为空,或者只是避免在映射值类型上使用@NonNull。

【讨论】:

  • 是的,我已经订阅了 nullity profile 错误,因为我很清楚 null 注释(即使有 Java 8 带来的类型注释)还没有准备好迎接黄金时段,但没有它们,因为,如果没有这些配置文件,我的代码中充斥着来自核心 Java API 调用的警告,以至于分析基本上毫无用处。鉴于此,我默认禁用了 null 分析,但我仍在尝试使用注释编写代码,因为我发现它有助于我设计更能意识到正确 null 处理的 API。
  • 鉴于我正在编写所有注释并假设它们最终会实现这些配置文件,并且我的 Map 确实不应该包含任何空值,也许我会去提前并将值注释为 NonNull,并查看 get 方法未被识别为 Nullable 的事实,这是我默认启用此内容之前需要修复的另一个分析错误/限制。
  • 是的,我也遇到了很多麻烦,尤其是现在使用流(尝试过滤掉 null!)。如果不出意外,您绝对必须忽略“从非注释类型到@NonNull 类型的未经检查的转换”。到目前为止,我已经设法解决了其他警告/错误,并借助一些实用方法将@Nullable T 转换为@NonNull T,类似于 Optional 的“orElse”方法。
  • 这不是空注释的限制。这只是 Eclipse 实现它们的一个限制。其他工具可以精确地处理地图,不会发出误报警告,也不需要通过对 Map.containsKey 的无关调用来混乱代码。
【解决方案2】:

您似乎指的是 Eclipse 的无效性检查实现。你是对的,它并不能准确地推断对Map.get 的调用。

如果对Map.get 的调用进行更精确的推理对您很重要,您可能需要考虑使用不同的无效性检查器。例如,在 Checker Framework 上构建的 Nullness Checker 处理您的案例。如果the key is in the map 并且映射的所有元素都是非空的,它会将Map.get 的返回值视为非空。

【讨论】:

    【解决方案3】:

    这是一个简单的库,可以用作解决方法:

    package xxxx;
    
    import java.util.*;
    
    import org.eclipse.jdt.annotation.*;
    
    public class NullUtil
    {
        @SuppressWarnings ("null")
        public static <@NonNull K, @NonNull T> @NonNull Optional<@Nullable T> get (@NonNull Map<@NonNull K, @NonNull T> m, @NonNull K key)
        {
            return Optional.ofNullable(m.get(key));
        }
    
        @SuppressWarnings ("null")
        public static <@NonNull K, @NonNull T> @NonNull T getOrThrow (@NonNull Map<@NonNull K, @NonNull T> m, @NonNull K key)
        {
            return Optional.ofNullable(m.get(key)).orElseThrow(
                () -> new RuntimeException("Map does not contain element '" + key + "'"));
        }
    
        @SuppressWarnings ("null")
        public static <@NonNull K, @NonNull T> @Nullable T getOrNull (@NonNull     Map<@NonNull K, @NonNull T> m, @NonNull K key)
        {
            return Optional.ofNullable(m.get(key)).orElse(null);
        }
    }
    

    以下是一些示例用法:

    package xxxx;
    
    import static org.junit.Assert.*;
    
    import java.util.*;
    
    import org.eclipse.jdt.annotation.NonNull;
    import org.junit.*;
    
    public class NullUtilTest
    {
        @Before
        public void setup ()
        {
            _m.put("a", "va");
            _m.put("b", "vb");
            _m.put("c", "vc");
        }
    
        @Test
        @SuppressWarnings ("null")
        public void testOptional ()
        {
            assertTrue(NullUtil.get(_m, "a").isPresent());
            assertFalse(NullUtil.get(_m, "x").isPresent());
    
            assertEquals("va", NullUtil.get(_m, "a").orElse(null));
            assertEquals(null, NullUtil.get(_m, "x").orElse(null));
    
        }
    
        @Test
        public void testOrNull ()
        {
            assertEquals("va", NullUtil.getOrNull(_m, "a"));
            assertEquals(null, NullUtil.getOrNull(_m, "x"));
        }
    
        @Test
        public void testOrThrow ()
        {
            assertEquals("va", NullUtil.getOrThrow(_m, "a"));
    
            try
            {
                assertEquals("vx", NullUtil.getOrThrow(_m, "x"));
                fail();
            }
            catch (Exception e)
            {
                assertEquals("Map does not contain element 'x'", e.getMessage());
            }
        }
    
        private final @NonNull Map<@NonNull String, @NonNull String> _m = new HashMap<>();
    }
    

    【讨论】:

      【解决方案4】:
      ObjectMapper mapper = new ObjectMapper(); 
      mapper.setSerializationInclusion(Include.NON_NULL);
      

      当你有这个空值或键的错误时:

      c.f.j.c.JsonGenerationException:  
        Null key for a Map not allowed in JSON (use a converting NullKeySerializer?)     
                  at c.f.j.d.s.i.FailingSerializer.serialize(FailingSerializer.java:36)
      

      你解决了像这样的覆盖序列化方法:

      class MySerializer extends StdSerializer<Object> {     
              public MyDtoNullKeySerializer() {
                   this(null);}     
                   public MySerializer(Class<Object> t) { 
                      super(t);     
                  }     
            @Override   
            public void serialize(Object nullKey, JsonGenerator jsonGenerator,SerializerProvider unused) 
                throws IOException, JsonProcessingException { 
                  jsonGenerator.writeFieldName("");    
              } 
      }
      

      例如完整的见这里: https://lentux-informatica.com/jackson-problema-con-maps-e-valori-null/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-06-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多