天天看点

java enum用法_Java 枚举

java enum用法_Java 枚举

一、枚举简介

某些情况下,类的对象有限且固定,比如季节,有春夏秋冬四个对象,行星类目前只有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<E extends Enum<E>>
        implements Comparable<E>, 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 static<T extends Enum<T>>T valueOf(Class<T>enumType,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

两个枚举值的方法时,就会看到两个枚举值的方法表现不同的行为方式。

java enum用法_Java 枚举

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

假设有一个

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 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://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

提供了许多工厂方法以便于初始化,如图所示:

java enum用法_Java 枚举

EnumSet

作为

Set

接口实现,它支持对包含的枚举常量的遍历:

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

继续阅读