【问题标题】:What makes enum in java non instantiable?是什么使 java 中的枚举不可实例化?
【发布时间】:2016-04-19 05:56:53
【问题描述】:

我知道一个枚举

enum Year
{
   First, Second, Third, Fourth;
}

转换成

final class Year extends Enum<Year>
{
        public static final Year First = new Year();
        public static final Year Second = new Year();
        public static final Year Third = new Year();
        public static final Year Fourth = new Year();
}

当我尝试实例化枚举(不是类)时,出现编译时错误:

error: enum types may not be instantiated
        Year y = new Year();

据我所知,私有构造函数使类不可实例化。而且我认为编译器提供了一个私有构造函数。但是当看到我们可以使用默认修饰符为枚举定义构造函数但仍然无法创建枚举类型的对象时,我再次感到困惑。

enum Year
{
        First, Second, Third, Fourth;
        Year()
        {
        }
}

class Example
{
        public static void main(String[] args)
        {
                Year y = new Year();
        }
}

我的疑问是,如果不是关于构造函数,那么是什么让 Java 中的枚举不可实例化?

【问题讨论】:

  • “因为语言是这样说的。”真的,没有比这更好的答案了。您的enum 声明导致四个对象被预创建,并且类的每个对象都必须是这四个对象之一。所以没有理由实例化另一个。
  • 旁注:enum Year 被编译成final class Year extends Enum&lt;Year&gt;

标签: java constructor enums instantiation


【解决方案1】:

Java Language Specification中指定:

8.9. Enum Types

...

枚举类型除了由其枚举常量定义的实例外,没有其他实例。尝试显式实例化枚举类型是编译时错误(第 15.9.1 节)。

因此 编译器 确保满足此要求。由于编译器“知道”该类型是enum,因此它可以区分enum Yearfinal class Year

此外,枚举构造函数不允许访问修饰符:

8.9.2. Enum Body Declarations

...

如果枚举声明中的构造函数声明是公共的或受保护的,则会出现编译时错误。

...

在枚举声明中,没有访问修饰符的构造函数声明是私有的

因此,在实践中,enum 构造函数看起来像包作用域(没有访问修饰符),但它实际上是私有的。

最后,同样的部分也说明了

在没有构造函数声明的枚举声明中,默认构造函数被隐式声明。 默认构造函数是私有的,没有形参,也没有 throws 子句。

这使得enum 不可实例化,即使没有显式声明构造函数。

【讨论】:

  • 您的回答也忘记了重要的一点:这不仅仅是编译时的事情:编译器确实为枚举生成了一个私有构造函数,因此无法实例化。使用javap -c -p 可以轻松找到。
  • @JB Nizet:但不要高估private 修饰符。对于普通类,private 构造函数允许在同一个类甚至嵌套类中创建新实例,但对于enum 类型,编译器甚至禁止这样做。此外,反射Constructor 实例对enum 实例化尝试执行额外检查。但是,如果您深入研究反射,您会找到绕过该检查的方法(然后,private 修饰符也不是障碍)......
【解决方案2】:

Java 中的enum 在未定义且为私有的情况下具有默认构造函数。

默认访问修饰符在不同的作用域有不同的含义。例如,在类中,方法和字段的默认访问修饰符是 package private

在接口中,默认访问修饰符表示public。事实上,接口字段上不能有其他修饰符,因此它是隐式公开的。

在顶级类上,它是包私有的(其中只允许 2 个访问修饰符 public 和默认包私有)

所以你的问题的答案是这样,因为编译器决定了。编译器编写者必须遵守语言规范合同。

你的想法是对的,毕竟这是一堂普通的课。 Java 中的每个对象蓝图类型都是一个可以用java.lang.Class 表示的类。接口、枚举、抽象类、匿名类、方法本地类的这些限制仅由编译器验证。

如果你能以某种方式逃避编译器并为枚举或其他方式生成你自己的字节码,如果你可以修改生成的 enum 类的字节码,以便它的私有构造函数变为公共,那么你可以调用它是枚举私有范围之外的构造函数。你也可以尝试用反射来做同样的事情。事实上,通过手动生成字节码,Groovy、Jython、JRuby、Clojure 等 JVM 语言能够提供 Java 本身不具备的功能。他们正在绕过 java 编译器。

enums 中有构造函数的目的是能够在一次调用中设置常量字段。 enum 中的所有常量都是 enum 类的实例,因此它们也包含其中声明的字段。

enum Test
{
    T1(1), // equivalent to public static final Test T1 = new Test(1);
    T2(2); // equivalent to public static final Test T2 = new Test(2);

    int id;
    Test(int id)
    {
        this.id = id;
    }
}

最后下面是上面enum使用java -p Test.class的反编译代码的输出

final class Test extends java.lang.Enum<Test>
{
    public static final Test T1;
    public static final Test T2;
    int id;
    private static final Test[] $VALUES;
    public static Test[] values();
    public static Test valueOf(java.lang.String);
    private Test(int);
    static {};
}

它应该可以更好地理解类编译时会发生什么。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-07
    • 1970-01-01
    • 2015-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多