Java 枚举

您所在的位置:网站首页 java枚举类型遍历 Java 枚举

Java 枚举

#Java 枚举| 来源: 网络整理| 查看: 265

一、枚举简介

某些情况下,类的对象有限且固定,比如季节,有春夏秋冬四个对象,行星类目前只有8个对象,因此,像这些实例固定且有限的类称为枚举类。

在 JDK 1.5 之前没有枚举类型,那时候一般用接口常量来替代,如:

public static final int SEASON_SPRING=1; public static final int SEASON_SUMMER=2; public static final int SEASON_FALL=3; public static final int SEASON_WINTER=4;

这种方式虽然定义简单,但是类型不安全,季节实际上是一个int的整数,但是int整数之间可以加减,那么进行SEASON_SPRING+SEASON_SUMMER,这样的代码完全正常,但是不合常理,还有打印输出不明确,当打印季节SEASON_SPRING时,实际上打印了数字1。而使用Java 枚举的出现可以更恰当地表示该常量。

使用enum 关键词定义,与class、interface地位是一样的,枚举类也是一种特殊的类,它一样可以有自己的成员变量、方法,可以实现一个或者多个接口,也可以定义自己的构造器。一个Java源文件最多只能定义一个public访问权限的枚举类,且该Java源文件也必须和该枚举类的类名相同。

但是枚举终究不是普通的类,与普通的类有着如下的简单区别:

枚举类可以实现一个或者多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是默认继承了Object类,因此枚举类不能显式继承其他父类,其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口;public abstract class Enum implements Comparable, Serializable { }

既然枚举类都继承了java.lang.Enum类,所以枚举类可以直接使用java.lang.Enum类中包含的方法,java.lang.Enum类有如下几种方法

int compare(E o):该方法用于与指定对象比较顺序,同一个枚举实例只能与相同类型的枚举实例进行比较。如果该枚举对象位于指定枚举对象之后,则返回正整数;如果该枚举对象位于指定枚举对象之前,则返回负整数;否则返回0;

String name():返回此枚举实例的名称,这个名称就是定义枚举类时列出所有的枚举值之一。与此方法相比,大多数程序员优先考虑toString()方法,因为toString()方法返回更加用户友好的名称

int ordinal():返回枚举值在枚举类中的索引值(就是枚举值在枚举声明中的位置,第一个枚举值得索引值为0);

String toString():返回枚举常量的名称,与name方法相似,但是toString()方法更加常用;

public staticT valueOf(ClassenumType,String name):这是一个静态方法,用于返回指定枚举类中指定名称的枚举值,名称必须与该枚举类中声明枚举值时所用的标识符完全匹配,不允许使用额外的空白字符。

当程序员使用System.out.println(s)语句来打印枚举值时,实际上输出的是该枚举值的toString()方法,也就是输出该枚举值的名字。

