侧边栏壁纸
博主头像
liveJQ博主等级

沒有乐趣,何来开始

  • 累计撰写 163 篇文章
  • 累计创建 66 个标签
  • 累计收到 2 条评论

Java中的Enum关键字

liveJQ
2019-06-30 / 0 评论 / 0 点赞 / 674 阅读 / 7,385 字

一般常量的定义方式

来自 Effective Java Edition 3


// The int enum pattern - severely deficient!
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;

缺点

1、类型不安全:当传递给需要 ORANGE_xx 的方法时不会检查

2、缺乏表现力:int 直接输出显示为数字,没有意义。

3、编译时常量:常量被编译到客户端时,若常量发生改变,则客户端会报错

4、硬编码易出错:String 类型常量若书写错误,编译时逃脱检验,运行时报错

5、功能有限:没有有效的方法迭代常量组,也没有办法获得常量组的大小

语法定义

从 JDK1.5 开始,枚举类解决了上述的所有缺点。创建枚举类型要使用 enum 关键字,隐含了所创建的类型都是 java.lang.Enum 类的子类(java.lang.Enum 是一个抽象类)。枚举类型符合通用模式 abstract Class Enum<E extends Enum>,而 E 表示枚举类型的名称。枚举类型的每一个值都将映射到 protected Enum(String name, int ordinal) 构造函数中(ordinal 从 0 开始)。在这里,每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。


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

这段代码实际上调用了7次 Enum(String name, int ordinal):
new Enum<EnumTest>("MON",0);
new Enum<EnumTest>("TUE",1);
new Enum<EnumTest>("WED",2);
    ... ...
遍历、switch 等常用操作

对enum进行遍历和switch的操作示例代码:
public class Test {
    public static void main(String[] args) {
        for (EnumTest e : EnumTest.values()) {
            System.out.println(e.toString());
        }
         
        System.out.println("----------------我是分隔线------------------");
         
        EnumTest test = EnumTest.TUE;
        switch (test) {
        case MON:
            System.out.println("今天是星期一");
            break;
        case TUE:
            System.out.println("今天是星期二");
            break;
        // ... ...
        default:
            System.out.println(test);
            break;
        }
    }
}

  • 输出结果:
MON
TUE
WED
THU
FRI
SAT
SUN
----------------我是分隔线------------------
今天是星期二

Enum 实例分析

由于枚举类都继承了 Enum 类,所以类中声明的枚举可以调用许多父类已有的方法

Enum.class

public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {

private final String name;
private final int ordinal;

public final String name() {
    return name;
}
public final int ordinal() {
    return ordinal;
}
protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}
public String toString() {
    return name;
}
public final boolean equals(Object other) {
    return this==other;
}
public final int hashCode() {
    return super.hashCode();
}
protected final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}
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;
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
    Class<?> clazz = getClass();
    Class<?> zuper = clazz.getSuperclass();
    return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}    
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                        String name) {
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
        "No enum constant " + enumType.getCanonicalName() + "." + name);
}
protected final void finalize() { }
private void readObject(ObjectInputStream in) throws IOException,
    ClassNotFoundException {
    throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
    throw new InvalidObjectException("can't deserialize enum");
}

常用方法简要说明

int compareTo(E o) 
比较此枚举与指定对象o的顺序。

Class<E> getDeclaringClass() 
返回与此枚举常量的枚举类型相对应的 Class 对象。

String name() 
返回此枚举常量的名称,在其枚举声明中对其进行声明。

int ordinal() 
返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。

String toString()
返回枚举常量的名称,它包含在声明中。

static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) 
返回带指定名称的指定枚举类型的枚举常量。

boolean equals(Object other)
比较两个枚举常量是否相等

测试 Demo

EnumTest

public enum EnumTest {

	MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6) {
        @Override
        public boolean isRest() {
            return true;
        }
    },
    SUN(0) {
        @Override
        public boolean isRest() {
            return true;
        }
    };

	private final int value;

	private EnumTest(int value) {
	    this.value = value;
	}

	public int getValue() {
	    return value;
	}
	
	public boolean isRest() {
	    return false;
	}
	
}

TestDemo1

public class TestDemo1 {
    public static void main(String[] args) {
        EnumTest test = EnumTest.TUE;
         
        // compareTo(E o),self.ordinal - other.ordinal
        switch (test.compareTo(EnumTest.MON)) {
        case -1:
            System.out.println("TUE 在 MON 之前");
            break;
        case 1:
            System.out.println("TUE 在 MON 之后");
            break;
        default:
            System.out.println("TUE 与 MON 在同一位置");
            break;
        }
         
        // getDeclaringClass()
        System.out.println("getDeclaringClass(): " + test.getDeclaringClass().getName());
         
        // name() 和  toString()
        System.out.println("name(): " + test.name());
        System.out.println("toString(): " + test.toString());
         
        // ordinal(), 返回值是从 0 开始
        System.out.println("ordinal(): " + test.ordinal());
        
        // 将常量名称转换为常量本身
        System.out.println("valueOf:" + Enum.valueOf(EnumTest.class, "TUE").getValue());
        
        // equals(Object o)
        System.out.println(test.equals(EnumTest.TUE));
    }
}


  • 输出结果:
