Java枚举

/ 默认分类 / 0 条评论 / 1193浏览

Java枚举深入浅出

一.概念介绍

关于枚举,你可以认为,他就是一个java类,而实时上,他也可以算是java中的一种"语法糖",其中定义的一些枚举值就是对当前类(枚举)本身做的枚举(列举),所谓枚举,也就是将某个事物本身的有限个数的状态全部列举出来,这里重点是有限个数,并且有固定特征,这样才有枚举的可能和实现出来的意义。

public enum Status {

    FAIL,
    SUCCESS;

}
public enum Status {

    FAIL,

    SUCCESS;

    int code;

    String msg;
    
}

public enum Status {

    FAIL(),

    SUCCESS();

    int code;

    String msg;

}
public enum Status {

    FAIL(1,"fail"),

    SUCCESS(2,"success");

    int code;

    String msg;

    Status(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}
        Status fail = Status.valueOf("fail");
        Status fail1 = Status.valueOf(Status.class, "fail");
        Status[] statuses = Status.values();
public enum Status {

    FAIL(1,"fail"),

    SUCCESS(2,"success");

    int code;

    String msg;

    Status(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    //可以定义普通方法
    public boolean isSuccess(){
        return isSucc();
    }

    private boolean isSucc(){
        return true;
    }

    //可以声明属性,但是如果你不想在枚举的时候列出来,可以不要写在相应的构造方法中即可
    static HashMap<Integer,Status> map = new HashMap<>();

    //可以声明静态代码块,总之类可以做的枚举基本都可以
    static {
        for (Status value : values()) {
            map.put(value.code,value);
        }
    }

    //可以定义静态方法
    public static Status value(int code){
        return map.get(code);
    }

}

public enum Status {

    FAIL(1,"fail"){
        @Override
        public String isSucc() {
            return null;
        }
    },

    SUCCESS(2,"success"){
        @Override
        public String isSucc() {
            return null;
        }
    };

    int code;

    String msg;

    Status(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public abstract String isSucc();

}
public enum TimeUnit {
 
	//可以看到,这里列出出来的子类也是重写了方法,只是这里使用的是默认的无参构造方法并且省略了()
    MILLISECONDS {
        public long toNanos(long d)   { return x(d, C2/C0, MAX/(C2/C0)); }
        public long toMicros(long d)  { return x(d, C2/C1, MAX/(C2/C1)); }
        public long toMillis(long d)  { return d; }
        public long toSeconds(long d) { return d/(C3/C2); }
        public long toMinutes(long d) { return d/(C4/C2); }
        public long toHours(long d)   { return d/(C5/C2); }
        public long toDays(long d)    { return d/(C6/C2); }
        public long convert(long d, TimeUnit u) { return u.toMillis(d); }
        int excessNanos(long d, long m) { return 0; }
    },


    SECONDS {
        public long toNanos(long d)   { return x(d, C3/C0, MAX/(C3/C0)); }
        public long toMicros(long d)  { return x(d, C3/C1, MAX/(C3/C1)); }
        public long toMillis(long d)  { return x(d, C3/C2, MAX/(C3/C2)); }
        public long toSeconds(long d) { return d; }
        public long toMinutes(long d) { return d/(C4/C3); }
        public long toHours(long d)   { return d/(C5/C3); }
        public long toDays(long d)    { return d/(C6/C3); }
        public long convert(long d, TimeUnit u) { return u.toSeconds(d); }
        int excessNanos(long d, long m) { return 0; }
    },


    static final long C0 = 1L;
    static final long C1 = C0 * 1000L;
    static final long C2 = C1 * 1000L;
    static final long C3 = C2 * 1000L;
    static final long C4 = C3 * 60L;
    static final long C5 = C4 * 60L;
    static final long C6 = C5 * 24L;

    static final long MAX = Long.MAX_VALUE;

    static long x(long d, long m, long over) {
        if (d >  over) return Long.MAX_VALUE;
        if (d < -over) return Long.MIN_VALUE;
        return d * m;
    }

 
    public long convert(long sourceDuration, TimeUnit sourceUnit) {
        throw new AbstractMethodError();
    }

    public long toNanos(long duration) {
        throw new AbstractMethodError();
    }


