枚举类介绍
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
语句中。因为枚举类天生具有类型信息和有限个枚举常量,所以比int
、String
类型更适合用在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)
反射无法破坏枚举类,枚举可以实现安全的单例效果参考设计模式之单例模式并发反射安全
文章部分概念介绍参考枚举类介绍