Как избежать исключения NullPointer в перечислениях

#java #java-8 #enums

Вопрос:

Я пишу перечисление и слежу за тем, чтобы перечисление не создавало исключения nullpointer, так как это плохо выглядит для клиента, который использует это перечисление. Я использую перечисление Google, все равно оно выдает NPE, если входной параметр mood is null

 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()));
    }
    
}
 

Комментарии:

1. » Я пишу перечисление и слежу за тем, чтобы перечисление не создавало исключения nullpointer, так как это плохо выглядит для клиента, который использует это перечисление » — Не обязательно. Если null это неверный ввод, то совершенно нормально бросить Exception . Тем Exception не менее, это должно быть полезно. Возможным кандидатом был бы IllegalArgumentException .

Ответ №1:

(1) Для API-интерфейсов совершенно обычно указывать «не передавайте мне a 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:

Не каждая операция становится лучше с потоками. Вместо set.stream().anyMatch(x -> x.equals(…)) того, чтобы просто использовать простое и эффективное set.contains(…) . Это проще, и вместо выполнения линейного поиска он будет выполнять эффективный поиск. Не то чтобы это имело большое значение для таких небольших наборов, но все же стоит отметить, что потоковый API здесь не только не дает никаких преимуществ, но и снижает производительность.

Если вы хотите, чтобы метод был терпим к входным данным, не соответствующим фактической константе перечисления, в первую очередь гораздо проще инициализировать a 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);
    }
}
 

Эти методы уже оценивают false для null или неизвестных имен.

Если вы хотите, чтобы проверка не учитывала регистр, вы можете использовать

 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 amp;amp; HAPPY_MOOD.contains(mood);
    }

    public static boolean isUpsetMood(String mood) {
        return mood != null amp;amp; UPSET_MOOD.contains(mood);
    }
}
 

Это будет иметь поиск O(log n) вместо O(1), но это все равно лучше, чем операция потока O(n), и все равно не имеет значения для таких небольших наборов. Кроме того, методы немного сложнее с явной null проверкой, но все же намного проще, чем любая попытка любой ценой внедрить потоковый API.

И вам не нужна сторонняя библиотека для такой простой задачи…

И, кстати, не называйте переменные …_LIST , когда они содержат a Set .