`

Java 1.5新特性Enum的学习和使用

    博客分类:
  • Java
 
阅读更多
很多时候我们定义了一组值来表示用于特定的数值,往往都是习惯性地使用常量:
private static final int COLOR_WHITE = Color.WHITE;
private static final int COLOR_BLACK = Color.BLACK;

后来才知道原来这样会使得类型不安全,你必须确保是int,而且还要确保它的范围必须正确。
private void updateBackgourndColor(int color){
        if (color == COLOR_WHITE || color == COLOR_BLACK)
                frameView.setBackgroundColor(color);
        }
}

像以上的代码,一旦color定义的常量以后增加了,这里就需要相应地修改。而enum可以很方便地解决。

首先看看最简单的enum用法:
public class EnumMonth{
        public static void main(String[] args) {
                for (Month month : Month.values()) {
                        System.out.println(month);
                }
        }

        private enum Month {
                JAN, SEP, MAR, APR, MAY;
        }
}

运行结果:
JAN
SEP
MAR
APR
MAY


使用javap EnumMonth$Month看到反编译的结果,Month会被编译成一个Java类:
Compiled from "EnumMonth.java"
final class EnumMonth$Month extends java.lang.Enum{
        public static final EnumMonth$Month JAN;
        public static final EnumMonth$Month SEP;
        public static final EnumMonth$Month MAR;
        public static final EnumMonth$Month APR;
        public static final EnumMonth$Month MAY;
        public static EnumMonth$Month[] values();
        public static EnumMonth$Month valueOf(java.lang.String);
        static {};
}


    1. 这个Month枚举类是final class,即不能被继承,而它本身是继承于Enum;
    2. 这些枚举值是Month对象,而且是static final修饰的;
    3. 注意values()方法,它能得到所有的枚举值,但这方法在Enum里面没有找到,我是在看到反编译结果后才确认的。

而valueOf(String)方法,返回带指定名称的指定枚举类型的枚举常量。在使用javap -c EnumMonth$Month进一步查看汇编代码后,知道原来是调用Enum.valueOf(Class<T> enumType, String name):
public static EnumMonth$Month valueOf(java.lang.String);
    Code:
     0:  ldc_w  #4; //class EnumMonth$Month
     3:  aload_0
     4:  invokestatic  #5; //Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
     7:  checkcast  #4; //class EnumMonth$Month
     10:  areturn

static{}里面主要是new这些枚举值对象。
0:  new  #4; //class EnumMonth$Month
3:  dup
4:  ldc  #7; //String JAN
6:  iconst_0
7:  invokespecial  #8; //Method "<init>":(Ljava/lang/String;I)V
10:  putstatic  #9; //Field JAN:LEnumMonth$Month;

这里要说说Enum的构造函数Enum(String name, int ordinal),形参name就是枚举值的string形式("JAN"),ordinal值则是编译器按照枚举值的顺序从0排着赋值的。要注意,这个ordinal值原来是不能改变的,即编译器是从0升序赋值的,在字节码里面固定写着iconst_0(n=0,1,2...)的。
static{
        JAN = new EnumMonth$Month("JAN", 0);
        SEP = new EnumMonth$Month("SEP", 1);
        ...
}
于是Enum里面的String name()和int ordinal()方法就是返回这两个值的。Enum.toString()也是返回name值,这就是为什么System.out.println(month);会输出那些值。

看看下面的例子:
public class EnumMonth{
        public static void main(String[] args) {
                for (Month month : Month.values()) {
                        System.out.println(month.ordinal());
                }
        }

        private enum Month {
                JAN(1), SEP(9), MAR(3), APR(4), MAY(5);
                
                final int id;
                
                Month(int id){
                        this.id = id;
                }
        }
}

一开始没有加上id成员变量和构造函数的,编译时提示找不到构造函数 Month(int),我当初还以为JAN(1)的1会赋值给形参ordinal呢!修正后,运行结果是
0
1
2
3
4
说明ordinal值是固定不变的。

查看汇编代码:
0:     new         #4; //class EnumMonth$Month
3:     dup
4:     ldc         #8; //String JAN
6:     iconst_0
7:     iconst_1
8:     invokespecial     #9; //Method "<init>":(Ljava/lang/String;II)V
11:    putstatic             #10; //Field JAN:LEnumMonth$Month;
这时发现构造函数似乎增加一个形参int类型id。
我再尝试把int id改为char,运行结果是一样的:
private enum Month {
        JAN('1'), SEP('9'), MAR('3'), APR('4'), MAY('5');

        final char id;

        Month(char id){
                this.id = id;
        }
}
但编译代码有所不同:
0:     new         #4; //class EnumMonth$Month
3:     dup
4:     ldc         #8; //String JAN
6:     iconst_0
7:     bipush    49
9:     invokespecial     #9; //Method "<init>":(Ljava/lang/String;IC)V
12:    putstatic             #10; //Field JAN:LEnumMonth$Month;
所以个人认为,如果我们添加了自定义的构造函数,编译器会自动构建新的构造函数:
Enum(String name, int ordinal, char id) {
        super(name, ordinal);
       
        //copy 自定义构造函数的内容
        this.id = id;
}
注意:构造器只能私有private,绝对不允许有public构造器。否则提示modifier public not allowed here。

现在了解了枚举值就是该枚举类的对象,可以传常量数值作为枚举对象的属性以及自定义构造函数,接下来看看我们平时常用的switch例子:
switch(color) {
case RED:
        System.out.println("红色");
        break;
case GREEN:
        System.out.println("绿色");
        break;
case BLACK:
        System.out.println("黑色");
        break;
...
default:
        break;
}

实际上用以下代码可以解决,每个枚举对象都包含desc属性值和getDesc()方法:
public class EnumColor {
        public static void main(String[] args) {
                for (Color color : Color.values())
                        System.out.println(color + " is : " + color.getDesc());
        }

        private enum Color {
                RED("红色"), GREEN("绿色"), BLUE("蓝色"), YELLOW("黄色"), BLACK("黑色"), WHITE("白色");

                private final String desc;

                private Color(String desc) {
                        this.desc = desc;
                }

                public String getDesc() {
                        return desc;
                }
        }
}

运行结果:
RED is : 红色
GREEN is : 绿色
BLUE is : 蓝色
YELLOW is : 黄色
BLACK is : 黑色
WHITE is : 白色

以上的例子提醒我们,不需要在外面的代码添加switch逻辑来判断以赋予不同的值,直接在enum里面处理就完成了。

注意:在case标签中,枚举前缀不能出现,即case Color.RED是不合法的,只能直接用枚举值RED。而在其他地方出现时则必须用Color.RED。

为什么switch可以支持enum呢?switch其实是支持int基本类型,而因为byte,short,char可以向上转换为int,所以switch也支持它们,但long因为转换int会截断便不能支持。
    而enum在switch中也是int类型,看了《switch之enum》,应该这样解释:
    我使用jd反编译上面那段switch代码:
??? = EnumColor.Color.RED;
switch (EnumColor.1.$SwitchMap$EnumColor$Color[???.ordinal()]) {
case 1:
    System.out.println("红色");
    break;
case 2:
    System.out.println("绿色");
    break;
case 3:
    System.out.println("黑色");
    break;
}
可以看到case后面的值变成int值,但这个数值并不是枚举值的ordinal。jd没有把EnumColor$1里面的代码反编译出来,但看了文章里面那段static int[] $SWITCH_TABLE$meiju$EnumTest(),原来这里面有一个int数组,按照枚举值的ordinal值作为索引值,依次给数组元素赋值从1开始:
ai[Color.RED.ordinal()] = 1;
ai[Color.GREEN.ordinal()] = 2;
...
再看看switch括号里面的就是数组[???.ordinal()],明了!

下面说一下方法枚举(多态),我一开始写的是以下代码:
public class EnumGrade {
        public static void main(String[] args) {
                for (Grade grade : Grade.values())
                        System.out.println(grade + "'s result is : " + grade.getResult());
        }

        private enum Grade {
                A(1) {
                        public int getResult() {
                                return base;
                        }
                },

                B(3) {
                        public int getResult() {
                                return base*2;
                        }
                },

                C(5) {
                        public int getResult() {
                                return base*3;
                        }
                };

                private final int base;

                private Grade(int base) {
                        this.base = base;
                }

                public abstract int getResult();
        }
}

结果编译不通过:
EnumGrade.java:10: 无法从静态上下文中引用非静态 变量 base
                return base;
                       ^
EnumGrade.java:16: 无法从静态上下文中引用非静态 变量 base
                return base*2;
                       ^
EnumGrade.java:22: 无法从静态上下文中引用非静态 变量 base
                return base*3;
                       ^
于是修改为以下代码:
public class EnumGrade {
        public static void main(String[] args) {
                for (Grade grade : Grade.values())
                        System.out.println(grade + "'s result is : " + grade.getResult(grade.ordinal()));
        }

        private enum Grade {
                A {
                        public int getResult(int base) {
                                return base;
                        }
                },

                B {
                        public int getResult(int base) {
                                return base*2;
                        }
                },

                C {
                        public int getResult(int base) {
                                return base*3;
                        }
                };

                public abstract int getResult(int base);
        }
}

运行结果:
A's result is : 0
B's result is : 2
C's result is : 6


另外Java1.5还有EnumMap和EnumSet(参考Java枚举类型enum使用详解):
import java.util.EnumMap;
import java.util.EnumSet;

public class EnumState{
        public static void main(String[] args) {
                // EnumSet的使用
                EnumSet<State> stateSet = EnumSet.allOf(State.class);
                for (State s : stateSet) {
                        System.out.println(s);
                }
                // EnumMap的使用
                EnumMap<State,String> stateMap = new EnumMap<State,String>(State.class);
                stateMap.put(State.ON, "is On");
                stateMap.put(State.OFF, "is off");
                for (State s : State.values()) {
                        System.out.println(s.name() + ":" + stateMap.get(s));
                }
        }

        private enum State {
                ON, OFF
        };
}

运行结果:
ON
OFF
ON:is On
OFF:is off
分享到:
评论

相关推荐

    java1.5新特性

    java1.5之后的新特性: 1,枚举(enum) 作用:一般用于代表一组相同类型的常用常量。 原理:语法结构与java类的语法不一样,但是经过编译器编译之后产生的是一个class文件。该class文件经过反编译之后实际上是...

    JDK 1.5新特性---枚举类型

    Enum是Sun全新引进的一个关键字,看起来很象是特殊的class,它也可以有自己的变量,可以定义自己的方法,可以实现一个或者多个接口。在声明一个enum类型时,应该注意到enum类型有如下的一些特征:

    三分钟快速掌握Java中枚举(enum)

    enum的全称为enumeration, 是 JDK 1.5中引入的新特性,存放在 java.lang包中。下面这篇文章是我在使用enum过程中的一些经验和总结,分享出来方便大家快速的掌握Java中枚举(enum),有需要的朋友们下面跟着小编来一起...

    Java enum(枚举)的使用

     enum 的全称为 enumeration, 是 JDK 1.5 中引入的新特性,存放在 java.lang 包中,在上面的这种情况下,enum能派上用场了。枚举类型的用途不仅如此,具体可用场景可看下面的介绍  1、常量  以前我们定义一...

    JAVA面向对象详细资料

    31.8 静态导入(1.5新特性) 46 32 单例模式 47 32.1 饿汉模式 47 32.2 懒汉模式 47 33 接口(interface) 48 33.1 如何创建一个接口。 48 33.2 如何使用接口 48 33.3 如何使用类实现一个接口 49 33.4 接口的细节 49...

    Java开发技术大全 电子版

    11.2.8枚举(Enum)使用示例355 11.2.9枚举集(EnumSet)使用示例358 11.3常用算法361 11.3.1Collections中的简单算法361 11.3.2排序362 11.3.3二分查找364 11.4遗留的类和接口366 11.4.1Enumeration接口简介...

    C++编程思想 (作者学习C++亲身体会及多年教学经验)

    本书作者根据自己学习C++的亲身体会及多年教学经验,用简单的例子和简练的叙述讲解C++编程,别具特色。 全书共分十八章,内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流...

    C++编程思想1-5 清晰PDF

    本书作者根据自己学习C++的亲身体会及多年教学经验,用简单的例子和简练的叙述讲解C++编程,别具特色。 全书共分十八章,内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流...

    MySQL5.1参考手册官方简体中文版

    1.5.5. MaxDB和MySQL之间的特性差异 1.5.6. MaxDB和MySQL之间的协同性 1.5.7. 与MaxDB有关的链接 1.6. MySQL发展大事记 1.6.1. MySQL 5.1的新特性 1.7. MySQL信息源 1.7.1. MySQL邮件列表 1.7.2. IRC(在线聊天系统...

    MySQL 5.1参考手册

    1.5.5. MaxDB和MySQL之间的特性差异 1.5.6. MaxDB和MySQL之间的协同性 1.5.7. 与MaxDB有关的链接 1.6. MySQL发展大事记 1.6.1. MySQL 5.1的新特性 1.7. MySQL信息源 1.7.1. MySQL邮件列表 1.7.2. IRC(在线聊天系统...

    mysql官方中文参考手册

    1.5.5. MaxDB和MySQL之间的特性差异 1.5.6. MaxDB和MySQL之间的协同性 1.5.7. 与MaxDB有关的链接 1.6. MySQL发展大事记 1.6.1. MySQL 5.1的新特性 1.7. MySQL信息源 1.7.1. MySQL邮件列表 1.7.2. IRC(在线聊天系统...

    MYSQL中文手册

    1.5.5. MaxDB和MySQL之间的特性差异 1.5.6. MaxDB和MySQL之间的协同性 1.5.7. 与MaxDB有关的链接 1.6. MySQL发展大事记 1.6.1. MySQL 5.1的新特性 1.7. MySQL信息源 1.7.1. MySQL邮件列表 1.7.2. IRC(在线...

    MySQL 5.1参考手册中文版

    1.5.5. MaxDB和MySQL之间的特性差异 1.5.6. MaxDB和MySQL之间的协同性 1.5.7. 与MaxDB有关的链接 1.6. MySQL发展大事记 1.6.1. MySQL 5.1的新特性 1.7. MySQL信息源 1.7.1. MySQL邮件列表 1.7.2. IRC(在线聊天...

    MySQL 5.1参考手册 (中文版)

    1.5.5. MaxDB和MySQL之间的特性差异 1.5.6. MaxDB和MySQL之间的协同性 1.5.7. 与MaxDB有关的链接 1.6. MySQL发展大事记 1.6.1. MySQL 5.1的新特性 1.7. MySQL信息源 1.7.1. MySQL邮件列表 1.7.2. IRC(在线聊天系统...

    C++编程思想(中文版)

    本书作者根据自己学习C++的亲身体会及多年教学经验,用简单的例子和简练的叙述讲解C++编程,别具特色。 全书共分十八章,内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流...

    mysql5.1中文手册

    MaxDB和MySQL之间的特性差异 1.5.6. MaxDB和MySQL之间的协同性 1.5.7. 与MaxDB有关的链接 1.6. MySQL发展大事记 1.6.1. MySQL 5.1的新特性 1.7. MySQL信息源 1.7.1. MySQL邮件列表 1.7.2. IRC...

    MySQL 5.1中文手冊

    1.5.5. MaxDB和MySQL之间的特性差异 1.5.6. MaxDB和MySQL之间的协同性 1.5.7. 与MaxDB有关的链接 1.6. MySQL发展大事记 1.6.1. MySQL 5.1的新特性 1.7. MySQL信息源 1.7.1. MySQL邮件列表 1.7.2. IRC(在线聊天系统...

    MySQL 5.1官方简体中文参考手册

    目录 前言 1. 一般信息 1.1. 关于本手册 ...1.5.5. MaxDB和MySQL之间的特性差异 1.5.6. MaxDB和MySQL之间的协同性 1.5.7. 与MaxDB有关的链接 1.6. MySQL发展大事记 1.6.1. MySQL 5.1的新特性 1.7. MySQL信息源 ...

Global site tag (gtag.js) - Google Analytics