是什么让java中的枚举不可实例化?

2022-09-03 12:42:59

我知道一个枚举

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中的枚举不可实例化?


答案 1

它在 Java 语言规范中指定:

8.9. 枚举类型

...

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

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

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

8.9.2. 枚举体声明

...

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

...

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

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

最后,同一部分还指出

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

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


答案 2

Java 中的 一个在未定义且为私有时具有默认构造函数。enum

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

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

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

所以你的问题的答案是,这是因为编译器决定如此。编译器编写者必须维护语言规范契约。

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

如果您可以以某种方式转义编译器并为枚举生成自己的字节代码,或者如果您可以修改生成的类的字节代码,使其私有构造函数成为公共,则可以在枚举的私有范围之外调用它的构造函数。您也可以尝试使用反射来执行相同的操作。事实上,通过手动生成字节码,JVM语言,如Groovy,Jython,JRuby,Clojure能够提供Java本身所没有的功能。他们正在绕过java编译器。enum

在 s 中使用构造函数的目的是能够在一次调用中设置常量字段。内的所有常量都是类的实例,因此它们也由其中声明的字段组成。enumenumenum

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

最后,波纹管是上面反编译代码的输出,通过使用enumjava -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 {};
}

它应该可以更好地理解类编译时发生的情况。