TUE 在 MON 之后
getDeclaringClass(): com.livejq.enums.EnumTest
name(): TUE
toString(): TUE
ordinal(): 1
valueOf:2
true

TestDemo2


public class TestDemo2 {
    public static void main(String[] args) {
        // EnumSet的使用
        EnumSet<EnumTest> weekSet = EnumSet.allOf(EnumTest.class);
        for (EnumTest day : weekSet) {
            System.out.println(day);
        }
 
        // EnumMap的使用
        EnumMap<EnumTest, String> weekMap = new EnumMap(EnumTest.class);
        weekMap.put(EnumTest.MON, "星期一");
        weekMap.put(EnumTest.TUE, "星期二");
        // ... ...
        for (Iterator<Entry<EnumTest, String>> iter = weekMap.entrySet().iterator(); iter.hasNext();) {
            Entry<EnumTest, String> entry = iter.next();
            System.out.println(entry.getKey().name() + ":" + entry.getValue());
        }
    }
}

原理分析

反汇编

根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。

EnumTest 使用反汇编命令 javap EnumTest.class 得到如下:


public class EnumTest extends java.lang.Enum<EnumTest> {
  public static final EnumTest MON;
  public static final EnumTest TUE;
  public static final EnumTest WED;
  public static final EnumTest THU;
  public static final EnumTest FRI;
  public static final EnumTest SAT;
  public static final EnumTest SUN;
  public static EnumTest[] values();
  public static EnumTest valueOf(java.lang.String);
  public int getValue();
  public boolean isRest();
  EnumTest(java.lang.String, int, int, EnumTest$1);
  static {};
}

所以,实际上 enum 就是一个 class,只不过 java 编译器帮我们做了语法的解析和编译而已。

反编译

使用 jad 反编译工具可以查看详细的编译后代码,
下载后放到想反编译的类下,运行:

jad -s java EnumTest.java

具体命令看README.txt,-s为suffix,定义后缀名(默认生成.jad文件)

enum 的语法结构尽管和 class 的语法不一样,但是经过编译器编译之后产生的是一个 class 文件。该 class 文件经过反编译可以看到实际上是生成了一个类,该类继承了java.lang.Enum。


public class EnumTest extends Enum
{

    public static EnumTest[] values()
    {
        return (EnumTest[])$VALUES.clone();
    }

    public static EnumTest valueOf(String s)
    {
        return (EnumTest)Enum.valueOf(EnumTest, s);
    }

    private EnumTest(String s, int i, int j)
    {
        super(s, i);
        value = j;
    }

    public int getValue()
    {
        return value;
    }

    public boolean isRest()
    {
        return false;
    }


    public static final EnumTest MON;
    public static final EnumTest TUE;
    public static final EnumTest WED;
    public static final EnumTest THU;
    public static final EnumTest FRI;
    public static final EnumTest SAT;
    public static final EnumTest SUN;
    private int value;
    private static final EnumTest $VALUES[];

    static 
    {
        MON = new EnumTest("MON", 0, 1);
        TUE = new EnumTest("TUE", 1, 2);
        WED = new EnumTest("WED", 2, 3);
        THU = new EnumTest("THU", 3, 4);
        FRI = new EnumTest("FRI", 4, 5);
        SAT = new EnumTest("SAT", 5, 6) {

            public boolean isRest()
            {
                return true;
            }

        }
;
        SUN = new EnumTest("SUN", 6, 0) {

            public boolean isRest()
            {
                return true;
            }

        }
;
        $VALUES = (new EnumTest[] {
            MON, TUE, WED, THU, FRI, SAT, SUN
        });
    }
}
  1. 由于其中的枚举常量声明为 final 类,并且构造方法为 private ,所以客户端不能够实例化或继承。
  2. 由于父类实现的序列化接口中实现了 readObject 方法为不可用,所以枚举常量不能够被反序列化。
  3. 枚举类提供了编译时的安全。若声明一个枚举类,则其中枚举类型必定相同,尝试传递错误类型的值将导致编译时错误。
  4. 可以在枚举类型中添加或重新排序常量,而无需重新编译其客户端,因为导出常量的属性在枚举类型与其客户端之间提供了一层隔离。
  5. 常量值不会编译到客户端,因为它们位于 int 枚举模式中,可以通过调用其 toString 方法将枚举转换为可打
    印的字符串。

相关文章:

  1. 浅谈在Java开发中的枚举的作用和用法
  2. 反编译工具分析枚举的原理
  3. java enum(枚举)使用详解
0

评论区