为了账号安全,请及时绑定邮箱和手机立即绑定

深入分析 Java 的枚举 enum

标签:
Java

1 定义

enum全称为enumeration,中文意为枚举,枚举简单的说就是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。

创建枚举类型是需要用到enum关键字,如:

public enum Color{  
    RED, GREEN, BLUE, BLACK, PINK, WHITE;  
}enum的语法看似与class不同,但它实际上就是一个类,经过编译器编译之后得到一个.class文件


https://img1.sycdn.imooc.com//5d2cab9b000188f407060233.jpg


把上面的编译成 Gender.class, 然后用 javap -c Gender反编译出来就是

https://img1.sycdn.imooc.com//5d2caba100010efd07260545.jpg


从字节码知


  • Gender 是 final 的

  • Gender 继承自 java.lang.Enum 类

  • 声明了字段对应的两个 static final Gender 的实例

  • 实现了 values() 和  valueOf(String) 静态方法

  • static{} 对所有成员进行初始化

有了以上的字节码,我们作进一步还原出 Gender 的普通类大概是这样的

public final class Gender extends java.lang.Enum { 
  public static final Gender Male;  public static final Gender Female; 
  private static final Gender[] $VALUES; 
  static {
    Male = new Gender("Male", 0);
    Female = new Gender("Female", 1);
 
    $VALUES = new Gender[] {Male, Female};
  }  //是我加上去的,是为了模拟枚举实例的创建,其实实例都是在 static 块中创建的。
  private Gender(String name, int original) {    super(name, original)
  } 
  public static Gender[] values() {    return $VALUE.clone();
  } 
  public static Gender valueOf(String name) {    return Enum.valueOf(Gender.class, name);
  }
}

创建的枚举类型默认是java.lang.enum<枚举类型名>(抽象类)的子类

每个枚举项的类型都为public static final 。

当然上面的那个类是无法被编译的,因为 Java  编译器限制了我们显式的继承自 java.Lang.Enum 类, 报错 "The type Gender may not subclass Enum explicitly", 虽然 java.Lang.Enum 声明的是


https://img1.sycdn.imooc.com//5d2caba900010d7607140074.jpg


这样看来枚举类其实用了多例模式,枚举类的实例是有范围限制的
它同样像我们的传统常量类,只是它的元素是有限的枚举类本身的实例
它继承自 java.lang.Enum, 所以可以直接调用 java.lang.Enum 的方法,如 name(), original() 等
name 就是常量名称


https://img1.sycdn.imooc.com//5d2cabca0001f1b507100112.jpg


original 与 C 的枚举一样的编号

https://img1.sycdn.imooc.com//5d2cabd000017f9d07160190.jpg


因为Java的单继承机制,emum不能再用extends继承其他的类。


https://img1.sycdn.imooc.com//5d2cabd50001a25006960254.jpg


可以在枚举类中自定义构造方法,但必须是  private 或  package protected, 因为枚举本质上是不允许在外面用 new Gender() 方式来构造实例的(Cannot instantiate the type Gender)

结合枚举实现接口以及自定义方法,可以写出下面那样的代码


https://img1.sycdn.imooc.com//5d2cabf50001170c07210488.jpg


方法可以定义成所有实例公有,也可以让个别元素独有

需要特别注明一下,上面在 Male {} 声明一个 print() 方法后实际产生一个 Gender 的匿名子类,编译后的 Gender$1,反编译它


https://img1.sycdn.imooc.com//5d2cabf90001708907240240.jpg


所以在 emum Gender 那个枚举中的成员  Male 相当于是

public static final Male = new Gender$1("Male", 0); //而不是 new Gender("Male", 0)

上面4: Invokespecial #1 要调用到下面的Gender(java.lang.String, int, Gender$1)方法

若要研究完整的 Male 元素的初始化过程就得 javap -c Gender 看 Gender.java 产生的所有字节码,在此列出片断

https://img1.sycdn.imooc.com//5d2cabfd000120b307180236.jpg


在 static{} 中大致看下 Male 的初始过程:加载 Gender

https://img1.sycdn.imooc.com//5d2cac0e0001b3b901990038.jpg

1(java.lang.String, int) 构造函数生成一个 Gender$1 实例赋给 Male 属性


既然enum是一个类,那么它就可以像一般的类一样拥有自己的属性与方法。但Java要求必须先定义enum实例。

否则会编译错误。

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

枚举实例的创建过程:枚举类型符合通用模式 Class Enum<E extends Enum<E>>,而 E 表示枚举类型的名称。枚举类型的每一个值都将映射到 protected Enum(String name, int ordinal) 构造函数中,在这里,每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。

public enum Color{  
    RED, GREEN, BLUE, BLACK, PINK, WHITE;  
}

相当于调用了六次Enum<Color>构造方法

Enum<Color>("RED", 0);

Enum<Color>("GREEN", 1);

Enum<Color>("BLUE", 2);

Enum<Color>("BLACK", 3);

Enum<Color>("PINK",4);

Enum<Color>("WHITE", 5);

枚举类型的常用方法:

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

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

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

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

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

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

二、常用用法
用法一:常量

在JDK1.5 之前,我们定义常量都是: public static fianl.... 。现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。

用法二:switch

JDK1.6之前的switch语句只支持int,char,enum类型,使用枚举,能让我们的代码可读性更强。

enum Color{  
    RED, GREEN, BLUE, BLACK, PINK, WHITE;  
}  
public class TestEnum {  
    public void changeColor(){  
        Color color = Color.RED;  
        System.out.println("原色:" + color);  
        switch(color){  
        case RED:  
            color = Color.GREEN;  
            System.out.println("变色:" + color);  
            break;  
        case GREEN:  
            color = Color.BLUE;  
            System.out.println("变色:" + color);  
            break;  
        case BLUE:  
            color = Color.BLACK;  
            System.out.println("变色:" + color);  
            break;  
        case BLACK:  
            color = Color.PINK;  
            System.out.println("变色:" + color);  
            break;  
        case PINK:  
            color = Color.WHITE;  
            System.out.println("变色:" + color);  
            break;  
        case WHITE:  
            color = Color.RED;  
            System.out.println("变色:" + color);  
            break;  
        }  
    }  
    public static void main(String[] args){  
        TestEnum testEnum = new TestEnum();  
        testEnum.changeColor();  
    }  
}

用法三:实现接口

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

用法四:枚举集合的应用

java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则可以是任意类型。关于这个两个集合的使用就不在这里赘述,可以参考JDK文档

public class Test {  
    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());  
        }  
    }  
}

三、综合实例

最简单的使用

最简单的枚举类

public enum Weekday {
    SUN,MON,TUS,WED,THU,FRI,SAT
}

如何使用它呢?
先来看看它有哪些方法:


https://img1.sycdn.imooc.com//5d2cac33000103d606040473.jpg


这是Weekday可以调用的方法和参数。发现它有两个方法:values()和valueOf()。还有我们刚刚定义的七个变量


https://img1.sycdn.imooc.com//5d2cac380001f70a06520469.jpg


这些事枚举变量的方法。我们接下来会演示几个比较重要的

https://img1.sycdn.imooc.com//5d2cac3c0001366a07120239.jpg

这段代码,我们演示了几个常用的方法和功能:



作者:JavaEdge
链接:https://www.jianshu.com/p/4b51ac50d78d


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消