【问题标题】:How to avoid NullPointer exception in Enums如何避免枚举中的 NullPointer 异常
【发布时间】:2021-11-10 22:49:57
【问题描述】:

我正在编写一个枚举并确保该枚举不会抛出空指针异常,因为它对于使用该枚举的客户端来说看起来很糟糕。我正在使用 Google 枚举,如果输入参数 mood is null 仍然会抛出 NPE

import com.google.common.base.Enums;
public enum KnowYourMoodEnum {
    ANGRY, SAD, HAPPY, FEELING_GOOD;
    
    private static final Set<KnowYourMoodEnum> HAPPY_MOOD_LIST = Sets.newHashSet(HAPPY, FEELING_GOOD);
    private static final Set<KnowYourMoodEnum> UPSET_MOOD_LIST = Sets.newHashSet(ANGRY, SAD);
    
    public static boolean isHappyMood(String mood) {
        return HAPPY_MOOD_LIST.stream()
                .anyMatch(x -> x.equals(Enums.getIfPresent(KnowYourMoodEnum.class, mood).orNull()));
    }
    
    public static boolean isUpsetMood(String mood) {
        return UPSET_MOOD_LIST.stream()
                .anyMatch(x -> x.equals(Enums.getIfPresent(KnowYourMoodEnum.class, mood).orNull()));
    }
    
}

【问题讨论】:

  • "我正在编写一个枚举并确保该枚举不会引发 nullpointer 异常,因为对于使用此枚举的客户端来说它看起来很糟糕" - 不一定。如果null 是无效输入,则抛出Exception 是完全可以的。但是,Exception 应该会有所帮助。一个可能的候选人是IllegalArgumentException

标签: java java-8 enums


【解决方案1】:

(1) API 指定“不要给我null 否则你会得到 NPE”是完全常规的。这是有道理的。

(2) 如果您不想这样做,那么if (mood == null) return false;

(3) 这是一个复杂而间接的实现。似乎在枚举上有一个属性boolean happy 会更简单。

【讨论】:

    【解决方案2】:

    使用Enums.getIfPresent 似乎是多余的,将输入mood 与枚举的name() 进行比较(可能忽略大小写)就足够了:

    public static boolean isHappyMood(String mood) {
        return isInSet(mood, HAPPY_MOOD_LIST);
    }
    
    public static boolean isUpsetMood(String mood) {
        return isInSet(mood, UPSET_MOOD_LIST);
    }
        
    private static boolean isInSet(String mood, Set<KnowYourMoodEnum> set) {
        return set.stream().anyMatch(x -> x.name().equalsIgnoreCase(mood));
    }
    

    这是 null 安全的,因为 name() 永远不会为 null。

    【讨论】:

      【解决方案3】:

      并不是每个操作都可以通过 Streams 变得更好。代替 set.stream().anyMatch(x -&gt; x.equals(…))只需使用简单高效的set.contains(…)即可。它更简单,而不是执行线性搜索,它将进行有效的查找。并不是说它对于这么小的集合很重要,但仍然值得注意的是,Stream API 不仅在这里没有任何好处,而且还会降低性能。

      如果您希望该方法能够容忍与实际枚举常量不匹配的输入,那么首先初始化名称的 Set 会简单得多。

      public enum KnowYourMoodEnum {
          ANGRY, SAD, HAPPY, FEELING_GOOD;
          
          private static final Set<String> HAPPY_MOOD = Stream.of(HAPPY, FEELING_GOOD)
              .map(Enum::name).collect(Collectors.toSet());
          private static final Set<String> UPSET_MOOD = Stream.of(ANGRY, SAD)
              .map(Enum::name).collect(Collectors.toSet());
      
          public static boolean isHappyMood(String mood) {
              return HAPPY_MOOD.contains(mood);
          }
      
          public static boolean isUpsetMood(String mood) {
              return UPSET_MOOD.contains(mood);
          }
      }
      

      对于null 或未知名称,这些方法已经评估为false

      如果您希望检查不区分大小写,可以使用

      public enum KnowYourMoodEnum {
          ANGRY, SAD, HAPPY, FEELING_GOOD;
          
          private static final Set<String> HAPPY_MOOD = Stream.of(HAPPY, FEELING_GOOD)
              .map(Enum::name).collect(Collectors.toCollection(
                  () -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)));
          private static final Set<String> UPSET_MOOD = Stream.of(ANGRY, SAD)
              .map(Enum::name).collect(Collectors.toCollection(
                  () -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)));
      
          public static boolean isHappyMood(String mood) {
              return mood != null && HAPPY_MOOD.contains(mood);
          }
      
          public static boolean isUpsetMood(String mood) {
              return mood != null && UPSET_MOOD.contains(mood);
          }
      }
      

      这将有 O(log n) 查找而不是 O(1),但这仍然比 O(n) 流操作更好,并且仍然与如此小的集合无关。此外,通过明确的null 检查,这些方法稍微复杂一些,但仍然比不惜一切代价引入 Stream API 的任何尝试都简单得多。

      而且你不需要 3rd 方库来完成如此简单的任务……

      顺便说一句,当变量包含Set 时,不要将它们命名为…_LIST

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-17
        • 2019-10-29
        • 1970-01-01
        • 1970-01-01
        • 2017-02-10
        • 1970-01-01
        • 2015-10-26
        • 1970-01-01
        相关资源
        最近更新 更多