• 枚举让盗版美国总统wcc给你整明白哈哈


    1.为什么要有枚举

    Java中的枚举其实是一种语法糖,在 JDK 1.5之后出现,用来表示固定且有限个的对象。比如一个季节类有春、夏、秋、冬四个对象;一个星期有星期一到星期日七个对象。这些明显都是固定的,且有限个。那jdk5之前如果想表示有限个对象,代码是怎么写的嘞?这样写有什么缺点,最终让jdk官方人员(大部分是美国人)看不下去了呐,我这里给大家举一个2020火热的事件,来体会,话不多说,我wcc直接上代码:

    package com.huawei.subtitle.portal.com;
    
    /*
      为什么需要Enum
     */
    public class AmericanFamily {
    
        public static final String bigSon = "特朗普";
        public static final String smallSon = "拜登";
        public static final String daughter = "奥巴马";
        //public static final String daughter = "罗斯福";
    
        public static void main(String[] args) {
            String person = "拜登";
            ourPresident(person);
            System.out.println("--------------------");
            String chineseBoyWccNickName = "特朗普";
            ourPresident(chineseBoyWccNickName);
            System.out.println("你看到了,我是中国小子wcc,可不是AmericanFamily成员");
        }
    
        public static void ourPresident(String person) {
            if (AmericanFamily.bigSon.equals(person)) {
                System.out.println(" our president 川建国,2020 我挺你");
            }
            if (AmericanFamily.smallSon.equals(person)) {
                System.out.println(" our president 拜登");
            }
            if (AmericanFamily.daughter.equals(person)) {
                System.out.println(" our president 奥巴马");
            }
    
             /* 
             如果上面定义了常量罗斯福,这里就要增加个if'判断',也就是就要改代码哈 
             if (AmericanFamily.daughter.equals(person)) {
                System.out.println(" our president 奥巴马");
            }
            */
        }
    }
    


    大家看到了吧,结果竟然是中国小子wcc因为昵称叫特朗普,当美国人将我进行校验的时候,得到的结果是这是美国总统特朗普哈哈。于是jdk官网人员开始反思,我目前主要是看出了2点:

    • 美国人肯定不干呀最后,jdk官方这些人更是看不下去,这验证方式不对呀,这家伙都不会美国人,这也通过了校验。这不行,我得把类型也得校验住了,最好在编译期就提示这个有问题,冒牌货
    • 这个代码写的不好呀,随着时间的更替,美国总统族谱上的人越来越多,每次增加了常量,ourPresident()这个方法就要增加判断逻辑,这代码水呀,我可是jdk官方,这得改。

    为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类,于是终于在jdk1.5版本,Enum枚举出来了,接下来我们看下怎么使用,以及它的原理。

    2.Enum定义和常用方法

    一般的定义形式:
    在定义枚举类型时我们使用的关键字是enum,与class关键字类似,enum 枚举名{ 枚举值表 }; 在枚举值表中应罗列出所有可用值。这些值也称为枚举元素。例如:

    enum Color { 
        RED, GREEN, BLUE; 
    } 
    

    枚举类型Color中定义了颜色的值,这里要注意,值一般是大写的字母,多个值之间以逗号分隔。同时我们应该知道的是枚举类型可以像类(class)类型一样,定义为一个单独的文件,当然也可以定义在其他类内部,更重要的是枚举常量在类型安全性和便捷性都很有保证,如果出现类型问题编译器也会提示我们改进,但务必记住枚举表示的类型其取值是必须有限的,也就是说每个值都是可以枚举出来的,比如上述描述的一周共有七天。
    使用枚举直接用枚举名.枚举值即可,例如Color.RED

    enum Color {
        RED, GREEN, BLUE;
    }
    
    public class Test {
        // 执行输出结果
        public static void main(String[] args) {
            Color c1 = Color.RED;
            System.out.println(c1);
        }
    }
    

    2.1Enum类常见的方法:

    我们使用enum关键字(注意是小写的),创建对应的枚举类时,默认是继承了Enum类的,而Enum类里自带了一些方法并且进行了实现,大家可以看下Enum源码。

    (1)  ordinal()方法: 返回枚举值在枚举类种的顺序。这个顺序根据枚举值声明的顺序而定。(顺序是以0开始的,即0,1,2......)
              Color.RED.ordinal();  //返回结果:0
              Color.BLUE.ordinal();  //返回结果:2
    (2)  compareTo()方法: Enum实现了java.lang.Comparable接口,因此可以比较对象与指定对象的顺序。Enum中的compareTo返回的是两个枚举值的顺序之差。当然,前提是两个枚举值必须属于同一个枚举类,否则会抛出ClassCastException()异常。(具体可见源代码)
              Color.RED.compareTo(Color.BLUE);  //返回结果 -2,即(0-2)
    (3)  values()方法: 静态方法,返回一个包含全部枚举值的数组。
              Color[] colors=Color.values();
              for(Color c:colors){
                 System.out.print(c+","); 
              }//返回结果:RED,GREEN,BLUE
    (4)  toString()方法: 返回枚举常量的名称。和.name一样的效果
              Color c=Color.RED;
              System.out.println(c);//返回结果: RED
    (5)  valueOf()方法: 这个方法和toString方法是相对应的,返回带指定名称的指定枚举类型的枚举常量。
              Color.valueOf("BLUE");   //返回结果: Color.BLUE
    (6)  equals()方法: 比较两个枚举类对象的引用是否相同,每一个枚举值其实都是一个类的实例,这个接下来我会讲下。
    

    因为这些api比较简单,就不写例子一一举例了,我们把美国总统这个例子改写下,让大家体会下使用枚举后有么有将我们第一部分的问题给解决:

    public enum AmericanFamily {
    
        teRuPu,baiDeng,obama,wccHaHa;
    
        public static void ourPresident(AmericanFamily person) {
            //大家看这块代码是不是以后都不用管改了哈
            for (AmericanFamily human:AmericanFamily.values()){
                if (human.equals(person)){
                    System.out.println("this is our president" +" "+ person);
                }
            }
        }
    
        public static void enumMethodTest(){
            AmericanFamily teRuPu = AmericanFamily.teRuPu;
            int ordinal = teRuPu.ordinal();
            int i = teRuPu.compareTo(AmericanFamily.wccHaHa);
            boolean same = teRuPu.equals(AmericanFamily.wccHaHa);
            System.out.println(teRuPu);
            System.out.println(ordinal);
            System.out.println(i);
            System.out.println(same);
        }
    
        public static void main(String[] args) {
            AmericanFamily person = AmericanFamily.teRuPu;
            ourPresident(person);
            System.out.println("--------------------");
            String chineseBoyWccNickName = "特朗普";
            System.out.println("你看到了,我是中国小子wcc,可不是AmericanFamily成员,强行使用,我异常了");
            // ourPresident(chineseBoyWccNickName);编译器检查就通不过了
            // 不兼容的类型: java.lang.String无法转换为com.huawei.subtitle.portal.com.AmericanFamily
            ourPresident(AmericanFamily.wccHaHa);
            System.out.println("--------------------");
            enumMethodTest();
        }
    }
    

    3.枚举的原理

    我们来看下,我们简单的创建一个枚举类,Java底层为我们做了什么,为了搞反编译,试了很多,因为Java的发展,很多反编译软件可能不支持Java的新的语法特性,或者支持不友好:
    这里仅附上我使用的反编译工具下载地址:jad反编译下载

    
    **直接使用jad命令反编译,具体如下:**
    - 1. 打开命令,将当前位置指向xjad所在目录
    - 2. 使用Jad -sjava xxx.class
    即可在同目录下生成xxx.java的反编译后的文件
    


    public enum Day {
        MONDAY, TUESDAY, WEDNESDAY,THURSDAY, FRIDAY, SATURDAY, SUNDAY;
    }
    

    这里直接展示反编译的结果,具体使用不同工具反编译的差别,请查看我另一篇文章--暂时起名反编译工具使用吧嘻嘻。话不多说,附上反编译后代码,从反编译代码中看这个类的真实样子:

    // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
    // Jad home page: http://www.kpdus.com/jad.html
    // Decompiler options: packimports(3) 
    // Source File Name:   Day.java
    
    package com.huawei.subtitle.portal;
    
    
    public final class Day extends Enum {
    
        public static Day[] values() {
            return (Day[])$VALUES.clone();
        }
    
        public static Day valueOf(String s) {
            return (Day)Enum.valueOf(Day, s);
        }
    
        private Day(String s, int i) {
            super(s, i);
        }
    
        public static final Day MONDAY;
        public static final Day TUESDAY;
        public static final Day WEDNESDAY;
        public static final Day THURSDAY;
        public static final Day FRIDAY;
        public static final Day SATURDAY;
        public static final Day SUNDAY;
        private static final Day $VALUES[];
    
        static {
            MONDAY = new Day("MONDAY", 0);
            TUESDAY = new Day("TUESDAY", 1);
            WEDNESDAY = new Day("WEDNESDAY", 2);
            THURSDAY = new Day("THURSDAY", 3);
            FRIDAY = new Day("FRIDAY", 4);
            SATURDAY = new Day("SATURDAY", 5);
            SUNDAY = new Day("SUNDAY", 6);
            $VALUES = (new Day[] {
                MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
            });
        }
    }
    

    首先我们从第一行

    • 可以知道enum定义的枚举本身也是一个class,和我们常见的类使用上没有差别
    • 这个类默认继承了Enum接口,由于Java的单继承,所以enum不能再继承别的类,同时由于继承,也就有了Enum接口中的已经实现的方法。
    • 这个类被final修饰,也就是太监类,没有子类,不允许继承

    接下来我们看成员变量和static代码块这块:

    • static静态代码块的特性大家应该都知道哈,这里会随着类的加载使用而执行,且仅执行一次,为我们创建了Day这个对象的实例,共计7个,每一个枚举值都是一个对象的实例,这点要时刻记住,后面会用到
    • 通过私有构造器,并调用父构造器初始化name和ordinal属性,name值默认就是我们定义的枚举名称的字符串形式,如"Monday"
    • VALUS常量是一个Day类型数组,也是在静态代码块中进行的初始化,数组中的值为对应的枚举类对象

    到此我们也就明白了,使用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在的类,而在该类中,会存在每个在枚举类型中定义好变量的对应实例对象,如上述的MONDAY枚举类型对应public static final Day MONDAY;,同时编译器会为该类创建两个方法,分别是values()和valueOf()。ok~,到此相信我们对枚举的实现原理也比较清晰啦。接下来我们来看下枚举的高级用法:

    4.枚举的高级用法

    向enum类添加方法与自定义属性和构造函数 重新定义一个日期枚举类,带有desc成员变量描述该日期的对于中文描述,同时定义一个getDesc方法,返回中文描述内容,自定义私有构造函数,在声明枚举实例时传入对应的中文描述,代码如下:

    public enum DayDesc {
        MONDAY("星期一", 1),
        TUESDAY("星期二", 2),
        WEDNESDAY("星期三", 3),
        THURSDAY("星期四", 4),
        FRIDAY("星期五", 5),
        SATURDAY("星期六", 6),
        SUNDAY("星期日", 7);
    
        private String desc;//文字描述
        private Integer code; //对应的代码
    
        /**
         * 私有构造,防止被外部调用,默认就是私有构造,且只允许私有构造
         *
         * @param desc
         */
        private DayDesc(String desc, Integer code) {
            this.desc = desc;
            this.code = code;
        }
    
        /**
         * 定义方法,返回描述,跟常规类的定义没区别
         *
         * @return
         */
        public String getDesc() {
            return desc;
        }
    
        /**
         * 定义方法,返回代码,跟常规类的定义没区别
         *
         * @return
         */
        public int getCode() {
            return code;
        }
    
        public static void main(String[] args) {
            for (DayDesc day : DayDesc.values()) {
                System.out.println("name:" + day.name() +
                        ",desc:" + day.getDesc());
            }
        }
    }
    

    输出结果:


    枚举类中定义抽象方法,由于每个枚举值都是一个对象实例,在枚举类中定义的抽象方法,会由每个对应的枚举对象去实现,在第一个枚举值后面打上{},报红后alt+enter即可实现方法。先上代码,给大家看看

    public enum Human {
        MEN{
            @Override
            public void say() {
                System.out.println("男:我可以约你出来看月亮么");
            }
        },WOMEN {
            @Override
            public void say() {
                System.out.println("女:好哒,月亮不睡我们不睡");
            }
        };
    
        public abstract void say();
    
        public static void main(String[] args) {
            Human.MEN.say();
            Human.WOMEN.say();
        }
    }
    

    探索:可能大家还是有疑问哈,这个就简单的使用Idea内嵌的反编译工具叫啥flower给大家看下,大概就知道原理了:
    首先执行javac Human.java 对该类进行编译,我们会发现得到了三个class文件:

    直接用Idea内置的反编译,默认已经嵌入,大家什么都不用干,直接打开.class文件即可。代码如下:

    package com.huawei.subtitle.portal;
    
    public enum Human {
        MEN {
            public void say() {
                System.out.println("男:我可以约你出来看月亮么");
            }
        },
        WOMEN {
            public void say() {
                System.out.println("女:好哒,月亮不睡我们不睡");
            }
        };
    
        private Human() {
        }
    
        public abstract void say();
    
        public static void main(String[] var0) {
            MEN.say();
            WOMEN.say();
        }
    }
    ---
    enum Human$1 {
        Human$1() {
        }
    
        public void say() {
            System.out.println("男:我可以约你出来看月亮么");
        }
    }
    
    ---
    enum Human$2 {
        Human$2() {
        }
    
        public void say() {
            System.out.println("女:好哒,月亮不睡我们不睡");
        }
    }
    

    首先我们看到由于我们定义了抽象方法say(),所以Human这个类是抽象的,要不违背了Java基础有抽象方法的类肯定是抽象类,然后两个枚举对象,分别实现了Human这个类并重写了say()方法,看到这里大家应该懂了吧。好吧,今天就写到这里吧,我有点累了,准备去公司楼下健身房锻炼下。
    有什么问题留言即可,基本每天都会登录我的博客。谁让我笨但是勤劳嘞嘻嘻

    艾欧尼亚,昂扬不灭,为了更美好的明天而战(#^.^#)
  • 相关阅读:
    查看每个核的资源情况
    什么时候使用NO_UNNEST
    走FILTER效率高的2种情况
    PL/SQL 包头和包体
    产品研发要配合好
    ElasticSearch 文档并发处理以及文档路由
    ES(ElasticSearch) 索引创建
    BaikalDB技术实现内幕(三)--代价模型实现
    腾讯位置服务地图SDK自定义地图和路况
    mysql数据库优化
  • 原文地址:https://www.cnblogs.com/lovelywcc/p/13931061.html
Copyright © 2020-2023  润新知