使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类(对于抽象的枚举类而言,系统会默认使用abstract修饰,而不是final修饰);枚举类的构造器只能用 private访问控制符,如果省略了构造器的访问控制符,默认为 private;如果强制指定访问控制符,则只能指定private修饰符;枚举类的所有实例必须在枚举类第一行显式列出,否则这个枚举类不能产生实例,列出这些实例时,系统自动加上public static final修饰,无需程序员显式添加;枚举类提供了一个value()方法,该方法可以很方便地遍历所有的枚举值:public enum Season { //列出枚举的4个实例 SPRING,SUMMER,FALL,WINTER; } ​ public class TestEnum { public void judge(Season s){ switch (s){ case SPRING: System.out.println("春暖花开,正好踏春!"); break; case SUMMER: System.out.println("夏日炎炎,适合游泳!"); break; case FALL: System.out.println("秋高气爽,进补及时!"); break; case WINTER: System.out.println("冬日雪飘,围炉赏雪!"); break; } } public static void main(String[]args){ //枚举类默认有一个values()方法,返回该枚举类的所有实例 for(Season s:Season.values()){ System.out.println(s); } //使用枚举实例时,可以通过EnumClass.variable形式来访问 new TestEnum().judge(Season.SPRING); } } ​ 运行结果: SPRING SUMMER FALL WINTER 春暖花开,正好踏春! ​

上述程序测试了Season枚举类的用法,该类通过了values()方法返回了Season枚举类的所有实例,并通过循环迭代输出了枚举类的所有的实例

不仅如此,上面程序的switch表达式中还使用了Season对象作为表达式,这是JDK1.5增加枚举后对于switch的扩展:switch的控制表达式可以是任何枚举类型,当switch控制表达式使用枚举类型时,后面的case表达式中的值直接使用枚举值的名字,无需添加枚举类作为限定。

二、枚举的成员变量、方法和构造器

枚举类也是一种类,知识它是比较特殊的类,因此它一样可以定义成员变量、方法和构造器。下面以定义一个People枚举类,该枚举类里包含了一个name实例变量。

public enum People { MALE,FEMALE; //定义一个String类型的成员变量 public String name; } ​ public class TestPeople { public static void main(String[]args){ People p=People.valueOf(People.class,"MALE"); //直接给枚举的成员变量赋值 p.name="女"; //访问枚举的name实例变量 System.out.println(p+"表示:"+p.name); } } ​ 运行结果: MALE表示:女

并不能随意通过new来创建枚举类的对象,Java应该把所有类设计成良好封装的类,所以不应该直接访问People类的name成员变量,而是应该通过方法来控制对name的访问,否则可能很混乱的情形,比如将p.name="男",就会表示FEMALE代表男的局面,因此可以用改进的People类设计

public enum People { MALE,FEMALE; //定义一个String类型的成员变量 private String name; public void setName(String name){ switch(this){ case MALE: if(name.equals("男")){ this.name=name; }else{ System.out.println("参数错误!"); return; } break; case FEMALE: if(name.equals("女")){ this.name=name; }else{ System.out.println("参数错误!"); return; } break; } } public String getName(){ return this.name; } } ​ public class TestPeople { public static void main(String[]args){ People p=People.valueOf(People.class,"MALE"); p.setName("女"); System.out.println(p+"表示:"+p.getName()); p.setName("男"); System.out.println(p+"表示:"+p.getName()); } } ​ 运行结果: 参数错误! MALE表示:null MALE表示:男

上述代码通过get和set方法将FEMALE枚举值得name变量设置为“男”,系统设置会提示错误信息,实际上这种做法还是不够好,枚举类通常应该设计成不可变的类,也就是说成员变量值不应该允许改变,这样会更加安全,代码也会更加简洁,因此建议将枚举类的成员变量都使用private final修饰。

如果将所有的成员变量都使用了final修饰符来修饰,所有必须在构造器里为这些成员变量指定初始值(或者在定义成员变量时指定默认值,或者在初始化块中指定初始值,但是这两种情况并不常见),因此应该为枚举类显式定义带参数的构造器,一旦为枚举类显式定义了带有参数的构造器,列出枚举值得时候就必须对应地传入参数。

public enum People { //此处必须调用对应的枚举构造器来创建 MALE("男"),FEMALE("女"); private final String name; //枚举类的构造器必须用private修饰 private People(String name){ this.name=name; } public String getName(){ return this.name; } }

从上面的程序中可以看出,当为People枚举类创建一个People(String name)构造器之后,列出枚举值就应该采用MALE("男"),FEMALE("女");来完成,也就是说,在枚举类汇总列出枚举值时,实际上就是调用了构造器创建枚举类的对象,只是这里无须使用new关键词,也无须显式调用构造器,前面列出枚举值时无须传入参数,甚至无须使用括号,仅仅是因为前面的枚举类包含无参数的构造器

上面一行代码等同于:

public static final People MALE=new People("男"); public static final People FEMALE=new People("女");三、实现接口的枚举类

枚举类也可以实现一个或者多个接口,与普通类实现一个或多个接口完全一样,枚举类实现一个或者多个接口时,也可以实现该接口所包含的方法,

interface GenderDesc{ void info(); } public enum Gender implements GenderDesc { MALE("男"),FEMALE("女"); //实现接口方法 public void info(){ System.out.println("这是一个用于定义性别的枚举类"); } private final String name; private Gender(String name){ this.name = name; } ​ public String getName(){ return this.name; } }

如果由枚举类来实现接口的方法,则每个枚举值在调用该方法时都会有同样的行为方式(因为方法体完全一样),如果需要每个枚举值在调用该方法时呈现不同的行为方式,则可以让每个枚举值分别来实现该方法,每个枚举值提供不同的实现方式,从而让不同的枚举值调用该方法时具有不同的行为方式,在下面的Genden枚举类中,不同的枚举值对于info()方法的实现各不相同

interface GenderDesc{ void info(); } public enum Gender implements GenderDesc { MALE("男"){ @Override public void info() { System.out.println("male info"); } }, FEMALE("女"){ @Override public void info() { System.out.println("female info"); } }; private final String name; private Gender(String name){ this.name = name; } public String getName(){ return this.name; } } ​

上面这种方式,{}相当于创建Gender的匿名子类的实例,MALE和FEMALE实际上是Gender匿名子类的实例,而不是Gender类的实例,当调用了MALE和FEMALE两个枚举值的方法时,就会看到两个枚举值的方法表现不同的行为方式。

四、包含抽象方法的枚举类

假设有一个Operation枚举类,它的4个枚举值PLUS,MINUS,TIMES,DIVIDE分别代表加减乘除4种运算,该枚举类需要定义一个eval()方法来实运算。

综上所述,可以考虑为Operation枚举类定义一个eval()抽象方法,然后让4个枚举值分别为eval()提供不同的实现:

public enum Operation { PLUS{ @Override public int eval(int a, int b) { return a + b; } }, MINUS{ @Override public int eval(int a, int b) { return a - b; } }, TIMES{ @Override public int eval(int a,int b){ return a*b; } }, DIVIDE{ @Override public int eval(int a,int b){ return a/b; } }; //定义一个抽象方法, //每个枚举值都提供不同的实现 public abstract int eval(int a, int b); ​ public static void main(String[] args){ System.out.println(Operation.PLUS.eval(10, 2)); System.out.println(Operation.MINUS.eval(10, 2)); System.out.println(Operation.TIMES.eval(10, 2)); System.out.println(Operation.DIVIDE.eval(10, 2)); } } ​ 运行结果: 12 8 20 5

它的4个匿名内部子类分别对应一个class文件,枚举类里定义抽象方法时不能使用abstract关键词将枚举定义抽象类(因为系统自动会为它添加abstract关键词),但因为枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。

枚举集合

java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则可以是任意类型。

//定义数据库类型枚举 public enum DataBaseType { MYSQUORACLE,DB2,SQLSERVER } //某类中定义的获取数据库URL的方法以及EnumMap的声明 private EnumMapurls=new EnumMap(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://sql:1433;Database=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 接口实现,它支持对包含的枚举常量的遍历:

for(Operation op:EnumSet.range(Operation.PLUS,Operation.MULTIPLY)){ doSomeThing(op); } ​



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3