zoukankan      html  css  js  c++  java
  • 深入理解枚举类

    深入理解枚举

     最近刚学习完JVM相关知识,想到枚举既然这么异类,那就从字节码角度来分析一下它。有关枚举的讲解,很多博客已经很详细了,这里我们就从字节码的角度重新来认识一下它。
    
     枚举类是一种特殊的类,它可以有自己的成员变量,方法,可以实现一个或多个接口,也可也定义自己的构造器。
    

    1. 枚举类的继承结构:

    2. 枚举类和普通类的区别:

    (1)枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是继承Object类,因此枚举不能够显示继承其他父类(单继承局限性,但是可以实现接口)。其中“java.lang.Enum”实现了“Comparable”和“Serializable”接口。

    (2)使用enum定义,非抽象的枚举类默认会使用final修饰,因此枚举类不能够派生子类。

    (3)枚举类的构造器只能够使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰;如果强制指定访问控制符,则只能指定private修饰符。

    (4)枚举类的所有实例必须要在枚举类的第一行显示列出,否则这个枚举类永远都能产生实例。列出这些实时,系统会自动添加public static final修饰符,无需程序员显示添加。

    (5)枚举类默认提供了一个values方法,返回枚举实例数组,该方法可以很方便的遍历所有的枚举值。

    为了能够更好的说明,上面的这些不同之处,下面我们定义了一个枚举类,使用“javap -v -p ”来反编译它

    enum  Season {
       SPRING("春"),SUMMER("夏"),AUTUMN("秋"),WINTTER("冬");
       private String name;
    
       Season(String name) {
          this.name = name;
       }
    }
    

    反编译可以得到这些信息:

    $ javap -v -p Season.class
    Classfile /D:/Project/JvmDemo/target/classes/com/bigdata/java/Season.class
      Last modified 2020-3-21; size 1206 bytes
      MD5 checksum e78087beee7e634071bc6cd1e019c168
      Compiled from "Demo69.java"
    final class com.bigdata.java.Season extends java.lang.Enum<com.bigdata.java.Season>
      minor version: 0
      major version: 52
      flags: ACC_FINAL, ACC_SUPER, ACC_ENUM
    Constant pool:
      ...
    {
      //枚举类中定义的常量,在字节码层面上的反映     
      public static final com.bigdata.java.Season SPRING;
        descriptor: Lcom/bigdata/java/Season;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
    
      public static final com.bigdata.java.Season SUMMER;
        descriptor: Lcom/bigdata/java/Season;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
    
      public static final com.bigdata.java.Season AUTUMN;
        descriptor: Lcom/bigdata/java/Season;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
    
      public static final com.bigdata.java.Season WINTTER;
        descriptor: Lcom/bigdata/java/Season;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
    
      private java.lang.String name;
        descriptor: Ljava/lang/String;
        flags: ACC_PRIVATE
            
      //枚举数组,这个数组供values方法使用,用于返回枚对象数组
      private static final com.bigdata.java.Season[] $VALUES;
        descriptor: [Lcom/bigdata/java/Season;
        flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
      //自动生成了values方法,它所完成的功能就是$VALUES.clone
      public static com.bigdata.java.Season[] values();
        descriptor: ()[Lcom/bigdata/java/Season;
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             //从类中获取静态字段$VALUES,然后压入到操作数栈的栈顶
             0: getstatic     #1                  // Field $VALUES:[Lcom/bigdata/java/Season;
             //调用实例方法clone,即出栈$VALUES,然后执行$VALUES.clone,由于clone是native方法并且有返回值,所以返回值会被压入到操作数的栈顶,此时操作数的栈顶是枚举对象数组       
             3: invokevirtual #2                  // Method "[Lcom/bigdata/java/Season;".clone:()Ljava/lang/Object;
             //弹出栈顶的枚举对象数组,检查它的类型是否符合给定的类型,检查完毕后再次压入到操作数栈的栈顶     
             6: checkcast     #3                  // class "[Lcom/bigdata/java/Season;"
             //将操作数栈顶的值弹出,并返回到给调用处,这样返回的就是一个clone后的枚举对象数组          
             9: areturn
          LineNumberTable:
            line 9: 0
      //它所完成的功能就是根据传入的字符串,调用父类的valueOf方法返回对应的枚举常量
      public static com.bigdata.java.Season valueOf(java.lang.String);
        descriptor: (Ljava/lang/String;)Lcom/bigdata/java/Season;
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=1, args_size=1
             0: ldc           #4                  // class com/bigdata/java/Season
             2: aload_0
             3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
             6: checkcast     #4                  // class com/bigdata/java/Season
             9: areturn
          LineNumberTable:
            line 9: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      10     0  name   Ljava/lang/String;
      //枚举构造方法,接收String和int,String,第一个String是枚举常量字符串,第二个是枚举常量所定义的位置,第三个枚举中构造方法传入的值
      private com.bigdata.java.Season(java.lang.String);
        descriptor: (Ljava/lang/String;ILjava/lang/String;)V
        flags: ACC_PRIVATE
        Code:
          stack=3, locals=4, args_size=4
             //将本地变量表索引0上的引用类型元素压入到操作数的栈顶          
             0: aload_0
             //将本地变量表索引1上的引用类型元素压入到操作数的栈顶。尽管此时本地变量表对应索引上,元素为空,但运行时局部变量表被被填充为枚举常量字符串           
             1: aload_1
             //本地变量表索引2上的int型元素压入到操作数的栈顶,运行时本地变量表对应索引上会被填充为枚举常量定义的位置          
             2: iload_2
             //从操作数栈中依次弹出说压入的值,然后调用父类的构造方法,Enum."<init>":(Ljava/lang/String;I)V          
             3: invokespecial #6                  // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
             //将本地变量表索引0上的引用类型元素压入到操作数的栈顶          
             6: aload_0
             //将本地变量表索引3上的引用类型元素压入到操作数的栈顶         
             7: aload_3
             //为对象中的字段设置值,从操作数栈中依次弹出枚举常量和构造方法所传入的值,为name字段赋值,如:SEASON.SPRING.name=“春”          
             8: putfield      #7                  // Field name:Ljava/lang/String;
            11: return
          LineNumberTable:
            line 13: 0
            line 14: 6
            line 15: 11
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      12     0  this   Lcom/bigdata/java/Season;
                0      12     3  name   Ljava/lang/String;
        Signature: #42                          // (Ljava/lang/String;)V
      
      static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=5, locals=0, args_size=0
             0: new           #4                  // class com/bigdata/java/Season
             3: dup
             4: ldc           #8                  // String SPRING
             6: iconst_0
             7: ldc           #9                  // String 春
             9: invokespecial #10                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
            12: putstatic     #11                 // Field SPRING:Lcom/bigdata/java/Season;
            15: new           #4                  // class com/bigdata/java/Season
            18: dup
            19: ldc           #12                 // String SUMMER
            21: iconst_1
            22: ldc           #13                 // String 夏
            24: invokespecial #10                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
            27: putstatic     #14                 // Field SUMMER:Lcom/bigdata/java/Season;
            30: new           #4                  // class com/bigdata/java/Season
            33: dup
            34: ldc           #15                 // String AUTUMN
            36: iconst_2
            37: ldc           #16                 // String 秋
            39: invokespecial #10                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
            42: putstatic     #17                 // Field AUTUMN:Lcom/bigdata/java/Season;
            45: new           #4                  // class com/bigdata/java/Season
            48: dup
            49: ldc           #18                 // String WINTTER
            51: iconst_3
            52: ldc           #19                 // String 冬
            54: invokespecial #10                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;)V
            57: putstatic     #20                 // Field WINTTER:Lcom/bigdata/java/Season;
            60: iconst_4
            61: anewarray     #4                  // class com/bigdata/java/Season
            64: dup
            65: iconst_0
            66: getstatic     #11                 // Field SPRING:Lcom/bigdata/java/Season;
            69: aastore
            70: dup
            71: iconst_1
            72: getstatic     #14                 // Field SUMMER:Lcom/bigdata/java/Season;
            75: aastore
            76: dup
            77: iconst_2
            78: getstatic     #17                 // Field AUTUMN:Lcom/bigdata/java/Season;
            81: aastore
            82: dup
            83: iconst_3
            84: getstatic     #20                 // Field WINTTER:Lcom/bigdata/java/Season;
            87: aastore
            88: putstatic     #1                  // Field $VALUES:[Lcom/bigdata/java/Season;
            91: return
          LineNumberTable:
            line 10: 0
            line 9: 60
    }
    Signature: #45                          // Ljava/lang/Enum<Lcom/bigdata/java/Season;>;
    SourceFile: "Demo69.java"
    

    可以发现它在静态块中总共完成了两件事情:

    (1)实例化枚举类并赋值给枚举实例

    (2)创建引用类型的数组,并为数组赋值,这也是values方法能够得到枚举数组的原因。

    由于枚举类是继承自“java.lang.Enum”类的,所以它自动的就继承了该类的一些方法:

    引用自“疯狂JAVA讲义 李刚”

    3. 枚举类的成员变量,方法和构造器

    枚举类也是一种类,只是它是一种比较特殊的类,因此它一样可定义成员变量,方法和构造器。还是上面的一段代码

    public enum  Season {
        //枚举实例,必须要定义在第一行
       SPRING("春"),SUMMER("夏"),AUTUMN("秋"),WINTTER("冬");
        //定义了成员变量
       private String name;
       //定义了构造器,默认为private类型,无论是否显示修饰
       Season(String name) {
          this.name = name;
       }
    }
    

    实际上它底层自动完成了实例化工作,前面我们通过反编译也看到了这一点,它是在静态块中完成对象的实例化工作的。类似于这种:

    public static final  SPRING=new SPRING("春");
    public static final  SPRING=new SUMMER("夏");
    public static final  SPRING=new AUTUMN("秋");
    public static final  SPRING=new WINTTER("冬");
    

    4. 枚举类实现接口

     枚举虽然无法继承其他类,但是还是可以实现其他接口的,这是因为接口没有单继承的局限性。枚举类在实现接口上和普通类的实现接口是一样的,没有什么本质区别。
    

    定义接口:

    public interface GenderDesc {
        void info();
    }
    

    枚举类实现该接口:

    //实现接口,并且以内部类的形式
    public enum Gender implements GenderDesc
    {
        // public static final Gender MALE = new Gender("男");
        MALE("男") {
            public void info() {
                System.out.println("gender male information");
            }
        },
        FEMALE("女") {
    
            public void info() {
                System.out.println("gender female information");
            }
        };
        private String name;
        private Gender(String name) {
            this.name = name;
        }
    }
    public class enumTest {
    	public static void main(String[] args) {
    		// 通过valueof方法获取指定枚举类的值
    		Gender gf = Enum.valueOf(Gender.class, "FEMALE");
    		Gender gm = Enum.valueOf(Gender.class, "MALE");
    		System.out.println(gf + " is stand for " + gf.getName());
    		System.out.println(gm + " is stand for " + gm.getName());
    		gf.info();
    		gm.info();
    	}
    }
    
       Gender类在编译的时候,会生成三个类“Gender.class”,“Gender$1.class”和“Gender$2.class”,其中Gender$1和Gender$2都是表示匿名内部类。
    

    注1:关于上面所讲的非抽象的枚举类,使用“final”修饰,抽象的枚举类,使用abstract修饰,这点可以通过“javap -c Gender.class”看到:

    D:ProjectJUCDmoother	argetclassescomigdatajucenums>javap -c Gender.class
    Compiled from "Gender.java"
    //使用abstract修饰,因为尽管匿名内部实例MALE和FEMALE中实现了info方法,但是在Gender类中并没有实现info方法,所以它仍然会被认为是抽象的,除非显示的实现info方法
    public abstract class com.bigdata.juc.enums.Gender extends java.lang.Enum<com.bigdata.juc.enums.Gender> implements com.bigdata.juc.enums.GenderDesc {
      public static final com.bigdata.juc.enums.Gender MALE;
    
      public static final com.bigdata.juc.enums.Gender FEMALE;
    

    而“Gender$1.class”

    D:ProjectJUCDmoother	argetclassescomigdatajucenums>javap -p Gender$1.class
    Compiled from "Gender.java"
    //使用final修饰   
    final class com.bigdata.juc.enums.Gender$1 extends com.bigdata.juc.enums.Gender {
      com.bigdata.juc.enums.Gender$1(java.lang.String, int, java.lang.String);
      public void info();
    }
    

    注2:两个枚举值的方法表现出不同的行为,指的是它们在info的输出结果上不同,其他表现行为没有什么特殊的。

    5. 包含抽象方法的枚举类

    枚举类中定义抽象方法时,不能够使用abstract关键字将枚举定义为抽象类(因为系统会自动为它添加abstract关键字),但因为枚举类需要显示创建枚举值,所以定义每个枚举值时须为抽象方法提供实现,否则将出现编译异常。

    enum Operation {
        PLUS {
            public double eval(double x, double y) {
                return x + y;
            }
        },
        MINS {
            public double eval(double x, double y) {
                return x - y;
            }
        },
        TIMES {
            public double eval(double x, double y) {
                return x * y;
            }
        },
        DIVIDE {
            public double eval(double x, double y) {
                if (y == 0) {
                    return -1;
                }
                return x / y;
            }
        };
        //为枚举类定义抽象方法,具体由枚举值提供实现
        public abstract double eval(double x, double y);
    }
    public class OperationTest {
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println(Operation.PLUS.eval(1, 2));
            System.out.println(Operation.DIVIDE.eval(1, 0));
        }
    }
    

    反编译后的部分结果:

    D:ProjectJUCDmoother	argetclassescomigdatajucenums>javap -c Operation.class
    Compiled from "OperationTest.java"
    //类名为abstract修饰    
    abstract class com.bigdata.juc.enums.Operation extends java.lang.Enum<com.bigdata.juc.enums.Operation> {
      public static final com.bigdata.juc.enums.Operation PLUS;
    
      public static final com.bigdata.juc.enums.Operation MINS;
    
      public static final com.bigdata.juc.enums.Operation TIMES;
    
      public static final com.bigdata.juc.enums.Operation DIVIDE;
    
      public static com.bigdata.juc.enums.Operation[] values();
        Code:
           0: getstatic     #2                  // Field $VALUES:[Lcom/bigdata/juc/enums/Operation;
           3: invokevirtual #3                  // Method "[Lcom/bigdata/juc/enums/Operation;".clone:()Ljava/lang/Object;
           6: checkcast     #4                  // class "[Lcom/bigdata/juc/enums/Operation;"
           9: areturn
    
      public static com.bigdata.juc.enums.Operation valueOf(java.lang.String);
        Code:
           0: ldc           #5                  // class com/bigdata/juc/enums/Operation
           2: aload_0
           3: invokestatic  #6                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
           6: checkcast     #5                  // class com/bigdata/juc/enums/Operation
           9: areturn
    
      public abstract double eval(double, double);
    
      com.bigdata.juc.enums.Operation(java.lang.String, int, com.bigdata.juc.enums.Operation$1);
        Code:
           0: aload_0
           1: aload_1
           2: iload_2
           3: invokespecial #1                  // Method "<init>":(Ljava/lang/String;I)V
           6: return
    
      static {};
        ……
    

    6. 枚举的用途简介

    在单例中,使用枚举

    package com.bigdata.juc.singleton;
    /*
     * 枚举类型:表示该类型的对象是有限的几个
     * 我们可以限定为一个,就成了单例
     */
    enum EnumSingleton{
        //等价于 public static final INSTANCE=new EnumSingleton();
        INSTANCE
    }
    public class Singleton2 {
        public static void main(String[] args) {
            EnumSingleton s = EnumSingleton.INSTANCE;
            System.out.println(s);
        }
    }
    
    

    7. 关于枚举类中的values方法?

    枚举的values(),既不是来自于在Java.lang.Enum,也不是来自于Enum所实现的接口,它是在编译过程中自己产生的,在前面的反编译过程中,我们也看到了这点,而且我们也看到它底层实际上就是生成了一个引用类型的数组(clone得到的),然后values方法返回了这个枚举数组。关于它的使用可以参考枚举类enum的values()方法

    想要深入理解它的来源,可以参考该博客
    http://blog.sina.com.cn/s/blog_6fd0fd4b01014x8l.html

    8. 使用枚举实现单例设计

    public class SingletonObject7 {
        private SingletonObject7() {
    
        }
        //定义枚举内部类
        private enum Singleton {
            INSTANCE;
    
            private final SingletonObject7 instance;
    
            Singleton() {
                instance = new SingletonObject7();
            }
    
            public SingletonObject7 getInstance() {
                return instance;
            }
        }
    
        public static SingletonObject7 getInstance() {
            return Singleton.INSTANCE.getInstance();
        }
    
        public static void main(String[] args) {
            IntStream.rangeClosed(1, 100)
                    .forEach(i -> new Thread(String.valueOf(i)) {
                        @Override
                        public void run() {
                            System.out.println(SingletonObject7.getInstance());
                        }
                    }.start());
        }
    }
    

    9. 枚举为什么能够防止反射攻击

    观察如下实例:

    public enum SingletonClass {
        INSTANCE;
        private String name;
    
        public void test() {
            System.out.println("The Test!");
        }
    
        public void setName(String name) {
    
            this.name = name;
        }
    
        public String getName() {
    
            return name;
        }
    
        public static void main(String[] args) {
            System.out.println(SingletonClass.INSTANCE);
            try {
                Class<SingletonClass> aClass = SingletonClass.class;
                Constructor<SingletonClass> constructor = aClass.getDeclaredConstructor(null);
                constructor.setAccessible(true);
                constructor.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    运行结果:

    INSTANCE
    java.lang.NoSuchMethodException: com.bigdata.java.SingletonClass.<init>()
    	at java.lang.Class.getConstructor0(Class.java:3082)
    	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    	at com.bigdata.java.SingletonClass.main(SingletonClass.java:28)
    
    1. 为什么会报找不到“SingletonClass.<init>()”方法异常?
      为了解答这个问题,我们需要对该枚举类执行反编译,在反编译后能够看到这样一个构造方法“com.bigdata.java.SingletonClass(Ljava/lang/String;I)V”
     private com.bigdata.java.SingletonClass();
        descriptor: (Ljava/lang/String;I)V
        flags: ACC_PRIVATE
        Code:
          stack=3, locals=3, args_size=3
             0: aload_0
             1: aload_1
             2: iload_2
             3: invokespecial #6                  // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
             6: return
          LineNumberTable:
            line 7: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       7     0  this   Lcom/bigdata/java/SingletonClass;
        Signature: #39                          // ()V
    

    descriptor: (Ljava/lang/String;I)V,这说明SingletonClass构造方法的第一个参数为String类型,第二个参数为int类型,而它最终调用了父类中的构造方法:java/lang/Enum."<init>":(Ljava/lang/String;I)V

    也即:java.lang.Enum#Enum

     /**单独的构造方法。程序员无法调用此构造方法。该构造方法用于由响应枚举类型声明的编译器发出的代码。
         * Sole constructor.  Programmers cannot invoke this constructor.
         * It is for use by code emitted by the compiler in response to
         * enum type declarations.
         *
         * @param name - The name of this enum constant, which is the identifier
         *               used to declare it.
                         此枚举常量的名称,它是用来声明该常量的标识符。
    
         * @param ordinal - The ordinal of this enumeration constant (its position
         *         in the enum declaration, where the initial constant is assigned
         *         an ordinal of zero).
                          枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
         */
        protected Enum(String name, int ordinal) {
            this.name = name;
            this.ordinal = ordinal;
        }
    

    那么“com.bigdata.java.SingletonClass(Ljava/lang/String;I)V”何时被调用的呢?再看反编译后的static块:

    static {};
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=4, locals=0, args_size=0
              
             0: new           #4                  // class com/bigdata/java/SingletonClass
                 //创建SingletonClass实例并放置到操作数栈的栈顶
             3: dup
             //复制操作数栈顶的值,并将复制后的值压入到操作数栈的栈顶,此时栈中有两个SingletonClass类的对象
             4: ldc           #19                 // String INSTANCE
             //将“INSTANCE”从运行时常量池中取出,并放置到操作数栈的栈顶,此时栈中有三个元素
             6: iconst_0
             //将0压入到操作数栈的栈顶,此时栈中有四个元素
             7: invokespecial #20                 // Method "<init>":(Ljava/lang/String;I)V
             //调用该类的构造方法"<init>":(Ljava/lang/String;I)V,在调用构造方法的时候,依次从栈顶弹出0,INSTANCE,实例,然后调用该实例的构造方法,传入参数“INSTANCE”和“0”,此时操作数栈的栈顶只有一个元素
            10: putstatic     #11                 // Field INSTANCE:Lcom/bigdata/java/SingletonClass;
             //从操作数栈顶弹出该类的引用,然后赋值给类中的静态字段INSTANCE
            13: iconst_1
            //将1压入到操作数栈的栈顶,此时操作数栈中有1个元素
            14: anewarray     #4                  // class com/bigdata/java/SingletonClass
            //创建引用类型的数组,将栈顶元素弹出,作为创建的数组的长度,也即数组的长度为1,然后将数组的引用arrayref(为了叙述方便,使用arrayref代替)压入到操作数栈中
            17: dup
            //复制栈顶的值,并压入到操作数栈中,此时栈中有两个元素
            18: iconst_0
            //将0压入到操作数栈中,此时栈中有三个元素
            19: getstatic     #11                 // Field INSTANCE:Lcom/bigdata/java/SingletonClass;
            //获取静态字段INSTANCE的值,并压入到操作数栈中,此时栈中有四个元素
            22: aastore
            //从操作数栈读取一个reference类型数据存入到数组中,即依次弹出INSTANCE,0,arrayref,然后将INSTANCE放入到数组arrayref的0号位置。然后将arrayref压入到操作数栈的栈顶,此时栈中有两个元素,都是arrayref。
            23: putstatic     #1                  // Field $VALUES:[Lcom/bigdata/java/SingletonClass;
             //从操作数栈顶弹出该类的引用,然后赋值给类中的静态字段$VALUES,此时操作数栈中只有一个元素arrayref了
            26: return
             //方法返回void,此时操作数栈中的所有元素都会被丢弃   
          LineNumberTable:
            line 8: 0
            line 7: 13
    

    通过以上的分析,可以看到在static块中,完成了以下任务

    • 创建SingletonClass的实例
    • 为类中的静态字段INSTANCE赋值
    • 创建数组并赋值给静态字段$VALUES

    通过static块,能够看到在实例化类的时候,调用的是Method "<init>":(Ljava/lang/String;I)V 方法。再回到上面的问题中,由于没有生成无参的构造方法,只有这么一个构造方法,所以会给出“java.lang.NoSuchMethodException: com.bigdata.java.SingletonClass.()”异常信息。

    既然没有无参构造方法,那么我们能否通过调用Method "<init>":(Ljava/lang/String;I)V 构造方法来进行实例化呢?

    如果这样来修改main方法

        public static void main(String[] args) {
            System.out.println(SingletonClass.INSTANCE);
            try {
                Class<SingletonClass> aClass = SingletonClass.class;
                Constructor<SingletonClass> constructor = aClass.getDeclaredConstructor(java.lang.String.class,int.class);
                constructor.newInstance("instance2",1);
            } catch (Exception e) {
               e.printStackTrace();
            }
        }
    

    运行结果:

    INSTANCE
    java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    	at com.bigdata.java.SingletonClass.main(SingletonClass.java:30)
    

    为什么会报这个异常,查看newInstance源码即可:

    public T newInstance(Object ... initargs)
            throws InstantiationException, IllegalAccessException,
                   IllegalArgumentException, InvocationTargetException
    {
           ...
            if ((clazz.getModifiers() & Modifier.ENUM) != 0)
                throw new IllegalArgumentException("Cannot reflectively create enum objects");
           ...
     } 
    

    可以看到异常是上面代码抛出的,说明它不能够对于枚举类型进行反射创建枚举类的实例,也即API层面就限制住了不能通过反射实例化枚举实例。

    10. 建议使用“Enum.getDeclaringClass”,而不是“Object.getClass”来获取枚举类的Class实例

    枚举类实现了Comparable接口,实现了compareTo方法:

    public abstract class Enum<E extends Enum<E>>
            implements Comparable<E>, Serializable {
    
    /**将此枚举与指定的order对象进行比较。返回一个负整数、零或正整数,因为该对象小于、等于或大于指定的对象
         * Compares this enum with the specified object for order.  Returns a
         * negative integer, zero, or a positive integer as this object is less
         * than, equal to, or greater than the specified object.
         *枚举常数只能与同一枚举类型的其他枚举常数相比较。此方法实现的自然顺序与声明常量的顺序相同。
         * Enum constants are only comparable to other enum constants of the
         * same enum type.  The natural order implemented by this
         * method is the order in which the constants are declared.
         */
        public final int compareTo(E o) {
            Enum<?> other = (Enum<?>)o;
            Enum<E> self = this;
            if (self.getClass() != other.getClass() && // optimization
                self.getDeclaringClass() != other.getDeclaringClass())
                throw new ClassCastException();
            return self.ordinal - other.ordinal;
        }
    

    注意这个self.getClass() != other.getClass() && self.getDeclaringClass() != other.getDeclaringClass()

    在这里你一定会问,为什么还要进行getDeclaringClass()的比较,直接比较两个枚举常量的class对象类型是否相同不就行了吗?

    观察如下的实例:

    在使用枚举类的时候,建议用getDeclaringClass返回枚举类。但是为什么不用getClass呢?下面来看看代码:

    public enum FruitEnum{
        BANANA,APPLE;
    
        public static void main(String[] args) {
            System.out.println(BANANA.getDeclaringClass());
            System.out.println(BANANA.getClass());
        }
    }
    
    # 运行结果
        class FruitEnum
        class FruitEnum
    }
    

    有人说结果不是一样吗?不急,看下面这种情况。

    public enum FruitEnum{
        BANANA{
            String getName() {
                return "香蕉";
            }
        },APPLE{
            String getName() {
                return "苹果";
            }
        };
    
        abstract String getName();
    
        public static void main(String[] args) {
            System.out.println(BANANA.getDeclaringClass());
            System.out.println(BANANA.getClass());
        }
    }
    
    # 运行结果
    class FruitEnum
    class FruitEnum$1
    

    这种情况下就不同了。因为此时BANANA和APPLE相当于FruitEnum的内部类。下面来看看Enum的源码:

    public final Class<E> getDeclaringClass() {
        Class var1 = this.getClass();
        Class var2 = var1.getSuperclass(); // 获取上一级的父类
        return var2 == Enum.class?var1:var2;
    }
    

    当上一级的父类不是Enum,则返回上一级的class。因此对枚举类进行比较的时候,使用getDeclaringClass是万无一失的。
    ————————————————
    版权声明:本文为CSDN博主「DaJian35」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/DaJian35/article/details/79705193

    再来看getDeclaringClass方法:

       /**返回与该enum常量的enum类型对应的类对象。当且仅当e1.getingclass () == e2.getingclass()时,两个enum常量e1和e2具有相同的enum类型。(此方法返回的值可能与对象返回的值不同。使用特定于常量的类主体的枚举常量的getClass方法。)
         * Returns the Class object corresponding to this enum constant's
         * enum type.  Two enum constants e1 and  e2 are of the
         * same enum type if and only if
         *   e1.getDeclaringClass() == e2.getDeclaringClass().
         * (The value returned by this method may differ from the one returned
         * by the {@link Object#getClass} method for enum constants with
         * constant-specific class bodies.)
         *
         * @return the Class object corresponding to this enum constant's enum type 对应于enum常量的类对象枚举类型
         */
        @SuppressWarnings("unchecked")
        public final Class<E> getDeclaringClass() {
            Class<?> clazz = getClass();
            Class<?> zuper = clazz.getSuperclass();
            return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
        }
    

    所以当枚举常量是普通的常量时,“clazz.getSuperclass()”获得得到的是“Enum.class”,此时返回的是就是这个枚举常量对应的Class实例。但是若该枚举常量是以匿名内部类的形式出现,则“clazz.getSuperclass()”返回的就是该枚举常量对应的Class实例,则返回的是“clazz.getSuperclass()”的计算结果。

    参考链接:

    java设计模式之单例模式(枚举、静态内部类)

    深入理解Java枚举类型(enum)

  • 相关阅读:
    Java 中的JOption函数
    01背包与完全背包(对比)
    AC注定不平坦(大神回忆录)
    背包精讲之——01背包
    动规问题概述(待整理)
    背包九讲
    Tautology(递推)||(栈(stack))(待整理)
    深度优先和广度优先区别
    Linux下JDK、Tomcat的安装及配置
    同IP不同端口Session冲突问题
  • 原文地址:https://www.cnblogs.com/cosmos-wong/p/11919418.html
Copyright © 2011-2022 走看看