您的位置:首页 > 编程语言 > Java开发

Java——枚举类详解

2016-01-26 17:43 423 查看

1.枚举类入门

Java 5 新增了一个enum关键字(它与class、interface关键字的地方相同),用以定义枚举类。正如前面看到的,枚举类是一种特殊的类,他一样可以有自己的成员变量、方法,可以实现一个或多个接口,也可以定义自己的构造器。一个Java源文件中最多只能定义一个public访问权限的枚举类,且java源文件也必须和该枚举类的类名相同。

但枚举类终究不是普通类,它与普通类有如下简单区别。

(1)枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是默认继承Object类,因此枚举类不能显示继承其他父类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口。

(2)使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。

(3)枚举类的构造器只能使用private访问控制符,如果省略了构造器的访问控制符,则默认使用private修饰;如果强制指定访问控制符,则只能指定private修饰符。

(4)枚举类的所有实例必须在枚举类的第一行显式列车,否则这个枚举类永远都不能产生实例。列出这些实例时,系统会自动添加public static final修饰,无须程序员显式添加。

枚举类默认提供了一个values()方法,该方法可以很方便地遍历所有的枚举值。

[code]public enum SeasonEnum {
    // 在第一行列出4个枚举实例
    SPRING,SUMMER,FALL,WINTER;
}


编译上面Java程序,将生成一个SeasonEnu.class文件,这表明枚举类型是一个特殊的Java类。由此可见,enum关键字和class、interface关键字的作用大致相似。

定义枚举类时,需要显示列出所有的枚举值,如上面的SPRING,SUMMER,FALL,WINTER;所示,所有的枚举值之间以英文逗号(,)隔开,枚举值列举结束后以英文分号作为结束。这些枚举值代表了该枚举类的所有可能的实例。

如果需要使用该枚举类的某个实例,则可使用EnumClass.variable的形式,如SeasonEnum.SPRING.下面我们通过一个例子来了解一下枚举的简单用法。

[code]public class Test3 {

    public enum SeasonEnum {
        // 在第一行列出4个枚举实例
        SPRING, SUMMER, FALL, WINTER;
    }

    public void judge(SeasonEnum seasonEnum) {
        // switch 语句里的表达式可以是枚举值
        switch (seasonEnum) {
            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) {
        Test3 test3 = new Test3();
        // 枚举类默认有一个values()方法,返回该枚举类的所有实例
        for (SeasonEnum s : SeasonEnum.values()) {
            System.out.println(s);
        }
        // 使用枚举实例时,可通过EnumClass.variable形式来访问
        test3.judge(SeasonEnum.SPRING);
    }
}


java.lang.Enum类

因为所有的枚举类都继承了java.lang.Enum类,所以枚举类可以直接使用java.lang.Enum类中所有包含的方法。java.lang.Enum类中提供了如下几个方法:

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

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

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

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

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

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

我们来看一个例子:

[code]public class Test4 {

    public enum Gender {
        MALE,FEMALE;
        // 定义一个public修士的实例变量
        public String name;
    }

    public static void main(String[] args) {
        // 通过Enum的valueOf()方法来获取指定枚举类的枚举值
        Gender g = Enum.valueOf(Gender.class, "FEMALE");
        // 直接为枚举值的name实例变量赋值
        g.name = "女";
        // 直接访问枚举值的name实例变量
        System.out.println(g + "代表:" + g.name);
    }
}


上面程序使用Gender枚举类时与使用一个普通类没有太大的差别,差别只是产生Gender对象的方式不同,枚举类的实例只能是枚举值,而不是随意地通过new来创建枚举类对象。

正如前面提到的,Java应该吧所有类设计成良好封装的类,所以不应该允许直接访问Gender类的name成员,而是应该通过方法来控制对name的访问。否则可能出现混乱的情形,例如上面程序恰好设置了g.name = “女”,要是采用g.name = “男”,那程序就会非常混乱了,可能出现FEMALE代表男的局面。可以按照如下代码来改进Gender类的设计。

[code]public class Test4 {

    public enum Gender {
        MALE, FEMALE;
        // 定义一个public修士的实例变量
        public 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 name;
        }
    }

    public static void main(String[] args) {
        // 通过Enum的valueOf()方法来获取指定枚举类的枚举值
        Gender g = Enum.valueOf(Gender.class, "FEMALE");
        // 直接为枚举值的name实例变量赋值
        g.setName("女");
        // 直接访问枚举值的name实例变量
        System.out.println(g + "代表:" + g.name);
        // 此时设置name值时将会提示参数错误
        g.setName("男");
        // 直接访问枚举值的name实例变量
        System.out.println(g + "代表:" + g.name);
    }
}


