咱们先来写一个简单的枚举类型的定义:java
Java代码mysql
[java] view plaincopyweb
<EMBED id=ZeroClipboardMovie_1 height=18 name=ZeroClipboardMovie_1 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=1&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">sql
public enum WeekDay{ 数据库
MONDAY, TUESDAY, WENSDAY, THURSDAY, FRIDAY; //最后这个“;”可写可不写。 数组
} oracle
这和类、接口的定义很相像嘛!Tiger(jdk1.5的代号)中的枚举类型就是一种使用特殊语法“enum”定义的类。全部的枚举类型是java.lang.Enum的子类。这是Tiger中新引入的一个类,它自己并非枚举类型,但它定义了全部枚举类型所共有的行为。 app
注意:虽然全部的枚举类型都继承自java.lang.Enum,可是你不能绕过关键字“enum”而使用直接继承Enum的方式来定义枚举类型。编译器会提示错误来阻止你这么作。ide
WeekDay中定义的五个枚举常量之间使用“,”分割开来。这些常量默认都是“public static final”的,因此你就没必要再为它们加上“public static final”修饰(编译器会提示出错),这也是为何枚举常量采用大写字母来命名的缘由。并且每个常量都是枚举类型WeekDay的一个实例。你能够经过相似“WeekDay.MONDAY”这种格式来获取到WeekDay中定义的枚举常量,也能够采用相似“WeekDay oneDay = WeekDay.MONDAY”的方式为枚举类型变量赋值(你不能给枚举类型变量分配除了枚举常量和null之外的值,编译器会提示出错)。函数
做为枚举类型实例的枚举常量是如何初始化的呢?其实答案很简单,这些枚举常量都是经过Enum中定义的构造函数进行初始化的。
Java代码
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_2 height=18 name=ZeroClipboardMovie_2 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=2&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
//java.lang.Enum中定义的构造函数,
//两个参数分别是定义的枚举常量名称以及它所在的次序。
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
在初始化的过程当中,枚举常量的次序是按照声明的顺序安排的。第一个枚举常量的次序是0,依此累加。
枚举类型除了拥有Enum提供的方法之外,还存在着两个隐藏着的与具体枚举类型相关的静态方法——values()和valueOf(String arg0)。方法values()能够得到包含全部枚举常量的数组;方法valueOf是java.lang.Enum中方法valueOf的简化版本,你能够经过它,根据传递的名称来获得当前枚举类型中匹配的枚举常量。
咱们来看一个枚举类型使用的小例子。需求中要求能够对指定的日期进行相应的信息输出。对于这么简单的需求,这里就使用枚举类型来进行处理。前面咱们已经定义好了包含有五个工做日的枚举类型。下面的代码则是进行输出的方法:
Java代码
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_3 height=18 name=ZeroClipboardMovie_3 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=3&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
/**
* 根据日期的不一样输出相应的日期信息。
* @param weekDay 表明不一样日期的枚举常量
*/
public void printWeekDay(WeekDay weekDay){
switch(weekDay){
case MONDAY:
System.out.println(“Today is Monday!”);
break;
case TUESDAY:
System.out.println(“Today is Tuesday!”);
break;
case WENSDAY:
System.out.println(“Today is Wensday!”);
break;
case THURSDAY:
System.out.println(“Today is hursday!”);
break;
case FRIDAY:
System.out.println(“Today is Friday!”);
break;
default:
throw new AssertionError("Unexpected value: " + weekDay);
}
}
在Tiger之前,switch操做仅能对int、short、char和byte进行操做。而在Tiger中,switch增长了对枚举类型的支持,由于枚举类型仅含有有限个可使用整数代替的枚举常量,这太适合使用switch语句了!就像上面代码中那样,你在swtich表达式中放置枚举类型变量,就能够在case标示中直接使用枚举类型中的枚举常量了。
注意:case标示的写法中没有枚举类型前缀,这意味着不能将代码写成 case Operation. PLUS,只需将其写成 case PLUS便可。不然,编译器会提示出错信息。
像上面的例子同样,虽然你已经在case标示中穷尽了某个枚举类型中的全部枚举常量,但仍是建议你在最后加上default标示(就像上面代码示意的那样)。由于万一为枚举类型添加一个新的枚举常量,而忘了在switch中添加相应的处理,是很难发现错误的。
为了更好的支持枚举类型,java.util中添加了两个新类:EnumMap和EnumSet。使用它们能够更高效的操做枚举类型。下面我一一介绍给你:
EnumMap是专门为枚举类型量身定作的Map实现。虽然使用其它的Map实现(如HashMap)也能完成枚举类型实例到值得映射,可是使用EnumMap会更加高效:它只能接收同一枚举类型的实例做为键值,而且因为枚举类型实例的数量相对固定而且有限,因此EnumMap使用数组来存放与枚举类型对应的值。这使得EnumMap的效率很是高。
提示:EnumMap在内部使用枚举类型的ordinal()获得当前实例的声明次序,并使用这个次序维护枚举类型实例对应值在数组的位置。
下面是使用EnumMap的一个代码示例。枚举类型DataBaseType里存放了如今支持的全部数据库类型。针对不一样的数据库,一些数据库相关的方法须要返回不同的值,示例中getURL就是一个。
Java代码
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_4 height=18 name=ZeroClipboardMovie_4 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=4&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
//现支持的数据库类型枚举类型定义
public enum DataBaseType{
MYSQL,ORACLE,DB2,SQLSERVER
}
//某类中定义的获取数据库URL的方法以及EnumMap的声明。
……
private EnumMap<DataBaseType ,String> urls =
new EnumMap<DataBaseType ,String>(DataBaseType.class);
public DataBaseInfo(){
urls.put(DataBaseType.DB2,"jdbc:db2://localhost:5000/sample");
urls.put(DataBaseType.MYSQL,"jdbc:mysql://localhost/mydb");
urls.put(DataBaseType.ORACLE,"jdbc:oracle:thin:@localhost :1521:sample");
urls.put(DataBaseType.SQLSERVER,"jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb");
}
/**
* 根据不一样的数据库类型,返回对应的URL
* @param type DataBaseType枚举类新实例
* @return
*/
public String getURL(DataBaseType type){
return this.urls.get(type);
}
在实际使用中,EnumMap对象urls每每是由外部负责整个应用初始化的代码来填充的。这里为了演示方便,类本身作了内容填充。
像例子中那样,使用EnumMap能够很方便的为枚举类型在不一样的环境中绑定到不一样的值上。如:例子中getURL绑定到URL上,在其它的代码中可能又被绑定到数据库驱动上去。
EnumSet是枚举类型的高性能Set实现。它要求放入它的枚举常量必须属于同一枚举类型。EnumSet提供了许多工厂方法以便于初始化,EnumSet做为Set接口实现,它支持对包含的枚举常量的遍历:
Java代码
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_5 height=18 name=ZeroClipboardMovie_5 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=5&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
for(Operation op : EnumSet.range(Operation.PLUS , Operation.MULTIPLY)) {
doSomeThing(op);
}
到目前为止,咱们仅仅使用了最简单的语法定义枚举类型,其实枚举类型能够作更多的事情,在Tiger的定义中,枚举是一种新的类型,容许用常量来表示特定的数据片段,它能胜任普通类的大部分功能,如定义本身的构造函数、方法、属性等等。这也是Java与C/C++或是Pascal中不一样的地方,在那两种语言中枚举类型表明的就是一些int类型的数字,但在Java中枚举更像是一个类。
接下来咱们将丰富一下咱们的枚举类型。
前面定义了包含五个工做日的枚举类型,可是真正在每一个工做日进行操做的动做是在其它类中的printWeekDay方法中进行的。假设咱们通过分析发现对工做日的操做应该属于枚举类型WeekDay的职责,那咱们就能够把枚举类型改造以下:
Java代码
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_6 height=18 name=ZeroClipboardMovie_6 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=6&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
public enum WeekDay {
MONDAY, TUESDAY, WENSDAY, THURSDAY, FRIDAY;
/**
* 根据工做日的不一样打印不一样的信息。
*/
public void printWeekDay(){
switch(this){
case MONDAY:
System.out.println(“Today is Monday!”);
break;
case TUESDAY:
System.out.println(“Today is Tuesday!”);
break;
case WENSDAY:
System.out.println(“Today is Wensday!”);
break;
case THURSDAY:
System.out.println(“Today is Thursday!”);
break;
case FRIDAY:
System.out.println(“Today is Friday!”);
break;
default:
throw new AssertionError("Unexpected value: " + this);
}
}
}
//测试程序
for(WeekDay weekDay: EnumSet.allOf(WeekDay.class)){
System.out.println("the message is : "+weekDay.printWeekDay());
}
如今的枚举类型Operation变得丰满多了,咱们在枚举类型WeekDay中增长了一个printWeekDay方法,你也能够用WeekDay.MONDAY.printWeekDay()方法来进行信息的输出了。
枚举类型也容许定义本身的构造函数,这使得枚举常量能够初始化更多的信息。来看看咱们在EnumMap与EnumSet一文中提到过的枚举类型DataBaseType,它存放了如今支持的全部数据库类型。但它仅是一个“代号”,因为和数据库相关的信息对于一个应用程序来讲是固定不变的,因此把这些数据放置在枚举类型自身中更符合设计的习惯。
Java代码
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_7 height=18 name=ZeroClipboardMovie_7 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=7&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
public enum DataBaseType{
MYSQL("com.mysql.jdbc.Driver", "jdbc:mysql://localhost/mydb"),
ORACLE("oracle.jdbc.driver.OracleDriver",
"jdbc:oracle:thin:@localhost :1521:sample"),
DB2("com.ibm.db2.jdbc.app.DB2Driver",
"jdbc:db2://localhost:5000/sample"),
SQLSERVER("com.microsoft.jdbc.sqlserver.SQLServerDriver",
"jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb");
private String driver;
private String url;
//自定义的构造函数,它为驱动、URL赋值
DataBaseType(String driver,String url){
this.driver = driver;
this.url = url;
}
/**
* 得到数据库驱动
* @return
*/
public String getDriver() {
return driver;
}
/**
* 得到数据库链接URL
* @return
*/
public String getUrl() {
return url;
}
}
//测试程序
for(DataBaseType dataBaseType: EnumSet.allOf(DataBaseType.class)){
System.out.println("the driver is : "+dataBaseType.getDriver());
System.out.println("the url is : "+dataBaseType.getUrl());
}
你注意到例子中的枚举常量是如何声明使用自定义构造函数初始化的吗?仅须要将初始化使用的数据放入在枚举常量名称后面的括号中就能够了。
如今咱们设计出了两个内容丰富的枚举类型,对枚举类型的使用也变得天然了许多。你也许以为枚举类型和类之间差异甚微。但是毕竟枚举类型有着诸多限制,你在实现本身的枚举类型时必定要遵循它们。
1. 枚举类型不能使用extends关键字,可是可使用implements关键字。这样咱们能够把不一样枚举类型共有的行为提取到接口中,来规范枚举类型的行为。
2. 枚举类型的自定义构造函数并不能覆盖默认执行的构造函数,它会跟在默认构造函数以后执行。
3. 枚举类型的自定义构造函数必须是私有的。你不须要在构造函数上添加private关键字,编译器会为咱们代劳的。
4. 枚举类型中枚举常量的定义必须放在最上面,其后才能是变量和方法的定义。
模板方法
谈这个话题前咱们要看一下改写的printWeekDay方法,在那个例子里WeekDay是丰富一些了,不过使用switch对枚举常量逐个判断以便定制不一样的行为,扩展起来要麻烦了一些。假如为WeekDay添加了一个新的枚举常量,若是你忘了同时为它在switch中添加相应的case标示,那么即便有default标示来提示错误,也只能在运行后才能发现。
怎么作能更好一点?咱们前面已经认识到枚举就是一个特殊的类,它能够有方法和属性,同时每一个声明的枚举项都是这个枚举类型的一个实例。那么咱们能不能使用“模板方法模式”来改造一下这个枚举类呢?固然能够!咱们把那个例子重构一下,变成下面这个样子:
Java代码
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_8 height=18 name=ZeroClipboardMovie_8 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=8&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
public enum WeekDay {
MONDAY{
@Override
public void printWeekDay() {
System.out.println(“Today is Monday!”);
}
},
TUESDAY{
@Override
public void printWeekDay() {
System.out.println(“Today is Tuesday!”);
}
},
WENSDAY{
@Override
public void printWeekDay() {
System.out.println(“Today is Wensday!”);
}
},
THURSDAY{
@Override
public void printWeekDay() {
System.out.println(“Today is Thursday!”);
}
},
FRIDAY{
@Override
public void printWeekDay() {
System.out.println(“Today is Friday!”);
}
};
/**
* 根据工做日的不一样打印不一样的信息
*/
public abstract void printWeekDay();
}
首先,咱们把方法printWeekDay改成抽象方法,而后咱们在每个枚举常量中实现了在枚举类型里定义的这个抽象方法。这样,每为枚举类型添加一个新的枚举常量,都必须实现枚举类型中定义的抽象方法,否则编译器提示出错。之因此能够这么作的缘由是,虚拟机将枚举类型中声明的每个枚举常量,建立成为一个单独的枚举类型的子类。
这样,再配合使用Tiger里的静态导入,调用者的代码就能够这样写了:
Java代码
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_9 height=18 name=ZeroClipboardMovie_9 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=9&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
MONDAY.printWeekDay();
TUESDAY.printWeekDay();
//or better...
getWeekDay().printWeekDay();
这些代码显然要比常见的if(weekDay == WeekDay.MONDAY){...} else if(weekDay == WeekDay.TUESDAY) else {...}形式强多了,它们易读、容易扩展和维护。
反向查找
前面说到枚举也能够自定义构造函数,能够用属性来关联更多的数据。那若是咱们有这样的一种须要该怎么办呢?——咱们须要根据关联的数据来获得相应的枚举项,例以下面的这种状况:
Java代码
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_10 height=18 name=ZeroClipboardMovie_10 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=10&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
public final enum Status {
WAITING(0),
READY(1),
SKIPPED(-1),
COMPLETED(5);
private int code;
private Status(int code) {
this.code = code;
}
public int getCode() { return code; }
}
这里每种Status对应了一个code,WAITING对应了0,而COMPLETED对应了5。若是想经过0获得WAITING这个枚举项要怎么作?
作法也很简单,使用一个静态的java.util.Map来把code和枚举项关联起来就能够了,就像这样:
Java代码
[java] view plaincopy
<EMBED id=ZeroClipboardMovie_11 height=18 name=ZeroClipboardMovie_11 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=11&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
public final enum Status {
WAITING(0),
READY(1),
SKIPPED(-1),
COMPLETED(5);
private static final Map<Integer,Status> lookup
= new HashMap<Integer,Status>();
static {
for(Status s : EnumSet.allOf(Status.class)){
lookup.put(s.getCode(), s);
}
}
public static Status get(int code) {
return lookup.get(code);
}
private int code;
private Status(int code) {
this.code = code;
}
public int getCode() { return code; }
}
静态方法get(int)提供了需求中的反向查找能力,而静态块里使用EnumSet来把起映射作用的Map组装起来,Over!
总结:使用枚举,但不要滥用!
学习任何新版语言的一个危险就是疯狂使用新的语法结构。若是这样作,那么您的代码就会忽然之间有 80% 是泛型、标注和枚举。因此,应当只在适合使用枚举的地方才使用它。那么,枚举在什么地方适用呢?一条广泛规则是,任何使用常量的地方,例如目前用 switch 代码切换常量的地方。若是只有单独一个值(例如,鞋的最大尺寸,或者笼子中能装猴子的最大数目),则仍是把这个任务留给常量吧。可是,若是定义了一组值,而这些值中的任何一个均可以用于特定的数据类型,那么将枚举用在这个地方最适合不过。