zoukankan      html  css  js  c++  java
  • Java中的Enum的使用与分析

    示例:

    package com.dxz.enumtest;
    
    public enum EnumTest {
        DUANXZ("The given name of me"), DXZ("The family name of me");
        private String context;
    
        private String getContext() {
            return this.context;
        }
    
        private EnumTest(String context) {
            this.context = context;
        }
    
        public static void main(String[] args) {
            for (EnumTest name : EnumTest.values()) {
                System.out.println(name + " : " + name.getContext());
            }
            System.out.println(EnumTest.DUANXZ.getDeclaringClass());
        }
    }

     Java中枚举实现的分析:

    示例: 

    package com.dxz.enumtest;
    
    public enum Color {
        RED,BLUE,BLACK,YELLOW,GREEN
    }

    显然,enum很像特殊的class,实际上enum声明定义的类型就是一个类。 而这些类都是类库中Enum类的子类(java.lang.Enum<E>)。它们继承了这个Enum中的许多有用的方法。我们对代码编译之后发现,编译器将enum类型单独编译成了一个字节码文件:Color.class。

    Color字节码代码 
    final enum com.dxz.enumtest.Color {  
        
     // 所有的枚举值都是类静态常量  
     public static final enum hr.test.Color RED;  
     public static final enum hr.test.Color BLUE;  
     public static final enum hr.test.Color BLACK;  
     public static final enum hr.test.Color YELLOW;  
     public static final enum hr.test.Color GREEN;  
       
    private static final synthetic com.dxz.enumtest.Color[] ENUM$VALUES;  
        
      // 初始化过程,对枚举类的所有枚举值对象进行第一次初始化  
     static {  
           0  new hr.test.Color [1]   
          3  dup  
          4  ldc <String "RED"> [16] //把枚举值字符串"RED"压入操作数栈  
          6  iconst_0  // 把整型值0压入操作数栈  
          7  invokespecial hr.test.Color(java.lang.String, int) [17] //调用Color类的私有构造器创建Color对象RED  
         10  putstatic hr.test.Color.RED : hr.test.Color [21]  //将枚举对象赋给Color的静态常量RED。  
          .........  枚举对象BLUE等与上同  
        102  return  
    };  
        
      // 私有构造器,外部不可能动态创建一个枚举类对象(也就是不可能动态创建一个枚举值)。  
     private Color(java.lang.String arg0, int arg1){  
         // 调用父类Enum的受保护构造器创建一个枚举对象  
         3  invokespecial java.lang.Enum(java.lang.String, int) [38]  
    };  
       
     public static hr.test.Color[] values();  
        
       // 实现Enum类的抽象方法    
      public static com.dxz.enumtest.Color valueOf(java.lang.String arg0);  
    } 

    下面我们就详细介绍enum定义的枚举类的特征及其用法。(后面均用Color举例)

    1、Color枚举类就是class,而且是一个不可以被继承的final类。其枚举值(RED,BLUE...)都是Color类型的类静态常量, 我们可以通过下面的方式来得到Color枚举类的一个实例:

    Color c=Color.RED; 

    注意:这些枚举值都是public static final的,也就是我们经常所定义的常量方式,因此枚举类中的枚举值最好全部大写。 

    2、即然枚举类是class,当然在枚举类型中有构造器,方法和数据域。但是,枚举类的构造器有很大的不同: 
          (1) 构造器只是在构造枚举值的时候被调用。

    Java代码 

      

    enum Color{  
                    RED(255,0,0),BLUE(0,0,255),BLACK(0,0,0),YELLOW(255,255,0),GREEN(0,255,0);  
                    //构造枚举值,比如RED(255,0,0)  
                    private Color(int rv,int gv,int bv){  
                     this.redValue=rv;  
                     this.greenValue=gv;  
                     this.blueValue=bv;  
                    }  
      
                    public String toString(){  //覆盖了父类Enum的toString()  
                    return super.toString()+"("+redValue+","+greenValue+","+blueValue+")";  
                    }  
         
                    private int redValue;  //自定义数据域,private为了封装。  
                    private int greenValue;  
                    private int blueValue;  
     }

          (2) 构造器只能私有private,绝对不允许有public构造器。 这样可以保证外部代码无法新构造枚举类的实例。这也是完全符合情理的,因为我们知道枚举值是public static final的常量而已。 但枚举类的方法和数据域可以允许外部访问。

    Java代码 
    public static void main(String args[])  
    {  
            // Color colors=new Color(100,200,300);  //wrong  
               Color color=Color.RED;  
               System.out.println(color);  // 调用了toString()方法  
    } 

    3、所有枚举类都继承了Enum的方法,下面我们详细介绍这些方法。 
           (1)  ordinal()方法: 返回枚举值在枚举类种的顺序。这个顺序根据枚举值声明的顺序而定。

    Color.RED.ordinal();  //返回结果:0
    Color.BLUE.ordinal();  //返回结果:1

           (2)  compareTo()方法: Enum实现了java.lang.Comparable接口,因此可以比较象与指定对象的顺序。Enum中的compareTo返回的是两个枚举值的顺序之差。当然,前提是两个枚举值必须属于同一个枚举类,否则会抛出ClassCastException()异常。(具体可见源代码)

    Color.RED.compareTo(Color.BLUE);  //返回结果 -1

           (3)  values()方法: 静态方法,返回一个包含全部枚举值的数组。

    Color[] colors=Color.values();
    for(Color c:colors){
          System.out.print(c+","); 
    }//返回结果:RED,BLUE,BLACK YELLOW,GREEN,

           (4)  toString()方法: 返回枚举常量的名称。

    Color c=Color.RED;
    System.out.println(c);//返回结果: RED

           (5)  valueOf()方法: 这个方法和toString方法是相对应的,返回带指定名称的指定枚举类型的枚举常量。

    Color.valueOf("BLUE");   //返回结果: Color.BLUE

           (6)  equals()方法: 比较两个枚举类对象的引用。

    //JDK源代码:      
    public final boolean equals(Object other) {  
            return this==other;  
    } 

    4、枚举类可以在switch语句中使用。

    Color color=Color.RED;  
    switch(color){  
            case RED: System.out.println("it's red");break;  
            case BLUE: System.out.println("it's blue");break;  
            case BLACK: System.out.println("it's blue");break;  
    }

     5、EnumUtil根据值获取枚举对象

    项目中使用枚举类的好处这里不再赘述,在使用枚举值时,通常需要根据值来获取枚举对象,下面介绍两种实现方案:

    1.在枚举类中定义方法实现

      首先给出如下性别枚举类:  

    public enum SexEnum {
      MAN("M", "男"),
      WOMAN("F", "女");
    
      private String code;
      private String desc;
    
      SexEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
      }
    
      public String getCode() {
        return code;
      }
    
      public void setCode(String code) {
        this.code = code;
      }
    
      public String getDesc() {
        return desc;
      }
    
      public void setDesc(String desc) {
        this.desc = desc;
      }
    
    }

      现在需要根据code的值获取枚举对象,简单直接的办法是在该枚举类中定义如下方法:

    public static SexEnum getSexEnumByCode(String code){
        for(SexEnum sexEnum : SexEnum.values()){
          if(StringUtils.equals(code, sexEnum.getCode())){
            return sexEnum;
          }
        }
        return null;
      }

      以这种方案实现时,需要在每个枚举类中都定义类似上述结构的方法。当项目中的枚举类较多时,显得代码冗余。

    2.利用反射实现

      首先介绍本方案的实现方式,再来介绍具体代码实现:

      1).定义一个EnumMessage接口,然后每个枚举类实现此接口;

      2).定义常量保存枚举类所在包名,以及接口全路径;

      3).在程序启动时,读取枚举类所在包下的所有枚举类的File文件,在从file文件信息中获取每个枚举类的全路径类名集合A;

      4).遍历A集合,利用反射获取每个类的class对象,再判断该类是否实现了EnumMessage接口;

      5).对于实现了EnumMessage接口的枚举类,遍历该枚举类的所有对象,保存Map<Object, EnumMessage>的集合映射;

      6).对枚举类保存Map<Class, Map<Object, EnumMessage>>的映射集合。

      至此完成了启动的初始化工作。下面给出上述过程的代码实现:

      定义接口EnumMessage:

    package com.example.myFirstProject.service;
    
    public interface EnumMessage {
        Object getValue();
    }

      枚举类SexEnum实现此接口: 

    package com.example.myFirstProject.enums;
    
    import com.example.myFirstProject.service.EnumMessage;
    import org.apache.commons.lang3.StringUtils;
    
    public enum SexEnum implements EnumMessage {
      MAN("M", "男"),
      WOMAN("F", "女");
    
      private String code;
      private String desc;
    
      SexEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
      }
    
      public String getCode() {
        return code;
      }
    
      public void setCode(String code) {
        this.code = code;
      }
    
      public String getDesc() {
        return desc;
      }
    
      public void setDesc(String desc) {
        this.desc = desc;
      }
    
      @Override
      public Object getValue() {
        //此处需要根据枚举对象的哪个属性返回枚举对象,就return该属性
        return code;
      }
    
    }

      Constant类定义了常量保存枚举类所在包名和接口全路径,以及Map的初始化工作:

    package com.example.myFirstProject.common;
    
    import com.example.myFirstProject.service.EnumMessage;
    import com.example.myFirstProject.util.PackageUtil;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class Constant {
    
      /**
       * 枚举类包名集合
       */
      public static List<String> pathList = initPackagePathList();
      /**
       * 枚举接口类全路径
       */
      public final static String ENUM_MESSAGE_PATH = "com.example.myFirstProject.service.EnumMessage";
    
      /**
       * 枚举类对应的全路径集合
       */
      public static final List<String> ENUM_OBJECT_PATH = PackageUtil.getPackageClasses(pathList, true);
    
      /**
       * 存放单个枚举对象 map常量定义
       */
      private static Map<Object, EnumMessage> SINGLE_ENUM_MAP = null;
    
      /**
       * 所有枚举对象的 map
       */
      public static final Map<Class, Map<Object, EnumMessage>> ENUM_MAP = initialEnumMap(true);
    
      private static List<String> initPackagePathList() {
        List<String> list = new ArrayList<>();
        list.add("com.example.myFirstProject.enums");
        return list;
      }
    
      static {
        System.out.println("类被加载时,先初始化各个静态变量,再执行static块。" +
                "所以不能在这里执行pathList的add操作("com.example.myFirstProject.enums")。");
      }
      /**
       * 加载所有枚举对象数据
       *
       * @param isFouceCheck 是否强制校验枚举是否实现了EnumMessage接口,若为false则没有实现接口的枚举类也会被加载
       */
      private static Map<Class, Map<Object, EnumMessage>> initialEnumMap(boolean isFouceCheck) {
        Map<Class, Map<Object, EnumMessage>> ENUM_MAP = new HashMap<>();
        try {
          for (String classname : ENUM_OBJECT_PATH) {
            Class<?> cls = null;
            cls = Class.forName(classname);
            Class<?>[] iter = cls.getInterfaces();
            boolean flag = false;
            if (isFouceCheck) {
              for (Class cz : iter) {
                if (cz.getName().equals(ENUM_MESSAGE_PATH)) {
                  flag = true;
                  break;
                }
              }
            }
            if (flag == isFouceCheck) {
              SINGLE_ENUM_MAP = new HashMap<>();
              initialSingleEnumMap(cls);
              ENUM_MAP.put(cls, SINGLE_ENUM_MAP);
            }
    
          }
        } catch (Exception e) {
    
        }
        return ENUM_MAP;
      }
    
      /**
       * 加载每个枚举对象数据
       */
      private static void initialSingleEnumMap(Class<?> cls) throws Exception {
        Method method = cls.getMethod("values");
        EnumMessage inter[] = (EnumMessage[]) method.invoke(null, null);
        for (EnumMessage enumMessage : inter) {
          SINGLE_ENUM_MAP.put(enumMessage.getValue(), enumMessage);
        }
      }
    
    
    }

      PackageUtil工具类主要完成根据枚举类所在包名获取该package下所有class的全路径名称的工作:

    package com.example.myFirstProject.util;
    
    import java.io.File;
    import java.util.ArrayList;
    import java.util.List;
    
    public class PackageUtil {
    
      /**
       * 返回包下所有的类
       *
       * @param packagePathList   包名全路径集合
       * @param classWithPath 返回全路径开关 true 自动带上包名 false 只返回类名
       * @return List<String> 包下所有的类
       */
      public static List<String> getPackageClasses(List<String> packagePathList, boolean classWithPath) {
        List<String> result = new ArrayList<>();
        for(String packagePath : packagePathList) {
          List<String> classNames = getClassName(packagePath);
          String path = classWithPath ? packagePath + "." : "";
          for (String className : classNames) {
            //className:com.example.myFirstProject.enums.SexEnum
            result.add(path + className.substring(className.lastIndexOf(".") + 1));
          }
        }
        return result;
      }
    
      /**
       * 获取该报名全路径下的所有class全路径集合
       * @param packageName 包名全路径
       * @return
       */
      private static List<String> getClassName(String packageName) {
        //根据报名获取该package的系统路径
        String filePath = ClassLoader.getSystemResource("").getPath() + packageName.replace(".", "\");
        // filePath: /D:/workspace-git/springbootlearning/target/classes/comexamplemyFirstProjectenums
        List<String> fileNames = getClassName(filePath, null);
        return fileNames;
      }
    
      /**
       * 获取filePath文件夹下的所有class的全路径集合
       * @param filePath
       * @param className
       * @return
       */
      private static List<String> getClassName(String filePath, List<String> className) {
        List<String> myClassName = new ArrayList<>();
        File file = new File(filePath);
        File[] childFiles = file.listFiles();
        for (File childFile : childFiles) {
          if (childFile.isDirectory()) {
            //递归获取该文件夹下的子文件夹里的所有文件
            myClassName.addAll(getClassName(childFile.getPath(), myClassName));
          } else {
            String childFilePath = childFile.getPath();
            //childFilePath:  D:workspace-gitspringbootlearning	argetclassescomexamplemyFirstProjectenumsSexEnum.class
            childFilePath = childFilePath.substring(childFilePath.indexOf("\classes") + 9, childFilePath.lastIndexOf("."));
            childFilePath = childFilePath.replace("\", ".");
            myClassName.add(childFilePath);
          }
        }
    
        return myClassName;
      }
    
    }

      定义EnumUtil,提供根据值获取枚举对象的入口方法: 

    package com.example.myFirstProject.util;
    
    import com.example.myFirstProject.common.Constant;
    import com.example.myFirstProject.service.EnumMessage;
    
    public class EnumUtil {
    
        /**
         * 获取value返回枚举对象
         * @param value
         * @param clazz
         * */
        public static <T extends EnumMessage>  T getEnumObject(Object value, Class<T> clazz){
            return (T) Constant.ENUM_MAP.get(clazz).get(value);
        }
        
    }

      最后编写测试语句:

      System.out.println(EnumUtil.getEnumObject("M", SexEnum.class));  //MAN

      至此该方案实现了根据枚举对象的值"M"获取枚举类对象"MAN"。

      注意:关于static变量的加载时机:

      当在EnumUtil中调用Constant的静态变量ENUM_MAP时,Constant类被加载,Conatant类中的pathList,ENUM_OBJECT_PATH,ENUM_MAP被按顺序加载,即先执行了Conatant的initPackagePathList()方法,再执行了PackageUtil的getPackageClasses(pathList, true)方法

    最后在 public static final Map<Class, Map<Object, EnumMessage>> ENUM_MAP = initialEnumMap(true)被调用时,ENUM_OBJECT_PATH已经有值。

      附:类被加载的时机:  

      1、用Class.forName()显示加载的时候;

      2、实例化一个类的时候;

      3、调用类的静态方法的时候;

      4、调用类的静态变量的时候;

  • 相关阅读:
    KnockoutJS 3.X API 第五章 高级应用(4) 自定义处理逻辑
    KnockoutJS 3.X API 第五章 高级应用(3) 虚拟元素绑定
    KnockoutJS 3.X API 第五章 高级应用(2) 控制后代绑定
    KnockoutJS 3.X API 第五章 高级应用(1) 创建自定义绑定
    KnockoutJS 3.X API 第四章(14) 绑定语法细节
    KnockoutJS 3.X API 第四章(13) template绑定
    KnockoutJS 3.X API 第四章 表单绑定(12) selectedOptions、uniqueName绑定
    KnockoutJS 3.X API 第四章 表单绑定(11) options绑定
    KnockoutJS 3.X API 第四章 表单绑定(10) textInput、hasFocus、checked绑定
    KnockoutJS 3.X API 第四章 表单绑定(9) value绑定
  • 原文地址:https://www.cnblogs.com/duanxz/p/3753267.html
Copyright © 2011-2022 走看看