我是靠谱客的博主 昏睡咖啡豆,最近开发中收集的这篇文章主要介绍第三十条 用enum代替int常量,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述


枚举类型是指一组固定常量组成合法值的类型,例如一年四季、太阳系行星、一副牌的四种花色等可以归纳的东西。在枚举出现之前,一般都是用常量来标识每个不同的类型

public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;

public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOD = 2;

这种方法称为int枚举模式,在代码中经常会看到,但却有一些不足。这种写法,如果一不留神,写串了,把 APPLE_FUJI 传进了 需要 ORANGE_NAVEL 的方法里,因为值一样,所以编译不会报错,如果需要用 == 来比较两个值是否都是苹果,此时明显不是,但值却相等,很容易逻辑出错。并且我们为了区分 apple 和 orange,在给他们定义常量时,通常会把他们的类型作为前缀,防止命名冲突。有时候我们也会用String类型来标识类型,例如 

public static final String BANANA_FUJI = "FUJI";
public static final String BANANA_PIPPIN = "PIPPIN";
public static final String BANANA_GRANNY_SMITH = "GRANNY_SMITH";

这种被称为String枚举模式,但与int枚举模式相比,这种更糟糕。我们知道,int是基本类型,String则不是,String 类型是在常量池里的,比较是用 equals(()方法,一旦写错了任何一个字母,运行时也会出问题。所以,Java 1.5 开始,出现了替代的解决方案,就是枚举。

public enum Apple {
    FUJI, PIPPIN, GRANNY_SMITH
}

public enum Orange {
    NAVEL, TEMPLE, BLOD
}

枚举是通过公有的静态final域为每个枚举常量导出实例的类,没有对外构造器,默认都是 final 类型,不会被继承,不能被构造创建,直接使用,同时也是单利类型,所以,也可以把枚举看成一个特殊的类,这个类不能被继承,不能创建构造方法,不能被 new 出对象,它直接提供对象,并且这个类是单利模式的。 书中这个太阳系八个星星的信息,书中是通过枚举来举例子

public enum Planet {

    MERCURY(1, 1),
    VENUS(2, 2),
    EARTH(3, 3);

    private final double mass;
    private final double radius;
    private final double surfaceGravity;

    private static final double G = 6.67 * 1000000;

    Planet(double mass, double radius){
        this.mass = mass;
        this.radius = radius;
        surfaceGravity = G * mass / (radius * radius);
    }

    public double surfaceGravity(){
        return surfaceGravity;
    }

    public double surfaceWeight(double mass){
        return mass * surfaceGravity;
    }
}

通过这个枚举,和上面 Apple 枚举相比,我们可以发现一些东西。 因为枚举不对外构造,它是内部自己实现的,所以 Apple 是默认无参构造,可以忽略不谢,Planet 则有两个参数需要传进来,所以声明了两个参数的构造;每个对象的值都是在内部统一写好的,后期不能改变,枚举,就是把所有的都列举出来的意思,既然要列举,那肯定要提前知道所有信息,才能罗列;枚举也对外提供方法,对外暴露自己参数的信息;枚举也可以做一些业务逻辑操作,按照业务计算一些值。 看到这,我们是不是感觉和 class 类差不多,可以看做是特殊的 class 类来用就行了,没那么多的神秘感。我们如果要获取枚举单独一个值,只需要 Planet o = Planet.MERCURY; 有点类似class 的静态对象调用;获取枚举的所有值,则 Planet[] arr = Planet.values(); 是不是像List集合中对外暴露的方法,转换为数组。枚举有一些自己独特的 api, 只需要明白与普通class 的图通,就能灵活运用。比如,要打印所有星星的surfaceWeight值,则

    private void test() {
        double erathWeight = 10f;
        double mass = erathWeight / Planet.EARTH.surfaceGravity();
        for (Planet p : Planet.values()) {
            System.out.println(p + " weight is " + p.surfaceWeight(mass));
        }
    }

通过遍历,拿到每个Planet,然后穿进去参数,获取想要的值,用法和普通类基本一样,逻辑都是相通的。枚举的另外一种使用方式,比如要标识几种不同的状态

public enum Operation {
    PLUS, MINUS, TIMES, DIVIDE;

    double apply(double x, double y) {
        switch (this) {
            case PLUS:
                return x + y;
            case MINUS:
                return x - y;
            case TIMES:
                return x * y;
            case DIVIDE:
                return x / y;
        }
        throw new AssertionError("Unknow oop : " + this);
    }
}

以枚举列举四种类型,一一对应。代码可行,但容易出错和不利于扩展,扩展的原则是尽量不改变原先的代码,如果我们增加一种新的类型,比如 取余,那么我们需要在switch方法里添加新类型,如果一不小心添加错了,忘了添加对应的条件,那么仍能编译,但运行会出错。之前提到了,枚举可以成是特殊的class类,那么,我们可以用抽象技术,把apply()方法抽象出来,每个 枚举类型在自己内部再去实现它,修改为 

public enum Operation {
    PLUS {
        @Override
        double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS {
        @Override
        double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES {
        @Override
        double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE {
        @Override
        double apply(double x, double y) {
            return x / y;
        }
    };

    abstract double apply(double x, double y);
}

添加新类型,直接写新的就好了,不牵涉原先逻辑。有木有感觉和class是一样的, 可以理解为 Operation 抽象的class, PLUS 等是创建的对象,因为是抽象的,所以创建对象时要把抽象的方法给实现,跟new 一个抽象类和new一个接口是同样道理。 进一步改进方案是在增加一个符号标识,解释方法的功能,例如添加 + - * / %等等

public enum Operation {
    PLUS("+") {
        @Override
        double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        @Override
        double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        @Override
        double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        @Override
        double apply(double x, double y) {
            return x / y;
        }
    },
    Residual("/") {
        @Override
        double apply(double x, double y) {
            return x % y;
        }
    };


    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    abstract double apply(double x, double y);
}

这样就把问题解决了,并且更加清晰。但有时候,复杂的情况怎么办?

enum PayrollDay {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY;

    private static final int HOURS_PER_SHIFT = 8;

    double pay(double hoursWorked, double payRate) {
        double basePay = hoursWorked * payRate;

        double overtimePay; // Calculate overtime pay
        switch(this) {
            case SATURDAY: case SUNDAY:
                overtimePay = hoursWorked * payRate / 2;
                break;
            default: // Weekdays
                overtimePay = hoursWorked <= HOURS_PER_SHIFT ?
                        0 : (hoursWorked - HOURS_PER_SHIFT) * payRate / 2;
        }
        return basePay + overtimePay;
    }
}

像这种,一周七天,周一到周五是平常工资,周六和周日有加班工资,这段代码看起来比较简洁,但犯了和上面同样的问题,不利于扩展。改法如果和上面一样修改。简单的例子

enum PayrollDay {
    MONDAY {
        @Override
        double pay(double hoursWorked, double payRate) {
            double basePay = hoursWorked * payRate;
            double overtimePay = hoursWorked <= HOURS_PER_SHIFT ?
                    0 : (hoursWorked - HOURS_PER_SHIFT) * payRate / 2;
            return basePay + overtimePay;
        }
    },
    TUESDAY {
        @Override
        double pay(double hoursWorked, double payRate) {
            double basePay = hoursWorked * payRate;
            double overtimePay = hoursWorked <= HOURS_PER_SHIFT ?
                    0 : (hoursWorked - HOURS_PER_SHIFT) * payRate / 2;
            return basePay + overtimePay;
        }
    };

    private static final int HOURS_PER_SHIFT = 8;

    abstract double pay(double hoursWorked, double payRate);
}

里面只包含了两个,其他五个大家可以自己完善。如果写全了,我们会发现 周一到周五的pay()方法一样,周六和周日的一样,有重复,能复用吗?能! 该抽取的就抽取,该封装的就封装,那么怎么办?我们可以再用一个枚举来分类,分为平常日和周末日,这样,就归为两类了,在这两类里面各自实现自己的逻辑,然后外面的周一到周五调用一个,周六到周日调用另外一个。这样相当于封装了一层,划分更清晰

public enum PayRoll {
    MONDY(PayType.WEEKDAY),
    TUESDAY(PayType.WEEKDAY),
    WEDNESDAY(PayType.WEEKDAY),
    THURSDAY(PayType.WEEKDAY),
    FRIDAY(PayType.WEEKDAY),
    SATURDAY(PayType.WEEKEND),
    SUNDAY(PayType.WEEKEND);

    private final PayType payType;
    PayRoll(PayType payType) {
        this.payType = payType;
    }

    double pay(double hoursWorked, double payRate) {
        return payType.pay(hoursWorked, payRate);
    }

    private enum PayType {
        WEEKDAY {
            @Override
            double overtimePay(double hoursWorked, double payRate) {
                double overtime = hoursWorked - HOURS_PER_SHIFT;
                return overtime <= 0 ? 0 : overtime * payRate / 2;
            }
        },

        WEEKEND {
            @Override
            double overtimePay(double hoursWorked, double payRate) {
                return hoursWorked * payRate / 2;
            }
        };

        private static final int HOURS_PER_SHIFT = 8;
        abstract double overtimePay(double hoursWorked, double payRate);

        double pay(double hoursWorked, double payRate) {
            double basePay = hoursWorked * payRate;
            return basePay + overtimePay(hoursWorked, payRate);
        }
    }
}

里面定义一个 PayType ,标识两种类型,平常和周末,抽象一个额外补助工资的方法overtimePay(),两种类型 WEEKDAY WEEKEND 分别实现自己的细节,那么这个枚举就ok了。再看外面,周一到周五属于 WEEKDAY,周六周日是 WEEKEND ,那么我们在枚举 PayRoll 定义属性,之前定义的属性是对象,这次特殊点,定义的属性时枚举,同时通过构造,一周七天前五天对应WEEKDAY,后两天对应WEEKEND,这样,就完美了。

枚举分类很好用,android中也常用到,但枚举比常量好性能,android 代码中除了关键点,还是尽量少用。

最后

以上就是昏睡咖啡豆为你收集整理的第三十条 用enum代替int常量的全部内容,希望文章能够帮你解决第三十条 用enum代替int常量所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(57)

评论列表共有 0 条评论

立即
投稿
返回
顶部