zoukankan      html  css  js  c++  java
  • 枚举

    1. 特点(约束)

      Java5 新增了 enum 关键字(它与 class、interface 关键字的地位相同),用于定义枚举类。

      枚举类虽然是一种特殊的类,但它一样可以有自己的成员变量、方法,也可以定义自己的构造函器。

      一个 Java 源文件中最多只能定义一个 public 修饰的枚举类,且文件名必须和 public 修饰的类名相同。

      枚举类和普通类的区别:

    1. 使用 enum 定义的枚举类默认继承了 java.lang.Enum 类,而不是继承 Object 类,因此枚举类不能显式继承其他任何类
    2. java.lang.Enum 类实现了 java.lang.Serializablejava.lang.Comparable 接口,因此定义的枚举类也默认实现了上述两个接口。
    3. 枚举类可以实现一个或多个接口。
    4. 使用 enum 定义的非抽象的枚举类默认会使用 final 修饰,因此枚举类不能派生子类。
    5. 枚举类的构造器只能使用 private 修饰,如果省略了构造器的访问控制符,则默认使用 private 修饰。
    6. 枚举类的所有实例必须在枚举类的第一行显式列出,否则这个枚举类永远都不能产生实例。
    7. 系统会对枚举类的实例默认添加 public static final 修饰,无需程序员显式声明。  

     

    2. 定义

      简单定义:

      定义枚举类时,需要显式列出所有的枚举值,值与值之间使用英文逗号(,)隔开,枚举值列举结束后以英文分号(;)结束。

      这些枚举值代表了该枚举类所有可能的实例。如下:

    public enum Season {
        SPRING,SUMMER,FALL,WINTER;
    }

      如果需要使用该枚举类的某个实例,则可使用 EnumClass.variable 的形式,例如: Season.SPRING

    public class EnumTest {
        public void judge(Season season) {
            /*
            * 此处直接使用枚举对象作为表达式,这是 JDK1.5 增加枚举后对 switch 的扩展。
            * switch 的控制表达式可以是任何枚举类型,case 表达式中的值可以直接使用枚举值的名字,无须添加枚举类作为限定。
            * */
            switch (season) {
                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) {
            for (Season s : Season.values()) {   // 枚举类默认都有一个 values() 方法,返回该枚举类的所有实例
                System.out.println(s);  // 实际上输出的是该枚举值的 toString() 方法的值
            }
    
            new EnumTest().judge(Season.SPRING);
        }
        /*
        * SPRING
        * SUMMER
        * FALL
        * WINTER
        * 春风佛面来,百花齐开放。
        * */
    }

      

      Enum 类中的方法:

        所有的枚举类都默认继承了 java.lang.Enum 类,所以枚举类可以直接使用 Enum 类中的方法,Enum 类中提供了如下方法:

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

          boolean equals(Object other): 当指定对象与该枚举值相等时,返回true。

          Class<E> getDeclaringClass(): 返回该枚举值的枚举类型相对应的 Class 对象。

          int hashCode(): 返回该枚举值的哈希码。

          String name(): 返回该枚举对象的名称,这个名称就是定义枚举类时列出的所有枚举值之一。与此方法相比,通常应优先考虑使用 toString() 方法,因为 toString() 方法返回对用户更加友好的名称。

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

          String toString(): 返回该枚举值的名称,与 name() 方法相似,但 toString() 方法更长用。

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

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

        成员变量:

        枚举类虽然是一种特殊类,但它也可以定义成员变量、方法和构造器。例如下面的 Gender 枚举类中,定义了一个 name 实例变量:  

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

        上面 Gender 枚举类中定义了一个 name 实例变量,并且将其定义为 public 访问权限。下面通过如下程序来使用该枚举类:

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

        使用枚举类与使用普通类没有太大的差别,差别只是在产生枚举对象的方式不同,枚举类的对象只能是枚举值,而不能通过 new 来创建枚举类对象。

        成员方法:

        java应该把所有类设计成良好封装的类,所以不应该允许直接访问 Gender 类的 name 成员变量,而应该通过方法来控制对 name 的访问,否则可能出现混乱的情形。

        例如上面的程序恰好设置了 female = “女”,如果采用 female = “男”,那么就会出现 FEMALE 代表男的情况,程序逻辑就混乱了。我们可以使用如下代码来改进 Gender 类的设计:

    public enum Gender {
        MALE,FEMALE;
    
        // 定义一个实例变量 name,声明为 private,外部不能直接访问
        private String name;
    
        // 定义访问 name 变量的方法
        public void setName(String name) throws Exception {
            switch(this){
                case MALE:
                    if ("男".equals(name)) {
                        this.name = name;
                    }else {
                        throw new Exception("参数错误,需要的参数为: 男,但传入的参数为:" + name);
                    }
                    break;
                case FEMALE:
                    if ("女".equals(name)) {
                        this.name = name;
                    }else {
                        throw new Exception("参数错误,需要的参数为:女,但传入的参数为:" + name);
                    }
                    break;
            }
        }
    
        public String getName() {
            return this.name;
        }
    }
    public class GenderTest {
        public static void main(String[] args) throws Exception {
            // 通过 Enum 的 valueOf() 方法来获取指定枚举类的指定枚举值
    //        Gender female = Enum.valueOf(Gender.class, "FEMALE");
            Gender female = Gender.FEMALE;
            // 直接为枚举值的 name 实例变量赋值
            female.setName("男");
            // 直接访问该枚举值的 name 实例变量
            System.out.println(female + " 代表: " + female.getName());  
            /*
            * Exception in thread "main" java.lang.Exception: 参数错误,需要的参数为:女,但传入的参数为:男
            * at demo1.Gender.setName(Gender.java:23)
            * at demo1.GenderTest.main(GenderTest.java:9)
            * */
        }
    }

        上面程序把 name 设置成 private ,从而避免其他程序直接访问 name 成员变量,必须通过 setName() 方法来设置 name 变量的值,这样就保证了 name 变量的值和对应的对象不会产生混乱。

      

        构造器:

        实际上上面的做法依然不够好,枚举类通常应该设计成不可变的类,也就是它的成员变量的值不应该允许改变,这样会更安全,而且代码也更加简洁。

        因此建议将枚举类的成员变量都使用 private final 修饰。

        如果将所有成员变量都使用 private final 修饰,则必须在构造器里为这些成员变量指定初始值(或者在定义成员变量时指定默认值,或者在初始化块中指定初始值,但这两种方式并不常用)。

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

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

        从上面的程序中可以看出,当为枚举类创建了一个构造器后,列出枚举值就应该采用和构造器相对应的方式来完成。

        也就是说,在枚举类中列举枚举值时,实际上就是调用相应的构造器来创建枚举类对象,只是这里无须使用 new ,也无须显式调用构造器。

        前面列出枚举值时无须传入参数,设置无须使用括号,是因为前面的枚举类中包含无参数的构造器。

      实现接口的枚举类:

        由枚举类实现接口方法:

        枚举类也可以实现一个或多个接口。与普通类实现接口完全一样。例如:

    public interface GenderDesc {
        void info();
    }
    public enum Gender implements GenderDesc{
        // 此处的枚举值必须调用对应的构造器来创建
        MALE("男"), FEMALE("女");
    
        // 使用 private final 修饰成员变量
        private final String name;
    
        // 枚举类的构造器默认使用 private 修饰
        Gender(String name) {
            this.name = name;
        }
    
        public String getName() {
            return this.name;
        }
    
    
        @Override
        public void info() {
            System.out.println(name);
        }
    }
    public class GenderTest {
        public static void main(String[] args) {
            Gender female = Gender.FEMALE;
            female.info();  //
    
            Gender male = Gender.MALE;
            male.info();    //
    
        }
    }

        

        由枚举值实现接口方法:

        从上面我们可以看出,如果由枚举类来实现接口方法,则每个枚举值在调用该方法时都有相同的行为方式(因为方法体完全一样)。

        如果需要每个枚举值在调用该方法时呈现出不同的行为方式,则可以让每个枚举值分别来实现接口方法。

        每个枚举值提供不同的实现方式,从而让不同的枚举值调用接口方法时具有不同的行为方式。例如:

    public enum Gender implements GenderDesc{
        // 此处的枚举值必须调用对应的构造器来创建
        MALE("男") {
            @Override
            public void info() {
                System.out.println("这是男性的信息,信息为:" + getName());
            }
        },
        FEMALE("女") {
            @Override
            public void info() {
                System.out.println("这是女性的信息,信息为:" + getName());
            }
        };
    
        // 使用 private final 修饰成员变量
        private final String name;
    
        // 枚举类的构造器默认使用 private 修饰
        Gender(String name) {
            this.name = name;
        }
    
        public String getName() {
            return this.name;
        }
    
    }
    public class GenderTest {
        public static void main(String[] args) {
            Gender female = Gender.FEMALE;
            female.info();  // 这是女性的信息,信息为:女
    
            Gender male = Gender.MALE;
            male.info();    // 这是男性的信息,信息为:男
    
        }
    }

        从上面的代码中可以看出,当创建 MALE 和 FEMALE 两个枚举值时,后面有紧跟了一对花括号,这对括号里包含了一个 info() 方法定义。

        花括号部分实际上就是一个类体部分,这个匿名内部类的语法类似。

        在这种情况下,当创建 MALE 和 FEMALE 枚举值时,并不是直接创建 Gender 枚举类的实例,而是相当于创建 Gender 的匿名子类的实例。

        前面说过,非抽象的枚举类默认使用 final 修饰,不能派生子类。但并不是所有的枚举类都使用 final 修饰。

        对于一个抽象的枚举类而言(只要包含了抽象方法,就是抽象枚举类),系统会默认使用 abstract 修饰。

      包含抽象方法的枚举类:

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

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

    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));
    
            /*
            * 7.0
            * -1.0
            * 12.0
            * 0.75
            * */
        }
    }

        枚举类里定义抽象方法时不能使用 abstract 关键字将枚举类定义成抽象类(系统会自动为类添加 abstract 关键字),

        但枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。

        

    参考自《疯狂java讲义(第3版)》,将其中一些重要的概念和内容记录下来,以便学习。

        

      

  • 相关阅读:
    Codeforces467C George and Job
    Codeforces205E Little Elephant and Furik and RubikLittle Elephant and Furik and Rubik
    Codeforce205C Little Elephant and Interval
    51nod1829 函数
    51nod1574 排列转换
    nowcoder35B 小AA的数列
    Codeforce893E Counting Arrays
    gym101612 Consonant Fencity
    CodeForces559C Gerald and Giant Chess
    CodeForces456D A Lot of Games
  • 原文地址:https://www.cnblogs.com/yingtoumao/p/8867732.html
Copyright © 2011-2022 走看看