JDK源码学习笔记——Enum枚举使用及原理
一、为什么使用枚举
什么时候应该使用枚举呢?每当需要一组固定的常量的时候,如一周的天数、一年四季等。或者是在我们编译前就知道其包含的所有值的集合。
利用 public final static 完全可以实现的功能,为什么要使用枚举?
public class Season { public static final int SPRING = 1; public static final int SUMMER = 2; public static final int AUTUMN = 3; public static final int WINTER = 4; }
(1)安全性。这种模式不是类型安全的。比如说我们设计一个函数,要求传入春夏秋冬的某个值。但是使用int类型,我们无法保证传入的值为合法。如:传入5。
(2)可读性。我们需要方便得到枚举类型的字符串表达式。int常量打印出来,我们所见到的就是一组数字,没什么用;String常量可以打印出详细信息,但是字符串的比较操作性能较低。
二、枚举的几种使用方式
用法一:常量
public enum Color { RED, GREEN, BLANK, YELLOW }
用法二:switch
enum Signal { GREEN, YELLOW, RED } public class TrafficLight { Signal color = Signal.RED; public void change() { switch (color) { case RED: color = Signal.GREEN; break; case YELLOW: color = Signal.RED; break; case GREEN: color = Signal.YELLOW; break; } } }
用法三:向枚举中添加新方法
public enum Color { RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4); // 成员变量 private String name; private int index; // 构造方法 private Color(String name, int index) { this.name = name; this.index = index; } // 普通方法 public static String getName(int index) { for (Color c : Color.values()) { if (c.getIndex() == index) { return c.name; } } return null; } // get set 方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } }
用法四:覆盖枚举的方法
public enum Color { RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4); // 成员变量 private String name; private int index; // 构造方法 private Color(String name, int index) { this.name = name; this.index = index; } //覆盖方法 @Override public String toString() { return this.index+"_"+this.name; } }
用法五:实现接口
public interface Behaviour { void print(); String getInfo(); } public enum Color implements Behaviour{ RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4); // 成员变量 private String name; private int index; // 构造方法 private Color(String name, int index) { this.name = name; this.index = index; } //接口方法 @Override public String getInfo() { return this.name; } //接口方法 @Override public void print() { System.out.println(this.index+":"+this.name); } }
用法六:使用接口组织枚举
public interface Food { enum Coffee implements Food{ BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO } enum Dessert implements Food{ FRUIT, CAKE, GELATO } }
三、原理(代码易懂,不做详细介绍)
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY } // 反编译之后如下: // 反编译Day.class final class Day extends Enum { //编译器为我们添加的静态的values()方法 public static Day[] values() { return (Day[])$VALUES.clone(); } //编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法 public static Day valueOf(String s) { return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s); } //私有构造函数 private Day(String s, int i) { super(s, i); } //前面定义的7种枚举实例 public static final Day MONDAY; public static final Day TUESDAY; public static final Day WEDNESDAY; public static final Day THURSDAY; public static final Day FRIDAY; public static final Day SATURDAY; public static final Day SUNDAY; private static final Day $VALUES[]; static { //实例化枚举实例 MONDAY = new Day("MONDAY", 0); TUESDAY = new Day("TUESDAY", 1); WEDNESDAY = new Day("WEDNESDAY", 2); THURSDAY = new Day("THURSDAY", 3); FRIDAY = new Day("FRIDAY", 4); SATURDAY = new Day("SATURDAY", 5); SUNDAY = new Day("SUNDAY", 6); $VALUES = (new Day[] { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }); } }
四、枚举保证线程安全
由上面反编译代码可以看到,“ public static final Day MONDAY; ”,static类型的属性会在类被加载之后被初始化,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的。
JVM类加载机制中:
“ 并发:
虚拟机会保证一个类的类构造器<clinit>()在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的类构造器<clinit>(),其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。
特别需要注意的是,在这种情形下,其他线程虽然会被阻塞,但如果执行<clinit>()方法的那条线程退出后,其他线程在唤醒之后不会再次进入/执行<clinit>()方法,因为在同一个类加载器下,一个类型只会被初始化一次。 ”
参考资料 / 相关推荐:
技术在于学习 在于实践 在于总结 在于分享