枚举类深层次介绍

本文深入介绍了Java中的枚举类型(enum),枚举类与其他类的区别在于其继承自`java.lang.Enum`,并且枚举实例是唯一的。枚举在switch语句中尤其适用,同时可以通过添加字段和方法增强枚举的功能。枚举的顺序通过ordinal()获取,但不推荐依赖于它。此外,枚举可以实现安全的单例模式。文章还展示了如何通过反射访问枚举的构造函数,并强调了反射无法破坏枚举的安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

枚举类介绍

enum类型

通过enum定义的枚举类,和其他的class有什么区别?

答案是没有任何区别。enum定义的类型就是class,只不过它有以下几个特点:

  • 定义的enum类型总是继承自java.lang.Enum,且无法被继承;
  • 只能定义出enum的实例,而无法通过new操作符创建enum的实例;
  • 定义的每个实例都是引用类型的唯一实例;
  • 可以将enum类型用于switch语句。

例如,我们定义的Color枚举类:

public enum Color {
    RED, GREEN, BLUE;
}

编译器编译出的class大概就像这样:

实则是表面是这样

public final class Color extends Enum { // 继承自Enum,标记为final class
    // 每个实例均为全局唯一:
    public static final Color RED = new Color();
    public static final Color GREEN = new Color();
    public static final Color BLUE = new Color();
    // private构造方法,确保外部无法调用new操作符:
    private Color() {}
}

所以,编译后的enum类和普通class并没有任何区别。但是我们自己无法按定义普通class那样来定义enum,必须使用enum关键字,这是Java语法规定的。

因为enum是一个class,每个枚举的值都是class实例,因此,这些实例有一些方法:

name()

返回常量名,例如:

String s = Weekday.SUN.name(); // "SUN"

ordinal()

返回定义的常量的顺序,从0开始计数,例如:

int n = Weekday.MON.ordinal(); // 1

改变枚举常量定义的顺序就会导致ordinal()返回值发生变化。例如:

public enum Weekday {
    SUN, MON, TUE, WED, THU, FRI, SAT;
}

public enum Weekday {
    MON, TUE, WED, THU, FRI, SAT, SUN;
}

ordinal就是不同的。如果在代码中编写了类似if(x.ordinal()==1)这样的语句,就要保证enum的枚举顺序不能变。新增的常量必须放在最后。

有些童鞋会想,Weekday的枚举常量如果要和int转换,使用ordinal()不是非常方便?比如这样写:

String task = Weekday.MON.ordinal() + "/ppt";
saveToFile(task);

但是,如果不小心修改了枚举的顺序,编译器是无法检查出这种逻辑错误的。要编写健壮的代码,就不要依靠ordinal()的返回值。因为enum本身是class,所以我们可以定义private的构造方法,并且,给每个枚举常量添加字段:

public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Work at home!");
        } else {
            System.out.println("Work at office!");
        }
    }
}

enum Weekday {
    MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0);

    public final int dayValue;

    private Weekday(int dayValue) {
        this.dayValue = dayValue;
    }
}

这样就无需担心顺序的变化,新增枚举常量时,也需要指定一个int值。

默认情况下,对枚举常量调用toString()会返回和name()一样的字符串。但是,toString()可以被覆写,而name()则不行。我们可以给Weekday添加toString()方法:

public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Today is " + day + ". Work at home!");
        } else {
            System.out.println("Today is " + day + ". Work at office!");
        }
    }
}

enum Weekday {
    MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");

    public final int dayValue;
    private final String chinese;

    private Weekday(int dayValue, String chinese) {
        this.dayValue = dayValue;
        this.chinese = chinese;
    }

    @Override
    public String toString() {
        return this.chinese;
    }
}

覆写toString()的目的是在输出时更有可读性。

switch

最后,枚举类可以应用在switch语句中。因为枚举类天生具有类型信息和有限个枚举常量,所以比intString类型更适合用在switch语句中

代码汇总演示

package com.concurrent.demo21enum;