上面的做法可以提高安全性,但是还是不够好。枚举类通常应该设计成不可变类,就是说,它的成员变量值不应该允许改变,这样会更安全,而且代码更加简洁。因此建议将枚举类的成员变量使用private final 修饰。

如果所有的成员变量都是用了final修饰符来修饰,所以必须在构造器里为这些成员变量指定初始值(或者在定义成员变量是指定默认值,或者在初始化块中指定初始值,但这两种情况并不多见),因此应该为枚举类显示定义带参数的构造器。

一旦为枚举类显示定义了带参数的构造器,列出枚举值时就必须对应地传入参数:

我们更改上面的例子如下:

[code]public enum Gender {
    // 此处的枚举值必须调用对应的构造器来创建
    MALE("男"), FEMALE("女");
    // 定义一个public修士的实例变量
    private final String name;
    private Gender(String name) {
        this.name = name();
    }

    public String getName() {
        return this.name;
    }
}


实现接口的枚举类

枚举类也可以实现一个或多个接口。与普通类实现一个或多个接口完全一样,枚举类实现一个或多个接口时,也需要实现该接口所包含的方法。如下面代码所示:

[code]    public interface GenderDesc {
        void info();
    }

    public enum Gender implements GenderDesc{
        // 此处的枚举值必须调用对应的构造器来创建
        MALE("男"), FEMALE("女");
        // 定义一个public修士的实例变量
        private final String name;
        private Gender(String name) {
            this.name = name;
        }

        public String getName() {
            return this.name;
        }

        @Override
        public void info() {
            System.out.println("这是一个用于定义性别的枚举类");
        }
    }


与普通类实现接口完全一样,如果由枚举类来实现接口里的方法,则每个枚举值在调用这个方法时都有相同的行为方式(因为方法体完全一样)。如果需要每个枚举值在调用该方法时呈现出不同行为方式,则可以让每个枚举值分别来实现该方法,每个枚举值提供不同的实现方式,从而不同的枚举值调用该方法是具有不同的行为方式。代码如下:

[code]public interface GenderDesc {
        void info();
    }

    public enum Gender implements GenderDesc{
        // 此处的枚举值必须调用对应的构造器来创建
        MALE("男")
        {
            @Override
            public void info() {
                System.out.println("这个枚举值代表男");
            }
        }, 
        FEMALE("女")
        {
            @Override
            public void info() {
                System.out.println("这个枚举值代表女");
            }
        };
        // 定义一个public修士的实例变量
        private final String name;
        private Gender(String name) {
            this.name = name();
        }

        public String getName() {
            return this.name;
        }

    }


这里分别实现接口方法的语法与匿名内部类语法大致相似,只是它依然是枚举类的匿名内部子类。

注:并不是所有的枚举类都是用了final修饰!非抽象的枚举类才默认使用final修饰。对于一个抽象的枚举类而言——只要包含了抽象方法,他就是抽象枚举类,系统默认使用abstract修饰,而不是使用final修饰。

包含抽象方法的枚举类

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

从上面描述可以看出,Operation需要让PLUS,MINUS,TIMES,DIVIDE四个值对eval()方法各有不同的实现,此时可以考虑为Operation枚举类定义一个eval()抽象方法,然后让4哥枚举值分别为eval()提供不同的实现,代码如下:

[code]
package com.lyong.test;  

public enum Operation {

    PLUS {

        @Override
        public double eval(double x, double y) {
            return x + y;
        }

    },

    MINUS {

        @Override
        public double eval(double x, double y) {
            return x - y;
        }

    },

    TIMES {

        @Override
        public double eval(double x, double y) {
            return x * y;
        }

    },

    DIVIDE {

        @Override
        public double eval(double x, double y) {
            return x / y;
        }

    };

    // 为枚举类定义一个抽象方法
    // 这个抽象方法由不同的枚举值提供不同的实现
    public abstract double eval(double x, double y);

    public static void main(String[] args) {
        System.out.println(Operation.PLUS.eval(3, 4));
        System.out.println(Operation.MINUS.eval(3, 4));
        System.out.println(Operation.TIMES.eval(3, 4));
        System.out.println(Operation.DIVIDE.eval(3, 4));
    }
}


编译上面程序会生成5个class文件,其实Operation对应一个class文件,它的4个匿名内部子类分别对应一个class文件。

枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类(因为系统自动会为它添加abstract关键字),但因为枚举类需要显示创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: