【问题标题】:What are the differences between a Java enum and a class with private constructor? [duplicate]Java枚举和具有私有构造函数的类之间有什么区别? [复制]
【发布时间】:2013-03-31 20:33:26
【问题描述】:

我试图了解 Java 枚举的真正工作原理,我得出的结论是,它与将构造函数声明为私有的普通 Java 类非常相似。

我刚刚得出这个结论,并没有经过太多思考,但我想知道我是否错过了什么。

所以下面是一个简单的 Java 枚举和一个等效的 Java 类的实现。

public enum Direction {
    ENUM_UP(0, -1),
    ENUM_DOWN(0, 1),
    ENUM_RIGHT(1, 0),
    ENUM_LEFT(-1, 0);


    private int x;
    private int y;

    private Direction(int x, int y){
        this.x = x;
        this.y = y;
    }
    public int getEnumX(){
        return x;
    }
    public int getEnumY(){
        return y;
    }
}

上面和下面的代码在含义上有什么区别?

public class Direction{
    public static final Direction UP = new Direction(0, -1) ;
    public static final Direction DOWN = new Direction(0, 1) ;
    public static final Direction LEFT = new Direction(-1, 0) ;
    public static final Direction RIGHT = new Direction(1, 0) ;


    private int x ;
    private int y ;

    private Direction(int x, int y){
        this.x = x ;
        this.y = y ;
    }
    public int getX(){
        return x;
    }
    public int getY(){
        return y;
    }
}

【问题讨论】:

  • 好问题,它们在很多方面确实相似 :) 也有很多不同之处,我相信这里会有很好的答案。
  • 我能想到的至少三个区别:[1] 你将无法切换后一个实现(即Direction d; switch(d)),[2] Direction.toString() 实现将是不同,[3] enum 方法允许您通过.values() 获取“类”的所有实例,而类方法则不能。
  • 你将失去values()valueOf()ordinal()
  • Enum 是一种增强的构建语言。表达一些东西,它更短更清晰。您可以以 Scala 为例:Scala 在语言中构建了许多您必须用纯 Java 中的大量样板代码来表达的东西。以单身人士为例。 Java 在这方面非常(极端)保守。
  • 投票重新开放。如果有的话,other question 应该作为它的副本关闭,因为它更清晰并且有更好的答案。

标签: java enums enumeration


【解决方案1】:

区别:

  1. 枚举扩展java.lang.Enum 并获得其所有nice features
    1. 通过正确的序列化实现自动单例行为
    2. 自动人类可读的 .toString 枚举值方法,无需复制您的枚举名称
    3. .name.ordinal 专用方法
    4. 可用于基于高性能位集的 EnumSetEnumMap
  2. 枚举由语言特殊处理:
    1. 枚举使用一种特殊的语法来简化实例创建,而无需编写数十个 public static final 字段
    2. 枚举可用于switch 语句
    3. 枚举不能在枚举列表之外实例化,除非使用反射
    4. 枚举不能扩展到枚举列表之外
  3. Java 自动将额外的东西编译到枚举中:
    1. public static (Enum)[] values();
    2. public static (Enum) valueOf(java.lang.String);
    3. private static final (Enum)[] $VALUES;values() 返回这个的克隆)

其中大部分都可以通过适当设计的类来模拟,但Enum 只是让创建具有这组特别理想属性的类变得非常容易。

【讨论】:

  • 单例是如何工作的?对于每个值,它都有一个单例?如果您创建一个包含 setter 和 getter 的枚举怎么办?在单个枚举变量上使用 setter 会影响具有相同值的其他变量吗?
  • 每个 Enum 值都是一个单例,所以是的,在一个上使用 setter 会影响引用该值的所有变量。因此,强烈建议您在枚举中永远不要有可变状态。
  • 我明白了。似乎合乎逻辑。谢谢。
【解决方案2】:

回答这个问题:本质上,这两种方法之间没有区别。但是,枚举构造为您提供了一些额外的支持方法,如 values()valueOf() 等,您必须使用带有私有构造函数的类方法自己编写。

但是,是的,我喜欢 Java 枚举与 Java 中的任何其他类一样,它们可以有字段、行为等。但对我来说,枚举与普通类的区别在于枚举是类/类型,其实例/成员是预先确定的。与可以从中创建任意数量实例的通常类不同,枚举仅将创建限制为已知实例。是的,正如您所说明的,您也可以使用具有私有构造函数的类来执行此操作,但枚举只是让这更直观。

【讨论】:

    【解决方案3】:

    看看this blogpage,它描述了Java enums 是如何编译成字节码的。您将看到与您的第二个代码示例相比有一个小的添加,它是一个名为VALUESDirection 对象数组。该数组包含枚举的所有可能值,因此您将无法这样做

    new Direction(2, 2)
    

    (例如使用反射),然后将其用作有效的Direction 值。

    另外,正如@Eng.Fouad 正确解释的那样,您没有values()valueOf()ordinal()

    【讨论】:

    • 我知道你可以使用反射hackDirection 类的私有构造函数并创建它的新实例,但我不确定你是否可以这样做带有枚举。
    • 根据它们被编译为字节码的方式,你也许可以。我从来没有尝试过自己,虽然有趣的实验......
    【解决方案4】:

    正如人们指出的那样,你输了values()valueOf()ordinal()。您可以使用MapList 的组合相当轻松地复制此行为。

    public class Direction {
    
        public static final Direction UP = build("UP", 0, -1);
        public static final Direction DOWN = build("DOWN", 0, 1);
        public static final Direction LEFT = build("LEFT", -1, 0);
        public static final Direction RIGHT = build("RIGHT", 1, 0);
        private static final Map<String, Direction> VALUES_MAP = new LinkedHashMap<>();
        private static final List<Direction> VALUES_LIST = new ArrayList<>();
        private final int x;
        private final int y;
        private final String name;
    
        public Direction(int x, int y, String name) {
            this.x = x;
            this.y = y;
            this.name = name;
        }
    
        private static Direction build(final String name, final int x, final int y) {
            final Direction direction = new Direction(x, y, name);
            VALUES_MAP.put(name, direction);
            VALUES_LIST.add(direction);
            return direction;
        }
    
        public int getX() {
            return x;
        }
    
        public int getY() {
            return y;
        }
    
        public static Direction[] values() {
            return VALUES_LIST.toArray(new Direction[VALUES_LIST.size()]);
        }
    
        public static Direction valueOf(final String direction) {
            if (direction == null) {
                throw new NullPointerException();
            }
            final Direction dir = VALUES_MAP.get(direction);
            if (dir == null) {
                throw new IllegalArgumentException();
            }
            return dir;
        }
    
        public int ordinal() {
            return VALUES_LIST.indexOf(this);
        }
    
        @Override
        public int hashCode() {
            int hash = 7;
            hash = 29 * hash + name.hashCode();
            return hash;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final Direction other = (Direction) obj;
            return name.equals(other.name);
        }
    
        @Override
        public String toString() {
            return name;
        }
    }
    

    如您所见;代码很快就会变得非常笨重。

    我不确定是否可以使用此类复制switch 语句;所以你会失去它。

    【讨论】:

      【解决方案5】:

      主要区别在于每个enum 类隐式扩展Enum&lt;E extends Enum&lt;E&gt;&gt; 类。这导致:

      1. enum 对象具有 name()ordinal() 等方法
      2. enum 对象具有特殊的 toString()hashCode()equals()compareTo() 实现
      3. enum 对象适用于 switch 运算符。

      以上所有内容不适用于您的Direction 类版本。这就是“意义”的区别。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-04-01
        • 2016-07-17
        • 1970-01-01
        • 2019-10-06
        • 1970-01-01
        • 2016-11-26
        • 2011-10-30
        相关资源
        最近更新 更多