Java枚举用于分层/树形结构

5
我需要创建一个结构来表示以下内容(对于类别和子类别)。这只是一层深度。我正在考虑使用Java Enums来实现它,但不确定如何表示这种分层结构。
我的Java对象(业务对象)代表了一个设备,它们将具有类别和子类别属性,我想使用枚举而不是使用整数代码(如100、1等)。有些设备只有类别而没有子类别(如以下示例中的300)。
100  Switch  
     1   Interior
     2   Exterior
200  Security Sensor     
     1   Door Sensor
     2   Leak Sensor
     3   Motion Sensor
300  Camera

任何帮助都是受欢迎的。 感谢。

看一下Java的EnumMap或者这个SO问题 - mdo
你找到了“正确”的答案吗?请勾选你认为最正确的答案。 - AncientSwordRage
6个回答

7
这篇java.dzone文章展示了分层枚举的一个很好的例子。
public enum OsType {
    OS(null),
        Windows(OS),
            WindowsNT(Windows),
                WindowsNTWorkstation(WindowsNT),
                WindowsNTServer(WindowsNT),
            Windows2000(Windows),
                Windows2000Server(Windows2000),
                Windows2000Workstation(Windows2000),
            WindowsXp(Windows),
            WindowsVista(Windows),
            Windows7(Windows),
            Windows95(Windows),
            Windows98(Windows),
        Unix(OS) {
                @Override
                public boolean supportsXWindows() {
                    return true;
                }
            },
            Linux(Unix),
            AIX(Unix),
            HpUx(Unix),
            SunOs(Unix),
    ;
    private OsType parent = null;

    private OsType(OsType parent) {
        this.parent = parent;
    }
}

本文展示了使用此设置可以实现的许多小技巧。

3

我认为这个答案对你来说是一个很好的解决方案。使用这样的类型层次结构:

public enum Component {
    Interior(Part.Switch),
    Exterior(Part.Switch),
    DoorSensor(Part.SecuritySensor),
    LeakSensor(Part.SecuritySensor),
    MotionSensor(Part.SecuritySensor),
    Camera(Part.Camera);

    private final Part kindOf;

    Component(Part kindOf) {
        this.kindOf = kindOf;
    }

    enum Part {
        Switch, SecuritySensor, Camera
    }

}

更多细节可以在《Effective Java第二版》的枚举章节中找到。


1
似乎用枚举来表达这个问题不太寻常,相比继承。枚举的问题在于它们本质上是编译时常量,如果你想在层次结构中生成更多信息,则必须添加更多枚举。
这可能会很快变得混乱。 这也略微违反了枚举的真正目的 - 作为预定义常量而不是分层实体。
我建议使用一个名为CategoryBase的抽象类来绘制所有内容。 然后,根据此创建您的继承树。
下面是一个图示:

Hierarchy diagram, showing CategoryBase on top, with children Camera, SubCategory, SecuritySensor, and Switch.

大部分的工作是持有属性,我们永远不希望这些属性在创建后被更改,因此我们可以让抽象类来持有它们。我们还将它们设为“final”,这样它们就无法被修改。
public abstract class CategoryBase {

    protected final int ranking;
    protected final String name;
    protected final SubCategory[] subCategories;

    protected CategoryBase(int ranking, String name, SubCategory... subCategories) {
        this.ranking = ranking;
        this.name = name;
        this.subCategories = subCategories;
    }

    public int getRanking() {
        return ranking;
    }

    public String getName() {
        return name;
    }

    public SubCategory[] getSubCategories() {
        return subCategories;
    }
}

从这里开始,我们可以基于此构建我们的标记类 - 包括SubCategory,因为它实际上只是以不同的方式表示信息的持有者。
这也使得编写标记类变得简单明了。例如,这是Camera
public class Camera extends CategoryBase {

    protected Camera(int ranking, String name) {
        super(ranking, name);
    }
}

它与“SubCategory”非常相似-“SubCategory”没有任何嵌套的“SubCategory”,因此我们不会向构造函数的可变参数部分传递任何内容。
对于具有“SubCategory”的内容,我们需要在构建时实例化它们。这里以“SecuritySensor”为例。
public class SecuritySensor extends CategoryBase {

    public SecuritySensor(int ranking, String name) {
        super(ranking, name,
                new SubCategory(1, "Door Sensor"),
                new SubCategory(2, "Leak Sensor"),
                new SubCategory(3, "Motion Sensor"));
    }
}

这种方法还可以在排名方面提供一定的灵活性——如果您想要能够在运行时指定子类别的确切排名,您可以用支持可变参数签名的构造函数替换此构造函数。

0

你可以像下面的代码一样做。但那可能不是你想要的。你可以像这里那样将子枚举添加到父枚举的构造函数中。

enum Stuff {
    Swich,Sensor,Camera;
    enum swich {
        interior,exterior;
        enum Where {
            bathroom,kitchen
        }
    }
    enum sensor {
        door,leak,motion
    }
}

0
也许重新考虑,使用整数。然而,不要基于十进制系统(100、200、300...表示第一级;1、2、3...表示第二级),而是基于二进制表示法。
// top-level
public static final int SWITCH          = 1 << 16;
public static final int SECURITY_SENSOR = 2 << 16;
public static final int CAMERA          = 4 << 16;

// sub-types of 'switch'
public static final int INTERIOR = SWITCH | 1;
public static final int EXTERIOR = SWITCH | 2;

// sub-types of 'security sensor'
public static final int DOOR_SENSOR   = SECURITY_SENSOR | 1;
public static final int LEAK_SENSOR   = SECURITY_SENSOR | 2;
public static final int MOTION_SENSOR = SECURITY_SENSOR | 4;

这允许您进行一种弱化的继承测试:

if (value == SWITCH) {
   // value is a switch, but not interior or exterior
} else if (value & SWITCH != 0) {
   // value is interior or exterior
}

1
虽然这样做可以完成工作,但我强烈不建议在几乎所有情况下使用位运算。引用《Effective Java》中的第32条:“但是位字段具有int枚举常量的所有缺点,甚至更多。当它以数字形式打印时,解释位字段比简单的int枚举常量更困难。此外,没有一种简单的方法可以迭代由位字段表示的所有元素。” - mdo
仔细做出设计决策肯定是个好主意。但仅仅因为某些东西写在书里并不意味着你必须盲目遵循它。这真的取决于你的使用情况——如果你需要某个特定功能,比如快速继承测试,而枚举本身无法提供,则由你决定是否想要解决它,例如使用多个或嵌套的枚举,或者采用不同的方法。 - Thomas
1
我不建议这样做,因为书中有提到,我引用书中的内容是因为Bloch的陈述非常精确。在我看来,位运算类似于使用指针进行编程:有很多开发人员不理解这些概念,或者至少在这些主题上存在巨大问题。Joel Spolsky几年前写了一篇不错的文章。因此,如果我在团队中编写代码,我会避免使用位运算。SCJP考试取消了位运算符,从而传达了这样的含义:“不要使用它!” - mdo
我很感谢你的意见,但我不赞同它。 - Thomas

-1

以下代码是否符合您的要求?

public enum Category {Switch, ...}
public enum SubCategory {Interior, ...}

1
不,我想保持类别和子类别之间的关系。拥有2个单独的枚举将让您设置Category=100和Sub-category=3(Switch - Motion Sensor),这是错误的。谢谢。 - user2626222
@user2626222,这根本不应该发生,因为Motion Sensor是不会出现在Switch的值中的... - Obicere
然后您可以添加另一个枚举,如{enum Category cate; enum SubCategory subCate; SwitchInterior(Switch, Interior), SwitchExterior(Switch, Exterior)...},然后在您的对象类中使用此枚举。 - andy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接