/**
 * @author lane
 * @date 2021年05月30日 上午10:44
 */
public enum Result {

     SUCCESS(200,"成功"),
     ERROR(404,"失败");

    private int code;
    private String message;

 private  Result(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int code(){
     return this.code;
    }
    public String message(){
        return this.message;
    }
/*
    @Override
    public String toString() {
        return "enumDemo{" +
                "code=" + code +
                ", message='" + message + '\'' +
                '}';
    }*/
}

测试类

package com.concurrent.demo21enum;

/**
 * @author lane
 * @date 2021年05月30日 上午11:02
 */
public class EnumTest {

    public static void main(String[] args) {

        System.out.println("Result.SUCCESS.toString():"+Result.SUCCESS);
        System.out.println("Result.SUCCESS.name():"+Result.SUCCESS.name());
        System.out.println("Result.SUCCESS.ordinal():"+Result.SUCCESS.ordinal());
        System.out.println("~~~~~~~华丽的分割线~~~~~~~~自己定义的方法~~~~~");
        System.out.println("Result.SUCCESS.code():"+Result.SUCCESS.code());
        System.out.println("Result.SUCCESS.message():"+Result.SUCCESS.message());
        System.out.println("Result.ERROR:"+Result.ERROR);
    }
}

/*
Result.SUCCESS.toString():SUCCESS
Result.SUCCESS.name():SUCCESS
Result.SUCCESS.ordinal():0
~~~~~~~华丽的分割线~~~~~~~~自己定义的方法~~~~~
Result.SUCCESS.code():200
Result.SUCCESS.message():成功
Result.ERROR:ERROR
*/

接下来看下真正的枚举类

Week.java

/**
 * @author lane
 * @date 2021年05月30日 下午12:35
 */
public enum  Week {
    Monday;
}
//因为我的jad不能用,还是继续用这个吧
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}

Week.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.concurrent.demo21enum;

public enum Week {
    Monday,
    Tuesday;

    private Week() {
    }
}


javap -p Week.class

➜  demo21enum javap -p Week.class
Compiled from "Week.java"
public final class com.concurrent.demo21enum.Week extends java.lang.Enum<com.concurrent.demo21enum.Week> {
  public static final com.concurrent.demo21enum.Week Monday;
  public static final com.concurrent.demo21enum.Week Tuesday;
  private static final com.concurrent.demo21enum.Week[] $VALUES;
  public static com.concurrent.demo21enum.Week[] values();
  public static com.concurrent.demo21enum.Week valueOf(java.lang.String);
  private com.concurrent.demo21enum.Week();
  static {};
}

jad不能使用无法展示jad反编译的效果,还是使用原来的吧

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
package com.kuang.single;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/kuang/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
  
 
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}

实际最深层次的编译会自己维护一个类型为(String,int)的构造函数

private EnumSingle(String s, int i)
{
super(s, i);
}

不然为什么有编号呢,用反射一试便知

				Class<Week> weekClass = Week.class;
//无参构造
			  Constructor<Week> declaredConstructor = weekClass.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        declaredConstructor.newInstance();

错误信息如下java.lang.NoSuchMethodException

Exception in thread "main" java.lang.NoSuchMethodException: com.concurrent.demo21enum.Week.<init>()
	at java.base/java.lang.Class.getConstructor0(Class.java:3349)
	at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2553)
	at com.concurrent.demo21enum.EnumTest.main(EnumTest.java:26)

查看一下Enum这个类发现

 protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

使用我们知道的参数反射再试一下

  Constructor<Week> declaredConstructor = weekClass.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        declaredConstructor.newInstance();

错误信息如下java.lang.IllegalArgumentException: 这是官方介绍枚举使用反射抛出的标准异常

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:484)
	at com.concurrent.demo21enum.EnumTest.main(EnumTest.java:29)

反射无法破坏枚举类,枚举可以实现安全的单例效果参考设计模式之单例模式并发反射安全

image-20210531194105985

文章部分概念介绍参考枚举类介绍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值