    public long toMicros(long duration) {
        throw new AbstractMethodError();
    }


    public long toMillis(long duration) {
        throw new AbstractMethodError();
    }


    public long toSeconds(long duration) {
        throw new AbstractMethodError();
    }


    abstract int excessNanos(long d, long m);

    public void sleep(long timeout) throws InterruptedException {
        if (timeout > 0) {
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            Thread.sleep(ms, ns);
        }
    }

}

二.枚举的优缺点

  1. 枚举最大的优点肯定是,在方法入参需要严格控制参数类型和数据的时候,如果用枚举来作为入参类型,那么将最大程度保证代码的安全性和降低错误率.就像下面的场景:
public class Test {

    public static void test1(Color color){
        String co = color.val;
        System.out.println(co);
    }

    public static void test2(String color){
        System.out.println(color);
    }

    public static void main(String[] args) {
        test1(Color.RED);
        test1("black"); //编译报错
    }

}


public enum Color {

    RED,
    YELLOW;

    String val;

}

虽然这里的枚举Color本质还是String的值,但是如果使用Enum作为方法参数,那么别人在调用这个方法的时候,虽然是传入一个颜色值,但是无法自己定义,并且这样也不会发生传入其他类型的值,另外对于调用者也是一种方便.

  1. 但是枚举也有不好的地方,首先前面说了,枚举本身也即是java类,列举出来的都是当前类的实例对象,所以一个枚举中如果列举了多个值,那么就等于这一个枚举创建了多个对象,可想而知,使用枚举比我们单独使用数据类型值要更占用内存

比如下面这个枚举(代码摘自网络)

public enum  Sex {
    MAN, WOMAN;
}

将其编译后的class文件反编译得到的源码如下:

public final class Sex extends Enum
{
 
    public static Sex[] values()
    {
        return (Sex[])$VALUES.clone();
    }
 
    public static Sex valueOf(String s)
    {
        return (Sex)Enum.valueOf(Sex, s);
    }
 
    private Sex(String s, int i)
    {
        super(s, i);
    }
 
    public static final Sex MAN;
    public static final Sex WOMAN;
    private static final Sex $VALUES[];
 
    static 
    {
        MAN = new Sex("MAN", 0);
        WOMAN = new Sex("WOMAN", 1);
        $VALUES = (new Sex[] {
            MAN, WOMAN
        });
    }
}
public final static int MAN = 0;
public final static int WOMAN = 1;

对于一个java对象,对象头就已经是占用12个字节了,如果在加上对象封装的数据,使用枚举肯定比使用静态变量占用内存多得多

所以,在一个项目中也不要过于依赖使用枚举.

ps:但是,使用枚举的场景,本省就说明当前数据量较小,所以可以尽可能块的枚举出来,所以如果非要说内存,其实针对整个项目来说,其实只是九牛一毛

三.一个小问题记录

我在实际开发中发现,有的场景中可能

如果现在需要按照枚举中值的属性来获取枚举,那么可以这样做

public enum Status {

    FAIL(1),
    SUCCESS(2);

    Status(int code) {
        this.code = code;
    }

    private int code;

    public int getCode() {
        return code;
    }

    public static Status value(int code){
        Status[] values = values();
        for (Status value : values) {
            if(value.getCode() == code)
                return value;
        }
        return null;
    }

}

这样,比如在switch判断中就可以优雅的使用了

    public static void main(String[] args) {
        int code = 1;
		//如果没有添加value这个方法,那么可能就需要使用枚举的getCode然后
		//一个个使用if-else进行逻辑判断
        Status value = Status.value(code);
        switch (value) {
            case FAIL:
                System.out.println("1212");
                System.out.println(1212);
                break;
            case SUCCESS:
                System.out.println("177777212");
                System.out.println(77771212);
                break;
        }
    }

这里之所以这样实现,是因为,首先传入过来的匹配条件就是code这样一个枚举值的属性值int,所以无法使用枚举自带的value()方法获取到枚举本身,另外,因为switch-case的语法规定,case后匹配值只能使用一个常量表示,所以无法使用点符号来获取或者使用getter方法获取,所以这里采用这样的方法,或者你可以在代码中使用final,static的map来保存(code,Status)或者在业务代码中直接遍历,但是我个人觉得上面的方法会好一点.