【问题标题】:Workaround for using enum type as field for same enum in Java [closed]在Java中使用枚举类型作为相同枚举字段的解决方法[关闭]
【发布时间】:2022-01-18 05:10:39
【问题描述】:

以下情况的最佳解决方法是什么:

public enum AnimalType {
   CAT(new AnimalType[] {}),
   DOG(new AnimalType[] {CAT, RAT}),
   BAT(new AnimalType[] {DOG}),
   PIG(new AnimalType[] {BAT, DOG}),
   RAT(new AnimalType[] {DOG, PIG}),
   ANT(new AnimalType[] {RAT});

   private final AnimalType[] afraidTypes;
   
   AnimalType(AnimalType[] afraidTypes) {
      this.afraidTypes = afraidTypes;
   }
}

上面的代码不起作用,因为某些字段由于DOG <---> RAT之类的交叉引用而在定义之前尝试访问。

我需要枚举。将afraidTypes 添加为每个动物的数组的最佳方法是什么?

【问题讨论】:

  • 为什么是枚举?这似乎是寻找问题的解决方案——你是在尝试制作规则引擎吗?
  • 你可以使用这个方法stackoverflow.com/questions/5678309/…,不幸的是它不能保证在编译时你已经为所有枚举值提供了害怕类型。
  • 我应该已经阅读了所有答案——您可以在使用前向引用的枚举上声明一个方法。
  • 这能回答你的问题吗? Illegal Forward Reference and Enums

标签: java enums


【解决方案1】:

tl;博士

作为一种解决方法,使用 switch expression 返回硬编码集。

enum Species
{
    CAT, DOG, BAT, PIG, RAT, ANT;

    public Set < Species > afraidOf ( )
    {
        return switch ( this )
                {
                    case CAT -> Set.of();
                    case DOG -> Set.of( CAT , RAT );
                    case BAT -> Set.of( DOG );
                    case PIG -> Set.of( BAT , DOG );
                    case RAT -> Set.of( DOG , PIG );
                    case ANT -> Set.of( RAT );
                };
    }
}

用法:

System.out.println( Species.DOG.afraidOf() );

[猫,老鼠]

详情

这是 Java 的一个有趣的部分。

您似乎可以将枚举的元素传递给同一枚举的构造函数。比如这样:

package work.basil.animal;

import java.util.Set;

public class App3
{
    public static void main ( String[] args )
    {
        enum Species
        {
            CAT( Set.of() ),
            DOG( Set.of( CAT ) ),
            BAT( Set.of( DOG ) ),
            PIG( Set.of( BAT , DOG ) ),
            RAT( Set.of( DOG , PIG ) ),
            ANT( Set.of( RAT ) );

            Set < Species > afraidOf;

            Species ( Set < Species > afraidOf )
            {
                this.afraidOf = afraidOf;
            }
        }

        System.out.println( Species.DOG.afraidOf );
    }
}

非法前向引用错误

但是您不能进行“前向引用”。所以我们不能满足你让狗害怕老鼠的愿望,因为当狗的构造函数运行时,RAT 实例还没有被创建。

所以下面的代码失败了:

            CAT( Set.of() ),
            DOG( Set.of( CAT , RAT ) ),  // <--- Error: Illegal forward reference.
            BAT( Set.of( DOG ) ),
            PIG( Set.of( BAT , DOG ) ),
            RAT( Set.of( DOG , PIG ) ),
            ANT( Set.of( RAT ) );

解决方法:使用 switch 表达式进行硬编码

一种解决方法是添加一个方法afraidOf,它返回一个集合(或数组,等等)。使用switch expression 压缩代码。

package work.basil.animal;

import java.util.Set;

public class App3
{
    public static void main ( String[] args )
    {
        enum Species
        {
            CAT, DOG, BAT, PIG, RAT, ANT;

            public Set < Species > afraidOf ( )
            {
                return switch ( this )
                        {
                            case CAT -> Set.of();
                            case DOG -> Set.of( CAT , RAT );
                            case BAT -> Set.of( DOG );
                            case PIG -> Set.of( BAT , DOG );
                            case RAT -> Set.of( DOG , PIG );
                            case ANT -> Set.of( RAT );
                            default -> Set.of();  // Or throw an exception here, your choice.
                        };
            }
        }

        System.out.println( Species.DOG.afraidOf() );
    }
}

[猫,老鼠]

编译器检查所​​有情况

您的 IDE 可能会指出 default 在现代 Java 中是不必要的。

如果您向枚举添加一个新元素,例如COW,或者您从 switch 表达式中删除一个 case,编译器会警告您错误“'switch' 表达式不涵盖所有可能的输入值”。

所以下面的代码就足够了。我们可以放心,我们已经涵盖了所有案例。

                return switch ( this )
                        {
                            case CAT -> Set.of();
                            case DOG -> Set.of( CAT , RAT );
                            case BAT -> Set.of( DOG );
                            case PIG -> Set.of( BAT , DOG );
                            case RAT -> Set.of( DOG , PIG );
                            case ANT -> Set.of( RAT );
                        };

EnumSet

EnumSet 类是Set 的专门实现,针对枚举对象进行了优化。 EnumSet 非常高效,占用内存少,执行速度非常快。

您可以使用EnumSet.of 而不是Set.of 来优化我们的解决方案。但请注意,在 Java 的某些实现中,Set.of 方法可能足够聪明,可以自行进行优化并返回一个EnumSet 作为其Set 对象。

                return switch ( this )
                        {
                            case CAT -> EnumSet.noneOf( Species.class );
                            case DOG -> EnumSet.of( CAT , RAT );
                            case BAT -> EnumSet.of( DOG );
                            case PIG -> EnumSet.of( BAT , DOG );
                            case RAT -> EnumSet.of( DOG , PIG );
                            case ANT -> EnumSet.of( RAT );
                        };

EnumSet 是可修改的。所以如果你想要unmodifiable,坚持使用Set.of


我在这里使用Set,因为这是我自己的偏好。但是,如果您坚持,我希望您可以仅对数组使用相同的代码。

【讨论】:

  • 我会让 default 抛出一个异常,并有一个单元测试为枚举中的每个值执行afraidOf
  • @tgdavies 不需要;编译器检查所​​有被覆盖的情况。我刚刚用该信息修改了答案。我猜这种行为是 Java 最新版本中添加的“模式匹配”功能的一部分。
  • 确实如此!直到
  • 我在original Questionthis existing similar Answer 中添加了这里看到的开关表达式的现代语法。
【解决方案2】:

正如https://stackoverflow.com/a/20149764/11002 建议的那样:

public class App {

    public enum AnimalType {
        CAT {
            public AnimalType[] getAfraidOf() {
                return new AnimalType[] { DOG };
            }
        },
        DOG {
            public AnimalType[] getAfraidOf() {
                return new AnimalType[] { CAT };
            }
        };


        public abstract AnimalType[] getAfraidOf();
    }
}

虽然冗长,但可以保证当您向枚举添加新值时,您不会忘记提供属性。

【讨论】:

    猜你喜欢
    • 2021-12-22
    • 2013-09-23
    • 1970-01-01
    • 2019-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多