zoukankan      html  css  js  c++  java
  • 17. Java学习之枚举

    enum 的全称为 enumeration, 是 JDK 1.5  中引入的新特性,存放在 java.lang 包中。它是一个看似很小的特性,它使得我们在需要群组并使用枚举类型集时,可以很方便地处理。(note:可选值固定在某个范围时使用)。

    一. 枚举的引入

    1. 未引入枚举之前

    假如有以下需求需要实现:

    需求1:定义一年中的四季和对应的季度

    需求2:打印当前所在的季节

    需求3:获取下一个季节

    需求4:得到各个季节所对应的季度

    (1)需求1实现

     1 package com.test.a;
     2 
     3 public class Season {
     4 
     5     public static final int SPRING = 1;
     6     public static final int SUMMER = 2;
     7     public static final int AUTUMN = 3;
     8     public static final int WINTER = 4;
     9 
    10 }
    View Code

             说明:如果想要引用上面的变量,直接类名.变量名就可以了,比如:Season.SPRING

    (2)需求2实现

     1 package com.test.a;
     2 
     3 public class Season {
     4 
     5     public static final Season SPRING = new Season();
     6     public static final Season SUMMER = new Season();
     7     public static final Season AUTUMN = new Season();
     8     public static final Season WINTER = new Season();
     9 
    10     private Season() {
    11 
    12     }
    13 
    14     public static void printSeason(Season seasonNow) {
    15         if (seasonNow == SPRING)
    16             System.out.println("Now is Spring");
    17         else if (seasonNow == SUMMER)
    18             System.out.println("Now is Summer");
    19         else if (seasonNow == AUTUMN)
    20             System.out.println("Now is Autumn");
    21         else
    22             System.out.println("Now is Winter");
    23     }
    24 
    25 }
    View Code

             说明:这次代码做了修改,引入了私有构造器,且创建了四个成员变量对象,代表了四个季节,这样有利于防止输入别的季节,且只能在该类中使用构造函数,外部类是不可以更改的。并且这个打印方法也做了实现,打印除了当前所在的季节。

    (3)需求3实现

     1 package com.test.a;
     2 
     3 public class Season {
     4 
     5     public static final Season SPRING = new Season();
     6     public static final Season SUMMER = new Season();
     7     public static final Season AUTUMN = new Season();
     8     public static final Season WINTER = new Season();
     9 
    10     private Season() {
    11 
    12     }
    13 
    14     public static void printSeason(Season seasonNow) {
    15         if (seasonNow == SPRING)
    16             System.out.println("Now is Spring");
    17         else if (seasonNow == SUMMER)
    18             System.out.println("Now is Summer");
    19         else if (seasonNow == AUTUMN)
    20             System.out.println("Now is Autumn");
    21         else
    22             System.out.println("Now is Winter");
    23     }
    24 
    25     public static Season getNextSeason(Season seasonNow) {
    26         if (seasonNow == SPRING)
    27             return SUMMER;
    28         else if (seasonNow == SUMMER)
    29             return AUTUMN;
    30         else if (seasonNow == AUTUMN)
    31             return WINTER;
    32         else
    33             return SPRING;
    34 
    35     }
    36 }
    View Code

    (4)需求4实现

     1 package com.test.a;
     2 
     3 public class Season {
     4 
     5     public static final Season SPRING = new Season();
     6     public static final Season SUMMER = new Season();
     7     public static final Season AUTUMN = new Season();
     8     public static final Season WINTER = new Season();
     9 
    10     private Season() {
    11 
    12     }
    13 
    14     public static void printSeason(Season seasonNow) {
    15         if (seasonNow == SPRING)
    16             System.out.println("Now is Spring");
    17         else if (seasonNow == SUMMER)
    18             System.out.println("Now is Summer");
    19         else if (seasonNow == AUTUMN)
    20             System.out.println("Now is Autumn");
    21         else
    22             System.out.println("Now is Winter");
    23     }
    24 
    25     public static Season getNextSeason(Season seasonNow) {
    26         if (seasonNow == SPRING)
    27             return SUMMER;
    28         else if (seasonNow == SUMMER)
    29             return AUTUMN;
    30         else if (seasonNow == AUTUMN)
    31             return WINTER;
    32         else
    33             return SPRING;
    34 
    35     }
    36 
    37     public static int getQuarter(Season seasonNow) {
    38         if (seasonNow == SPRING)
    39             return 1;
    40         else if (seasonNow == SUMMER)
    41             return 2;
    42         else if (seasonNow == AUTUMN)
    43             return 3;
    44         else
    45             return 4;
    46 
    47     }
    48 
    49 }
    View Code

     总结:上面是没有采取枚举前的实现,每当需求变更,就会写很多的代码来适应需求,写了很多的If else,当然还有别的写法。不能简洁的实现需求。

    2. 引入枚举

     1 package com.test.a;
     2 
     3 public enum Season {
     4     SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
     5     private int quater;
     6 
     7     private Season(int quater) {
     8         this.quater = quater;
     9     }
    10 
    11     public static Season getNextSeason(Season seasonNow) {
    12         int nextSeasonQuater;
    13         if (seasonNow.quater == 4) {
    14             nextSeasonQuater = 1;
    15         }
    16         nextSeasonQuater = seasonNow.quater + 1;
    17         return getSeasonByQuater(nextSeasonQuater);
    18     }
    19 
    20     // 得到季度对应的季节,采用枚举
    21     private static Season getSeasonByQuater(int quater) {
    22         for (Season season : Season.values()) {
    23             if (season.quater == quater) {
    24                 return season;
    25             }
    26         }
    27         return null;
    28     }
    29 
    30 }
    View Code

        说明:采用了枚举以后,if else减少,定义静态常量也非常简洁,并且我们可以直接调用values方法类获取对应的每个枚举实例值。整体能够支持的功能更加的多样化,且代码更加的简洁。枚举类也自定义了很多的final变量和方法,下面将会详细讲解的。

    3. 为何引入枚举

    根据上面的例子我们知道,在没有枚举类的时候,我们要定义一个有限的序列,春夏秋冬,一般会通过上面那种静态变量的形式,但是使用那样的形式如果需要一些其他的功能,就会出现很多风格不同的代码,且包含大量的if else。所以,枚举类的出现,就是为了简化这种操作。

     二. Enum的源码分析

    1. 类定义

    public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable

    说明:这是一个抽象类,因此不能通过new来创建它的对象

    翻阅thining in java,Enum类无法被继承,为什么呢?

    1 public enum Season<E> extends Enum<Enum<E>>{    
    2 }
    3 
    4 public class Season extends Enum<Enum<E>>{
    5 }
    6 
    7 public class Season extends Enum{
    8 }
    View Code

          说明:上面三种方式都试图来继承Enum,但是编译器都报错了。

    2. 反编译

     为了解释这种现象,通过javap命令来反编译Season类:

    说明:1)上面是反编译的结果,发现上面的Season枚举类实际上是一个java类,且是final类型的,这就说明了这个枚举类型是不可以被继承的。并且Season继承了Enum类,因为java是单继承的,因此就意味着Season枚举类不可以继承其它的类了。(既不能再继承别人,又不能拥有子类了)。这个动作必须由编译器来完成,直接手动写成继承Enum编译报错。

               2)其中定义的四个季节类型的枚举对象都被反编译成了public static final类型常量,说明可以直接访问它们。

               3)上面还发现了多了一个Private类型的构造器,说明枚举类型是不允许被实例化的,即我们不可以用new来创建对象。并且,这个构造器再内部实际上调用了父类Enum的Protected类型的构造器protected Enum(String name, int ordinal) 。

               倘若我们自定义了构造函数,编译器自动添加的构造函数也是不会冲突的,它将以有参构造函数的形式展示:private com.test.a.Season(int);

     1 package com.test.a;
     2 
     3 public enum Season {
     4     SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
     5     private int a;
     6 
     7     Season(int a) {
     8         this.a = a;
     9     }
    10 }
    View Code

         4)编译的时候还自动添加了static静态代码块,它用来初始化所有的枚举对象,并添加到自动生成的一个数组常量$VALUES中存储起来。

         5)编译时还会自动生成values()方法,它用来返回所有枚举对象的数组。

         6)编译时还会自动生成valueOf(String),它实际上调用了Enum.valueOf方法

          7)注意点:values方法是编译器生成的,在父类Enum中没有;valueOf方法也是编译器生成的,它只有一个参数,但是父类中的valueOf是两个参数,但是最终还是调用了Enum类的valueOf方法。对比举例:

     1 package com.test.a;
     2 
     3 import java.util.Arrays;
     4 
     5 public class Test {
     6     public static void main(String args[]) {
     7         Season seasons[] = Season.values();// Season类在编译时编译器默认提供了静态方法values。
     8         System.out.println(Arrays.toString(seasons));
     9         Season season = Season.valueOf("SPRING");
    10         System.out.println(season);
    11 
    12     }
    13 }
    14 
    15 
    16 [SPRING, SUMMER, AUTUMN, WINTER]
    17 SPRING
    View Code

            说明:values()方法的作用就是获取枚举类中的所有变量,并且作为数组返回;valueOf(String name)同父类方法,只是它更简便。倘若将枚举实例向上转型成Enum,这两个方法都不可以被调用。比如:

     1 package com.test.a;
     2 
     3 import java.util.Arrays;
     4 
     5 public class Test {
     6     public static void main(String args[]) {
     7         Enum enum1 = Season.SPRING;
     8         enum1.valueOf("SPRING");// 只支持两个参数调用,编译错误
     9         enum1.values();// 编译错误,没有这个values方法
    10 
    11     }
    12 }
    View Code
       总结枚举类型它是一种特殊的数据类型,是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也让枚举变得相对简单和安全。

     三. 基本用法

     1. 变量

        (1)name

    相关的源码实现:

    1 //对应的源码方法
    2  public final String name() {
    3         return name;
    4    }
    View Code
    1 public String toString() {
    2         return name;
    3     }
    View Code

         说明:name()方法和toString()方法是一样的,唯一的区别是,你可以重写toString方法。name变量就是枚举变量的字符串形式。举例:

     1 package com.test.a;
     2 
     3 public class Test {
     4     public static void main(String args[]) {
     5         Season season = Season.SPRING;
     6         String name = season.name();
     7         String name2 = season.toString();
     8         System.out.println(name);
     9         System.out.println(name2);
    10 
    11     }
    12 }
    View Code

    输出:

    SPRING

    SPRING

        (2)ordinal

    1  //对应的源码方法
    2 public final int ordinal() {
    3         return ordinal;
    4     }
    View Code

         说明:默认情况下,枚举类会给所有的枚举变量一个默认的次序,该次序从0开始,用ordinal变量表示。比如:

     1 package com.test.a;
     2 
     3 public class Test {
     4     public static void main(String args[]) {
     5         Season season = Season.SPRING;
     6         int ordinal = season.ordinal();
     7         System.out.println(ordinal);
     8 
     9     }
    10 }
    11 
    12 输出:
    13 0
    View Code

     2. 方法

     就介绍一些上面没有提到过的:

    (1)compareTo

           实际上就是比较的oridinal,如果为负,就代表前者小于后者。例如:

     1 package com.test.a;
     2 
     3 public class Test {
     4     public static void main(String args[]) {
     5         int i = Season.SPRING.compareTo(Season.WINTER);
     6         System.out.println(i);
     7 
     8     }
     9 }
    10 
    11 -3
    View Code

    (2)getDeclaringClass

           说明:该方法是获取枚举实例变量的方法。针对上面向上转型以后是不可以调用values方法和valueOf方法的,就可以通过这个getDeclaringClass和反射来获取枚举实例。

           在Class类中,有以下两个方法和枚举有关:

    返回类型方法名称方法说明
    T[] getEnumConstants() 返回该枚举类型的所有元素,如果Class对象不是枚举类型,则返回null。
    boolean isEnum() 当且仅当该类声明为enum类型时返回true

           简单例子:

     1 package com.test.a;
     2 
     3 import java.util.Arrays;
     4 
     5 public enum Season {
     6     SPRING, SUMMER, AUTUMN, WINTER;
     7     public static void main(String args[]) {
     8         System.out.println(Season.SPRING.getDeclaringClass());
     9         System.out.println(Season.class.isEnum());
    10         Season seasons[] = Season.class.getEnumConstants();
    11         System.out.println(Arrays.toString(seasons));
    12     }
    13 }
    14 
    15 
    16 class com.test.a.Season
    17 true
    18 [SPRING, SUMMER, AUTUMN, WINTER]
    View Code

    (3)getClass   和 getDeclaringClass的区别

            Note:上面例子中的getClass可以提到getDeclaringClass。会得到相同的结果。但是并不是总是能够得到相同的结果。比如以下情况就不可以:

     1 package com.test.a;
     2 
     3 public enum Season {
     4 
     5     SPRING {
     6         int getOrdinal() {
     7             return 0;
     8         }
     9     },
    10     SUMMER {
    11         int getOrdinal() {
    12             return 1;
    13         }
    14     };
    15 
    16     public static void main(String args[]) {
    17         System.out.println(Season.SPRING.getClass());
    18         System.out.println(Season.SPRING.getDeclaringClass());
    19     }
    20 
    21     abstract int getOrdinal();
    22 
    23 }
    24 
    25 class com.test.a.Season$1
    26 class com.test.a.Season
    View Code

            说明:上面的打印结果就不相同。SPRING和SUMMER相当于Season的内部类,并研究getDeclaringClass源码如下:

    1 @SuppressWarnings("unchecked")
    2     public final Class<E> getDeclaringClass() {
    3         Class<?> clazz = getClass();
    4         Class<?> zuper = clazz.getSuperclass();
    5         return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    6     }
    View Code

           根据源码逻辑,我们知道父类是不是Enum,决定了最终返回的是何种类型,分类更加的清晰。因此,判断枚举类型时,使用getDeclaringClass更有保障。

    四. 高级用法

    1. 向enum类中添加变量和方法

           enum其实和常规类的用法差不多,可以添加变量,方法,甚至是main方法。

     1 package com.test.a;
     2 
     3 public enum Season {
     4 
     5     SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");
     6 
     7     private String name;
     8 
     9     private Season(String name) {
    10         this.name = name;
    11     }
    12 
    13     public String getName() {
    14         return name;
    15     }
    16 
    17     public static void main(String[] args) {
    18         for (Season day : Season.values()) {
    19             System.out.println("name:" + day.name() + ",name:" + day.getName());
    20         }
    21     }
    22 
    23 }
    View Code
    1 name:SPRING,name:春天
    2 name:SUMMER,name:夏天
    3 name:AUTUMN,name:秋天
    4 name:WINTER,name:冬天
    View Code

          说明:根据上面代码示例可以知道,enum和常规类用法差不多。但是enum中方法或者变量的定义必须要在enum实例对象定义之后,否则编译器会报错;不能手动创建enum类的对象,因为这必须且只能编译器自己做。

    2. 复写父类中的方法

        只支持toString方法的重写

    3. enum中定义抽象方法

        在enum中可以定义抽象方法,并且在每个枚举实例中重写这个抽象方法,这样就可以让不同的枚举实例拥有不同的行为。比如:

     1 package com.test.a;
     2 
     3 public enum Season {
     4     SPRING {
     5 
     6         @Override
     7         public void f() {
     8             System.out.println("this is spring");
     9 
    10         }
    11 
    12     },
    13     WINTER {
    14         @Override
    15         public void f() {
    16             System.out.println("this is winter");
    17 
    18         }
    19     };
    20 
    21     public static void main(String[] args) {
    22         Season.SPRING.f();
    23         Season.WINTER.f();
    24     }
    25 
    26     public abstract void f();
    27 
    28 }
    View Code
    1 this is spring
    2 this is winter
    View Code

       说明:enum类的实例似乎表现出了多态的特性,但是枚举类型的实例终究不能作为类型传递使用,一旦使用,编译器就会报错,比如

     public void text(Season.SPRING instance){ }  //编译错误,因为SEASON.SPRING是个实例对象
    4. enum可以实现多个接口
    1 public interface Base {    
    2     public void f();
    3 }
    4 public interface Base2 {
    5     public void g();
    6 
    7 }
    View Code
     1 package com.test.a;
     2 
     3 public enum Season implements Base, Base2 {
     4     SPRING, WINTER;
     5 
     6     public static void main(String[] args) {
     7         Season.SPRING.f();
     8         Season.SPRING.g();
     9     }
    10 
    11     @Override
    12     public void g() {
    13         System.out.println("ggggg");
    14 
    15     }
    16 
    17     @Override
    18     public void f() {
    19         System.out.println("fffffff");
    20     }
    21 
    22 }
    23 
    24 fffffff
    25 ggggg
    View Code

    5. switch中可以支持枚举

    六. EnumMap

    EnumMap是专门针对枚举类型而创建的类。它的键依照ordinal的值作为put顺序。简单例子:

     1 public enum Season{
     2     WINTER,AUTUMN,SPRING;
     3 }
     4 
     5 public class Test {
     6     public static void main(String args[]) {
     7         EnumMap enumMap=new EnumMap(Season.class);
     8         enumMap.put(Season.AUTUMN, "秋天");
     9         enumMap.put(Season.WINTER, "冬天");
    10         enumMap.put(Season.SPRING, "春天");
    11         System.out.println(enumMap);
    12     }
    13 }
    14 
    15 {WINTER=冬天, AUTUMN=秋天, SPRING=春天}
    View Code

       说明:根据输出结果可以知道和enum类定义中的ordinal顺序一致。

        知识点:EnumMap的基本实现原理,内部有两个数组,长度相同,一个表示所有可能的键,一个表示对应的值,值为null表示没有该键值对,键都有一个对应的索引,根据索引可直接访问和操作其键和值,效率很高。(知识点摘自:https://www.cnblogs.com/swiftma/p/6044672.html)

    note:更多关于enumMap和EnumSet的用法后面再补充。还有关于枚举单例模式等,后面补充。

  • 相关阅读:
    12.14 Daily Scrum
    12.13 Daily Scrum
    12.12 Daily Scrum
    12.11 Daily Scrum
    12.10 Daily Scrum
    12.9 Daily Scrum
    12.8 Daily Scrum
    M1事后分析汇报以及总结
    alpa开发阶段团队贡献分
    第9周团队作业
  • 原文地址:https://www.cnblogs.com/Hermioner/p/9747063.html
Copyright © 2011-2022 走看看