public enum Season {
SPRING, SUMMER, AUTUMN, WINTER;
}
1.Season.values()方法和Season.ordinal()方法
- Season.values()方法会返回包括所有枚举变量的数据。
- Season s = Season.valueOf("SPRING");->SPRING
- Season.ordinal()方法正是可以获取其次序的方法
for (Season s: Season.values()){
System.out.println(s + ".ordinal() --> "+s.ordinal());
}
SPRING.ordinal() --> 0
SUMMER.ordinal() --> 1
AUTUMN.ordinal() --> 2
WINTER.ordinal() --> 3
2.Season.toString()方法和Season.name()方法
- Season.toString()方法会返回枚举定义枚举变量时的字符串。此方法同Season.name()方法是一样的。
- 实现过程也是一样
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
public String toString() {
return name;
}
public final String name() {
return name;
}
}
- 但它们之间唯一的区别是,toString()方法可以重写,但name()方法被final修饰了,不能重写。
3.Season.compareTo()方法
- 这个方法用于比较两个枚举变量的“大小”,实际上比较的是两个枚举变量之间的次序,并返回次序相减之后的结果。
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;
}
- compareTo()方法中会先判断是否属于同一个枚举的变量,然后再返回差值。
4.枚举与switch
- 枚举是JDK1.5才有的特性,同时switch也更新了。使用switch进行条件判断的时候,条件整数一般只能是整型,字符型,而枚举型确实也被switch所支持。
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
class SeasonSwitch{
public void judge(Season s){
switch (s){
case SPRING:
System.out.println("spring");
break;
case SUMMER:
System.out.println("summer");
break;
case AUTUMN:
System.out.println("autumn");
break;
case WINTER:
System.out.println("winter");
break;
}
}
5.枚举的高级使用
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
SPRING对应的ordinal值对应的就是0,SUMMER对应的就是1。
如果我们想将SPRING的值为1,那么就需要自己定义变量:
public enum Season {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int value;
private Season(int value){
this.value = value;
}
}
- 我们想对一个枚举变量做两个维度的描述
public enum Season {
SPRING(1, "spring"), SUMMER(2, "summer"), AUTUMN(3, "autumn"), WINTER(4, "winter");
private int value;
private String lab;
private Season(int value, String lab){
this.value = value;
this.lab = lab;
}
}
- 构造函数必须私有化,但也不是一定要写private,事实上枚举的构造函数默认并强制为private,写public是无法通过编译的。
- ordinal还是按照它的规则给每个枚举变量按次序赋值,自定义变量与默认的ordinal属性并不冲突。
6.枚举的原理
public enum Season {
SPRING() {
@Override
public Season getNextSeason() {
return SUMMER;
}
}, SUMMER() {
@Override
public Season getNextSeason() {
return AUTUMN;
}
}, AUTUMN() {
@Override
public Season getNextSeason() {
return WINTER;
}
}, WINTER() {
@Override
public Season getNextSeason() {
return SPRING;
}
};
public abstract Season getNextSeason();
}
- 通过javac编译生成5个.class文件
- 通过javap反编译Season.class文件
public abstract class com.jessica.test.Season extends java.lang.Enum<com.jessica.test.Season> {
public static final com.jessica.test.Season SPRING;
public static final com.jessica.test.Season SUMMER;
public static final com.jessica.test.Season AUTUMN;
public static final com.jessica.test.Season WINTER;
public static com.jessica.test.Season[] values();
public static com.jessica.test.Season valueOf(java.lang.String);
public abstract com.jessica.test.Season getNextSeason();
com.jessica.test.Season(java.lang.String, int, com.jessica.test.Season$1);
static {};
}
- 也就是说,这四个枚举常量分别使用了内部类来实现。
- 同时还添加了两个方法values()和valueOf(String s)。我们使用的是默认的无参构造函数,但现在的构造函数有两个参数。还生成了一个静态代码块。
- 通过javap -c -v对Season.class反编译,汇总
。。。。。。。。。。。。
- 静态代码块部分做的工作,就是分别设置生成的四个公共静态常量字段的值,同时编译器还生成一个静态字段$VALUES,保存的是枚举类型定义的所有枚举常量。相当于以下代码:
Season SPRING = new Season1();
Season SUMMER = new Season2();
Season AUTUMN = new Season3();
Season WINTER = new Season4();
Season[] $VALUES = new Season[4];
$VALUES[0] = SPRING;
$VALUES[1] = SUMMER;
$VALUES[2] = AUTUMN;
$VALUES[3] = WINTER;
- values()方法
values()方法是一个公共的静态方法,所以我们可以直接调用该方法,返回枚举的数组。而这个方法实现的是,将静态代码块中初始化的$VALUES字段的值克隆出来,并且强制转换成Season[]类型返回,就相当于以下代码:
public static Season[] values(){
return (Season[])$VALUES.clone();
}
- valueof()方法
- valueOf()也是一个公共的静态方法,所以可以直接调用这个方法并返回参数字符串表示的枚举变量,另外,这个方法的实现是调用Enum.valueOf()方法,并把类型强制转换为Season,它相当于如下的代码:
public static Season valueOf(String s){
return (Season)Enum.valueOf(Season.class, s);
}
- 内部类
- 通过Javap反编译
Compiled from "Season.java"
final class Season$1 extends Season {
Season$1(java.lang.String, int);
public Season getNextSeason();
}
- 为什么Season1的构造函数有两个入参呢?关于这个问题,我们还是得从Season的父类Enum说起。
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
public final String name() {
return name;
}
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
......
}
- 从Enum中我们可以看到,每个枚举都定义了两个属性,name和ordinal,name表示枚举变量的名称,而ordinal则是根据变量定义的顺序授予的整型值,从0开始。
7.枚举与单例
- 枚举类实现单例模式相当硬核,因为枚举类型是线程安全的,且只会装载一次。使用枚举类来实现单例模式,是所有的单例实现中唯一一种不会被破坏的单例模式实现。
public class SingletonObject {
private SingletonObject() {
}
private enum Singleton {
INSTANCE;
private final SingletonObject instance;
Singleton() {
instance = new SingletonObject();
}
private SingletonObject getInstance() {
return instance;
}
}
public static SingletonObject getInstance() {
return Singleton.INSTANCE.getInstance();
}
}
7.彩蛋-Javap命令行
javap <options> <classes>
其中classes就是你要反编译的class文件。
在命令行中直接输入javap或javap -help可以看到javap的options有如下选项:
-hep --hep -? 输出此用法消息
-version 版本信息,其实是当前javap所在jdk的版本信息,不是cass在哪个jdk下生成的。
-v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
- 输出行号和本地变量表
-pubic 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类 和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示静态最终常量
-casspath &t;path> 指定查找用户类文件的位置
-bootcasspath &t;path> 覆盖引导类文件的位置
一般常用的是-v -l -c三个选项。
javap -v classxx,不仅会输出行号、本地变量表信息、反编译汇编代码,还会输出当前类用到的常量池等信息。
javap -l 会输出行号和本地变量表信息。
javap -c 会对当前class字节码进行反编译生成汇编代码。
查看汇编代码时,需要知道里面的jvm指令,可以参考官方文档:
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html
另外通过jclasslib工具也可以看到上面这些信息,而且是可视化的,效果更好一些。