zoukankan      html  css  js  c++  java
  • 5.9 枚举类

    在某些情况下,一个类的对象是有限而且固定的,比如季节类只有四个对象。这种实例有限而且固定的类,在Java里被称为枚举类。

    一、手动实现枚举类

    在早期代码中,可能会直接使用简单的静态常量来表示枚举,例如:

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

     存在问题:
    1、类型不安全:因为上面每个季节都是一个整数,因此完全可以把一个季节当成一个整数使用,例如执行加法运算:SEASON_SPRING+SEASON_SUMMER

    2、没有命名空间:当需要使用季节时,必须在SPRING前面使用SEASON_前缀,否则程序可能与其他类的静态常量混淆。

    3、打印输出的意义不明确:当输出某个季节时,例如输出SEASON_SPRING,实际输出为1,这个1很难猜测它代表春天。

    枚举有存在的意义,因此早期也可采用通过定义类的方式来实现,可采用以下设计方式:

    1、通过private将构造器隐藏起来

    2、把这个类的所有实例都使用public final static修饰符的类变量来保存。

    3、如有必要,可提供一些静态方法,允许在其他程序根据特定参数来获取与之匹配的实例。

    4、通过枚举类使程序更加健壮,避免创建对象的随意性。

    二、枚举类入门

    Java 5新增关键字enum,用于定义枚举。枚举是一种特殊的类,他一样可以拥有自己的成员变量、方法、可以实现一个或多个接口,也可以定义自己的构造器。

    枚举类与普通类的区别:

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

    (2)使用enum定义、非抽象的枚举类默认会使用final修饰。不允许被继承,只能用于产生实例。

    (3)枚举类的构造器只能使用private修饰,如果省略了private,则系统会为它默认添加private。由于枚举类的构造器使用的private修饰,而子类构造器总是要调用父类构造器一次,因此枚举类不能派生子类。

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

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

    下面程序定义了一个SeasonEnum枚举类:

    1 public enum SeasonEnum
    2 {
    3      //在一行列出四个枚举类
    4     SPRING,SUMMER,FALL,WINTER;      
    5 }

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

     1 enum SeasonEnum
     2 {
     3     //在一行列举出4个枚举实例
     4     SPRING,SUMMER,FALL,WINTER;
     5 }
     6 public class EnumTest
     7 {
     8     public void judge(SeasonEnum s)
     9     {
    10         // switch语句里的表达式可以是枚举值
    11         switch (s)
    12         {
    13             case SPRING:
    14                 System.out.println("春暖花开,正好踏青");
    15                 break;
    16             case SUMMER:
    17                 System.out.println("夏日炎炎,适合游泳");
    18                 break;
    19             case FALL:
    20                 System.out.println("秋高气爽,进补及时");
    21                 break;
    22             case WINTER:
    23                 System.out.println("冬日雪飘,围炉赏雪");
    24                 break;
    25         }
    26     }
    27     public static void main(String[] args)
    28     {
    29         // 枚举类默认有一个values方法,返回该枚举类的所有实例
    30         for (var s : SeasonEnum.values())
    31         {
    32             System.out.println(s);
    33         }
    34         // 使用枚举实例时,可通过EnumClass.variable形式来访问
    35         new EnumTest().judge(SeasonEnum.SPRING);
    36     }
    37 }
    38 ---------- 运行Java捕获输出窗 ----------
    39 SPRING
    40 SUMMER
    41 FALL
    42 WINTER
    43 春暖花开,正好踏青
    44 
    45 输出完成 (耗时 0 秒) - 正常终止
    View Code

     枚举类名.valueOf();这条语句返回枚举类的所有实例。

    JDK 1.5对switch进行扩展:swith表达式可以是任何枚举类型,后面的case可以直接使用枚举值的名字,无需添加枚举类作为限定。

    所有枚举类都继承java.lang.Enum类,因此所有枚举类都可以直接使用java.lang.Enum类的所有方法。java.lang.Enum类中提供的方法包括:
    (1)``int compareTo(E o);``该方法返回枚举对象的比较顺序,同一个枚举实例只能与相同类型的枚举类进行比较。如果该枚举类位于指定枚举类之后,则会返回正数;如果枚举类位于指定枚举类之前,则会返回负数。

    1 System.out.println((SeasonEnum.SUMMER).compareTo(SeasonEnum.SPRING));//输出1
    2 System.out.println((SeasonEnum.SUMMER).compareTo(SeasonEnum.SUMMER));//输出0
    3 System.out.println((SeasonEnum.SUMMER).compareTo(SeasonEnum.FALL));//输出-1
    4 System.out.println((SeasonEnum.SUMMER).compareTo(SeasonEnum.WINTER));//输出-2
    View Code

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

    1 System.out.println(SeasonEnum.SUMMER.name());//输出SUMMER
    2 System.out.println(SeasonEnum.SUMMER.toString());//输出SUMMER
    View Code

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

    1 System.out.println(SeasonEnum.SUMMER.ordinal());//输出1

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

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

    System.out.println(Enum.valueOf(SeasonEnum.class,"SUMMER"));//输出SUMMER

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

    3.1 在枚举类中定义成员变量、方法

     1 enum Gender
     2 {
     3     //列举出所有枚举类型
     4     MALE,FEMALE;
     5     
     6     //枚举类的实例变量,private修饰避免外部程序对实例变量进行修改,
     7     //只能通过同一个类中的stter()方法进行修改
     8     private String name;
     9     //setter()方法,MALL只能设置为"男"
    10     public void setName(String name)
    11         {
    12             switch(this)
    13             {
    14                 case MALE:
    15                     if(name.equals("男"))//String类已经重写了equals()方法,只需要内容相同即可
    16                         this.name=name;
    17                     else
    18                         {
    19                             System.out.println("参数错误");
    20                             return;
    21                         }
    22                     break;
    23                 case FEMALE:
    24                     if(name.equals("女"))//String类已经重写了equals()方法,只需要内容相同即可
    25                         this.name=name;
    26                     else
    27                         {
    28                             System.out.println("参数错误");
    29                             return;
    30                         }
    31                     break;
    32             }
    33         }
    34         public String getName()
    35         {
    36             return this.name;
    37         }
    38 }
    39 public class GnenderTest
    40 {
    41     public static void main(String[] args)
    42     {
    43         Gender g=Enum.valueOf(Gender.class,"FEMALE");//FEMALE代表女
    44         g.setName("女");
    45         System.out.println(g+"代表"+g.getName());//参数错误
    46         g.setName("男");
    47     }
    48 }
    View Code

     上面的做法不太好的一点是,所有成员枚举类应该设计成不可变类,因此建议它的成员变量不允许改变,这样会更安全,而且代码更加简洁。建议将枚举类的所有成员变量都使用private final修饰。

    3.2 枚举类中定义构造器

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

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

     1 enum Gender1
     2 {
     3     //此处的枚举值必须调用对应的构造器来创建
     4     MALE("男"),FEMALE("女");
     5     private final String name;
     6     private Gender1(String name)
     7     {
     8         this.name=name;
     9     }
    10     public String getName()
    11     {
    12         return this.name;
    13     }
    14 }
    15 public class Gender1Test
    16 {
    17     public static void main(String[] args)
    18     {
    19         Gender1 g=Enum.valueOf(Gender1.class,"MALE");
    20         System.out.println(g.getName());//
    21     }
    22 }
    View Code

    当Gender1枚举类型创建了一个Gender1(String name)构造器之后,在枚举类列出枚举值时,实际上就是调用构造器创建枚举类对象,只是这里无需使用 new关键字,也无需显示调用构造器。

    其实上面的列举出枚举类的所有枚举值等同于下面两行代码:

    1 public static final Genger1 MALE=new Gender1("男");
    2 public static final Genger1 FEMALE=new Gender1("女"); 

     四、实现接口中的枚举类

    枚举类我们知道都是继承java.lang.Enum类,而普通类都是继承java.lang.Object类,因此枚举类不能继承普通类。但是枚举类可以实现一个或多个接口,枚举类实现一个或多个接口时,必须实现该接口所包含的所有抽象方法。

    定义一个GenderDesc接口,该接口包含一个抽象方法。

    1 public interface GenderDesc
    2 {
    3     void info();
    4 }

    下面定义一个Gender2类实现GenderDesc接口中定义的info()方法:

     1 enum Gender2 implements GenderDesc//枚举类默认会使用public static final修饰
     2 {
     3     //此处的枚举值必须调用对应的构造器来创建
     4     MALE("男"),FEMALE("女");
     5     private final String name;
     6     private Gender2(String name)
     7     {
     8         this.name=name;
     9     }
    10     public String getName()
    11     {
    12         return this.name;
    13     }
    14     public void info()
    15     {
    16         System.out.println("这是一个定义性别的类");
    17     }
    18 }
    19 public class Gender2Test
    20 {
    21     public static void main(String[] args)
    22     {
    23         Gender2 g=Enum.valueOf(Gender2.class,"MALE");
    24         System.out.println(g.getName());//
    25         g.info();
    26 
    27         Gender2 g1=Gender2.FEMALE;
    28         System.out.println(g1.getName());//
    29         g1.info();
    30     }
    31 }
    32 33 这是一个定义性别的类
    34 35 这是一个定义性别的类
    36 请按任意键继续. . .
    View Code

    从上面代码可以看出,枚举类调用接口中的方法时都具有相同的行为方式。如果需要让每个枚举类调用该方法时具有不同的行为方式,则可以让每个枚举类分别实现该方法,每个枚举类提供不同的实现方法,从而让不同枚举类调用该方法时具有不同的行为方式:

     1 enum GenderPlus implements GenderDesc//枚举类默认会使用public static final修饰
     2 {
     3     //此处的枚举值必须调用对应的构造器来创建
     4     MALE("男")
     5         //花括号部分实际上是一个类体部分
     6     {
     7     public void info()
     8     {
     9         System.out.println("这个枚举类代表男性");
    10     }},
    11 
    12     FEMALE("女"){
    13     public void info()
    14     {
    15         System.out.println("这个枚举类代表女性");
    16     }};
    17 
    18     private final String name;
    19     private GenderPlus(String name)
    20     {
    21         this.name=name;
    22     }
    23     public String getName()
    24     {
    25         return this.name;
    26     }
    27 
    28 }
    29 public class GenderPlusTest
    30 {
    31     public static void main(String[] args)
    32     {
    33         GenderPlus g=Enum.valueOf(GenderPlus.class,"MALE");
    34         System.out.println(g.getName());//
    35         g.info();
    36 
    37         GenderPlus g1=GenderPlus.FEMALE;
    38         System.out.println(g1.getName());//
    39         g1.info();
    40     }
    41 }
    42 43 这个枚举类代表男性
    44 45 这个枚举类代表女性
    46 请按任意键继续. . .
    View Code

    上面程序中,在创建FEMALE和MALE两个枚举类时,后面又跟一个以堆花括号,这对花括号里就是info()方法定义。其实这就时匿名内部类的用法,花括好就是实现接口中的所有抽象方法的类体,在这种情况下,创建FEMALE、MALE实例时,并不是直接创建GenderPlus的实例,而是相当于创建GenderPlus的匿名子类的实例。

    注意:枚举类使用final修饰,即不能派生子类,为什么上面使用匿名内部类生成了子类?
    非抽象枚举类才使用final修饰,此时枚举类不能派生实例;对于一个抽象枚举类——只要它包含抽象方法,他就是抽象枚举类,系统默认使用abstarct修饰,从而失去了创建实例的功能,但获得了派生子类的功能。

    编译上面的程序我们可以看出生成了GenderPlus.class、GenderPlus$1.class(匿名内部类1)、GenderPlus$2.class(匿名内部类2).这就证明了上面的结论:MALE,FEMALE实际上是GenderPlus的匿名子类的实例,而不是GenderPlus的实例。当调用MALE,FEMALE两个枚举类的info()方法时,将会出现不同的行为方式。

    五、抽象枚举类

    假设有一个Operation的枚举类,它的4个枚举值PLUS,MINUS,TIMES,DIVIDE分别代表加减乘除4种运算,该枚举类需要定义一个eval()方法完成计算。显然不同的枚举类eval(0方法具有不同的行为方式:

     1 public enum Operation
     2 {
     3     Plus
     4     {
     5         public double eval(double x,double y)
     6         {
     7             return (x+y);
     8         }
     9     },
    10     MINUS
    11     {
    12         public double eval(double x,double y)
    13         {
    14             return (x-y);
    15         }
    16     },
    17     TIMES
    18     {
    19         public double eval(double x,double y)
    20         {
    21             return (x*y);
    22         }
    23     },
    24     DIVIDE
    25     {
    26         public double eval(double x,double y)
    27         {
    28             return (x/y);
    29         }
    30     };
    31     //为该枚举类定义一个抽象方法
    32     public abstract double eval(double x,double y);
    33     public static void main(String[] args)
    34     {
    35         System.out.println(Operation.Plus.eval(5,4));
    36         System.out.println(Operation.MINUS.eval(5,4));
    37         System.out.println(Operation.TIMES.eval(5,4));
    38         System.out.println(Operation.DIVIDE.eval(5,4));
    39     }
    40 }
    41 ---------- 运行Java捕获输出窗 ----------
    42 9.0
    43 1.0
    44 20.0
    45 1.25
    46 
    47 输出完成 (耗时 0 秒) - 正常终止
    View Code

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

    枚举类里定义抽象方法时不能使用abstract关键字将枚举类型定义为抽象类(因为系统会自动添加abstract关键字),而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。

  • 相关阅读:
    php中json_encode中文编码问题
    PLSQL Developer建表时注释(COMMENT)中文乱码的解决方案(Windows)
    JQuery实现 checkbox 全选、反选,子checkbox有没选去掉全选
    oracle group by 使用
    oracle distinct 去除重复,同时按某字段排序
    phpstorm 设置多项目并存
    putty修改编码
    Java基本开发环境搭建
    smarty 判断变量是否为空
    vim
  • 原文地址:https://www.cnblogs.com/weststar/p/12445969.html
Copyright © 2011-2022 走看看