Java 深入理解枚举

什么是枚举

我们平时定义常量,是这么定义的

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
    }
}

上述代码就是枚举的基本使用

枚举常用的方法

返回类型方法名称方法说明
intcompareTo(E o)比较此枚举与指定对象的顺序
booleanequals(Object other)当指定对象等于此枚举常量时,返回 true。
Class<?>getDeclaringClass()返回与此枚举常量的枚举类型相对应的 Class 对象
Stringname()返回此枚举常量的名称,在其枚举声明中对其进行声明
intordinal()返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)
StringtoString()返回枚举常量的名称,它包含在声明中
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。
booleanisEnum()当且仅当该类声明为源代码中的枚举时返回 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编程思想

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值