什么是枚举
我们平时定义常量,是这么定义的
public class Days {
public static final int MONDAY =1;
public static final int TUESDAY=2;
public static final int WEDNESDAY=3;
public static final int THURSDAY=4;
public static final int FRIDAY=5;
public static final int SATURDAY=6;
public static final int SUNDAY=7;
}
上述方式是int枚举模式,这种定义方式并没有错,只不过使用并不方便,可能定义重复的值,而且极有编译器也不会提出警告,枚举就是要代替上方的定义方式,例如:
public enum Days {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
我们在定义枚举时用的关键字是enum和class差不多,只不过前面是定义枚举,后面是定义类的,枚举类型定义了周一到周日的值,字母一般要大写,多个值之间用逗号分隔,枚举类型和class类型一样,可以定义为一个独立的文件,也可以定义在一个类中,枚举在类型安全性和便捷性都有保证,如果类型有问题编译器也会提示我们改正,那么枚举该如何使用呢?
public class EnumDemo {
public static void main(String[] args) {
// 直接引用
Days monday = Days.MONDAY;
}
public enum Days {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
}
上述代码就是枚举的基本使用
枚举常用的方法
返回类型 | 方法名称 | 方法说明 |
---|---|---|
int | compareTo(E o) | 比较此枚举与指定对象的顺序 |
boolean | equals(Object other) | 当指定对象等于此枚举常量时,返回 true。 |
Class<?> | getDeclaringClass() | 返回与此枚举常量的枚举类型相对应的 Class 对象 |
String | name() | 返回此枚举常量的名称,在其枚举声明中对其进行声明 |
int | ordinal() | 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零) |
String | toString() | 返回枚举常量的名称,它包含在声明中 |
static<T extends Enum> | T static valueOf(Class enumType, String name) | 返回带指定名称的指定枚举类型的枚举常量。 |
static T[] | T[] static valus() | 返回按照常量的声明顺序,产生由这些常量值组成的数组 |
ordinal()方法 ,该方法获取的是枚举变量在枚举类里声明的顺序,下标从0开始,如上方MONDAY是第一个声明的。那么MONDAY的ordinal值就是0,如果MONDAY的声明位置发生变化,那么ordinal值就会变,注意大多数情况下,不应该第一时间使用此方法,毕竟他是变换莫测的
compareTo(E o)方法,则比较枚举的大小,注意内部实现是根据ordinal方法的值进行比较的
name()方法和toString()几乎是等同的,都是输出枚举的字符串变量值
T static valueOf(Class enumType, String name)方法,则是根据枚举类的class对象和枚举名字,来获取枚举常量,该方法时静态的
public class EnumDemo {
public static void main(String[] args){
//创建枚举数组
Day[] days=new Day[]{Day.MONDAY, Day.TUESDAY, Day.WEDNESDAY,
Day.THURSDAY, Day.FRIDAY, Day.SATURDAY, Day.SUNDAY};
for (int i = 0; i <days.length ; i++) {
System.out.println("day["+i+"].ordinal():"+days[i].ordinal());
}
System.out.println("-------------------------------------");
//通过compareTo方法比较,实际上其内部是通过ordinal()值比较的
System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[1]));
System.out.println("days[0].compareTo(days[1]):"+days[0].compareTo(days[2]));
//获取该枚举对象的Class对象引用,当然也可以通过getClass方法
Class<?> clazz = days[0].getDeclaringClass();
System.out.println("clazz:"+clazz);
System.out.println("-------------------------------------");
//name()
System.out.println("days[0].name():"+days[0].name());
System.out.println("days[1].name():"+days[1].name());
System.out.println("days[2].name():"+days[2].name());
System.out.println("days[3].name():"+days[3].name());
System.out.println("-------------------------------------");
System.out.println("days[0].toString():"+days[0].toString());
System.out.println("days[1].toString():"+days[1].toString());
System.out.println("days[2].toString():"+days[2].toString());
System.out.println("days[3].toString():"+days[3].toString());
System.out.println("-------------------------------------");
Day d=Enum.valueOf(Day.class,days[0].name());
Day d2=Day.valueOf(Day.class,days[0].name());
System.out.println("d:"+d);
System.out.println("d2:"+d2);
}
/**
执行结果:
day[0].ordinal():0
day[1].ordinal():1
day[2].ordinal():2
day[3].ordinal():3
day[4].ordinal():4
day[5].ordinal():5
day[6].ordinal():6
-------------------------------------
days[0].compareTo(days[1]):-1
days[0].compareTo(days[1]):-2
clazz:class com.zejian.enumdemo.Day
-------------------------------------
days[0].name():MONDAY
days[1].name():TUESDAY
days[2].name():WEDNESDAY
days[3].name():THURSDAY
-------------------------------------
days[0].toString():MONDAY
days[1].toString():TUESDAY
days[2].toString():WEDNESDAY
days[3].toString():THURSDAY
-------------------------------------
d:MONDAY
d2:MONDAY
*/
}
enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
values()方法
Day[] days2 = Day.values();
System.out.println("day2:"+Arrays.toString(days2));
Day day = Day.valueOf("MONDAY");
System.out.println("day:"+day);
/**
输出结果:
day2:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
day:MONDAY
*/
现在Enum类的基本内容已经讲完了
枚举的实现原理
实际上利用关键字Enum定义后,编译器会为我们生成一个相关的类,这个类继承了Java API中的java.lang.Enum类,也就是说编译后也同样是一个class类型,继承自java.lang.Enum,我们编译EnumDemo.java 并查看生成的class来验证
//查看目录下的java文件
zejian@zejiandeMBP enumdemo$ ls
EnumDemo.java
//利用javac命令编译EnumDemo.java
zejian@zejiandeMBP enumdemo$ javac EnumDemo.java
//查看生成的class文件,注意有Day.class和EnumDemo.class 两个
zejian@zejiandeMBP enumdemo$ ls
Day.class EnumDemo.class EnumDemo.java
编译后生成Day.class 和 EnumDemo.class,Day.class 就是枚举类型,这也就验证前面所说的使用关键字enum定义枚举类型并编译后,编译器会自动帮助我们生成一个与枚举相关的类。我们再来看看反编译Day.class文件:
//反编译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
});
}
}
从反编译的代码我们可以看到,编译器为我们生成了一个Day类(注意是final的,不能够继承),而且该类继承自java.lang.Enum,该类是一个抽象类,除此之外,编译器还为我们生成了7个Day类型的实例对象,这也说明了我们用Enum定义的枚举,每种日期枚举常量都是实实在在的Day的实例对象,只不过表达的内容不一样,编译器还为我们添加了来个静态方法,values和valuesof ,到这里我们就可以明白,用Enum定义的枚举类型,在编译期后也可以转换成一个实实在在的类,而在该类中,每个枚举类型都存在相应的实例对象,如上述的MONDAY枚举类型对应public static final Day MONDAY;
Enum类中会有一个构造函数,但是只能编译器调用,我们看一下Enum类的源码
//实现了Comparable
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;
}
public String toString() {
return name;
}
public final boolean equals(Object other) {
return this==other;
}
//比较的是ordinal值
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;//根据ordinal值比较大小
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
//获取class对象引用,getClass()是Object的方法
Class<?> clazz = getClass();
//获取父类Class对象引用
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) {
//enumType.enumConstantDirectory()获取到的是一个map集合,key值就是name值,value则是枚举变量值
//enumConstantDirectory是class对象内部的方法,根据class对象获取一个map集合的值
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);
}
//.....省略其他没用的方法
}
values方法和valueof方法都是编译器给加入的方法,values方法Enum类并没有提供,但是Valuesof方法提供了,只不过有俩个参数,编译器产生valueof方法只有一个参数,刚才我们看源码也知道了最终还是调用了Enum类的valueof方法
values()方法的作用就是获取枚举类中的所有变量,并作为数组返回,而valueOf(String name)方法与Enum类中的valueOf方法的作用类似根据名称获取枚举变量,只不过编译器生成的valueOf方法更简洁些只需传递一个参数。这里我们还必须注意到,values方法是编译器插入到枚举类中的static方法,所以我们向上转型为Enum是不能调用values方法的
//正常使用
Day[] ds=Day.values();
//向上转型Enum
Enum e = Day.MONDAY;
//无法调用,没有此方法
//e.values();
枚举与Class对象
上面我们说到,枚举向上转型后values方法会失效,就无法一次获取所有的枚举变量,但是又Class对象的存在,即使不使用values对象也能获取所有的枚举变量
返回类型 | 方法名称 | 方法说明 |
---|---|---|
T[] | getEnumConstants() | 返回该枚举类型的所有元素,如果Class对象不是枚举类型,则返回null。 |
boolean | isEnum() | 当且仅当该类声明为源代码中的枚举时返回 true |
通过getEnumConstants方法可以轻而易举的获取所有枚举实例变量
//正常使用
Day[] ds=Day.values();
//向上转型Enum
Enum e = Day.MONDAY;
//无法调用,没有此方法
//e.values();
//获取class对象引用
Class<?> clasz = e.getDeclaringClass();
if(clasz.isEnum()) {
Day[] dsz = (Day[]) clasz.getEnumConstants();
System.out.println("dsz:"+Arrays.toString(dsz));
}
/**
输出结果:
dsz:[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
*/
枚举的进阶用法
上面的分析只是使用了枚举的简单定义,也就是定义枚举时,只定义了枚举类型,没有定义方法和成员变量,实际上用Enum定义的枚举类,除了不能用继承(因为编译器会为我们自动继承java.lang.Enum,因为只能单继承,所以只能不能继承),我们可以把枚举当做常规的类,也就是说可以定义方法,变量
向Enum中添加方法与自定义函构造函数
public enum Day2 {
MONDAY("星期一"), //这个就相当于调用自定义的构造函数
TUESDAY("星期二"),
WEDNESDAY("星期三"),
THURSDAY("星期四"),
FRIDAY("星期五"),
SATURDAY("星期六"),
SUNDAY("星期日");//记住要用分号结束
private String desc;//中文描述
/**
* 私有构造,防止被外部调用
* @param desc
*/
private Day2(String desc){
this.desc=desc;
}
/**
* 定义方法,返回描述,跟常规类的定义没区别
* @return
*/
public String getDesc(){
return desc;
}
public static void main(String[] args){
for (Day2 day:Day2.values()) {
System.out.println("name:"+day.name()+
",desc:"+day.getDesc());
}
}
/**
输出结果:
name:MONDAY,desc:星期一
name:TUESDAY,desc:星期二
name:WEDNESDAY,desc:星期三
name:THURSDAY,desc:星期四
name:FRIDAY,desc:星期五
name:SATURDAY,desc:星期六
name:SUNDAY,desc:星期日
*/
}
从上方我们可以知道,枚举类可以自定义方法和类,但是我们需要注意,如果在Enum中定义方法,请务必声明完枚举后,用分号隔开,如果在枚举前定义任何方法都会报错,同时即使自定义构造函数,我们也无法手动调用构造函数创造实例,这件事只能编译器做。
关于覆盖Enum父类方法
虽然Enum类和其他类没有什么区别,其实还是有些约束的,Enum父类只有toString没有加final,所以只能覆盖toString方法
Enum中定义抽象方法
与常规类一样Enum允许我们定义抽象方法,然后每个枚举类都实现该方法,以便产生不同的行为
public enum EnumDemo3 {
FIRST{
@Override
public String getInfo() {
return "FIRST TIME";
}
},
SECOND{
@Override
public String getInfo() {
return "SECOND TIME";
}
}
;
/**
* 定义抽象方法
* @return
*/
public abstract String getInfo();
//测试
public static void main(String[] args){
System.out.println("F:"+EnumDemo3.FIRST.getInfo());
System.out.println("S:"+EnumDemo3.SECOND.getInfo());
/**
输出结果:
F:FIRST TIME
S:SECOND TIME
*/
}
}
Enum与接口
Enum单继承的原因是因为单继承,但是这并不影响他实现接口
interface food{
void eat();
}
interface sport{
void run();
}
public enum EnumDemo2 implements food ,sport{
FOOD,
SPORT,
; //分号分隔
@Override
public void eat() {
System.out.println("eat.....");
}
@Override
public void run() {
System.out.println("run.....");
}
}
有的时候我们要对一组数据进行分类,比如食物进行分类,而且希望这些菜单属于food类型,appetizer(开胃菜)、mainCourse(主菜)、dessert(点心)、Coffee等,每种分类下有多种具体的菜式或食品,此时可以利用接口来组织
public interface Food {
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS;
}
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
}
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
}
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
LATTE, CAPPUCCINO, TEA, HERB_TEA;
}
}
public class TypeOfFood {
public static void main(String[] args) {
Food food = Appetizer.SALAD;
food = MainCourse.LASAGNE;
food = Dessert.GELATO;
food = Coffee.CAPPUCCINO;
}
}
我们可以利用嵌套枚举的方式,把前面定义的菜谱,存放到Meal菜单中,这样就可以统一存放菜单数据了
public enum Meal{
APPETIZER(Food.Appetizer.class),
MAINCOURSE(Food.MainCourse.class),
DESSERT(Food.Dessert.class),
COFFEE(Food.Coffee.class);
private Food[] values;
private Meal(Class<? extends Food> kind) {
//通过class对象获取枚举实例
values = kind.getEnumConstants();
}
public interface Food {
enum Appetizer implements Food {
SALAD, SOUP, SPRING_ROLLS;
}
enum MainCourse implements Food {
LASAGNE, BURRITO, PAD_THAI,
LENTILS, HUMMOUS, VINDALOO;
}
enum Dessert implements Food {
TIRAMISU, GELATO, BLACK_FOREST_CAKE,
FRUIT, CREME_CARAMEL;
}
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
LATTE, CAPPUCCINO, TEA, HERB_TEA;
}
}
}
枚举与Switch
Switch 判断是一般是整型,java1.7 之后支持了字符串,枚举也被switch支持
enum Color {GREEN,RED,BLUE}
public class EnumDemo4 {
public static void printName(Color color){
switch (color){
case BLUE: //无需使用Color进行引用
System.out.println("蓝色");
break;
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
}
}
public static void main(String[] args){
printName(Color.BLUE);
printName(Color.RED);
printName(Color.GREEN);
//蓝色
//红色
//绿色
}
}
EnumMap
EnumSet
之后有时间在补充
参考 https://blog.csdn.net/javazejian/article/details/71333103
Java编程思想