带你快速看完9.8分神作《Effective Java》—— 枚举 & 注解篇

本文详细介绍了《Effective Java》中关于枚举和注解的使用技巧,包括用enum替代int常量、使用EnumSet和EnumMap的优势,以及为何注解优于命名模式。强调了Override注解的重要性,并讨论了标记接口在定义类型时的作用。通过实例展示了枚举在提高代码安全性、可读性和扩展性方面的优势。
摘要由CSDN通过智能技术生成

🍊 Java学习:Java从入门到精通总结

🍊 Spring系列推荐:Spring源码解析

📆 最近更新:2021年12月16日

🍊 个人简介:通信工程本硕💪、阿里新晋猿同学🌕。我的故事充满机遇、挑战与翻盘,欢迎关注作者来共饮一杯鸡汤

🍊 点赞 👍 收藏 ⭐留言 📝 都是我最大的动力!

豆瓣评分9.8的图书《Effective Java》,是当今世界顶尖高手Josh Bloch的著作,在我之前的文章里我也提到过,编程就像练武,既需要外在的武功招式(编程语言、工具、中间件等等),也需要修炼心法(设计模式、源码等等)学霸、学神OR开挂

在这里插入图片描述

我个人在Java领域也已经学习了近5年,在修炼“内功”的方面也通过各种途径接触到了一些编程规约,例如阿里巴巴的泰山版规约,在此基础下读这本书的时候仍是让我受到了很大的冲激,学习到了很多约定背后的细节问题,还有一些让我欣赏此书的点是,书中对于编程规约的解释让我感到十分受用,并愿意将他们应用在我的工作中,也提醒了我要把阅读JDK源码的任务提上日程。

最后想分享一下我个人目前的看法,内功修炼不像学习一个新的工具那么简单,其主旨在于踏实,深入探索底层原理的过程很缓慢并且是艰辛的,但一旦开悟,修为一定会突破瓶颈,达到更高的境界,这远远不是我通过一两篇博客就能学到的东西。

接下来就针对此书列举一下我的收获与思考。

不过还是要吐槽一下的是翻译版属实让人一言难尽,有些地方会有误导的效果,你比如java语言里extends是继承的关键字,书本中全部翻译成了扩展 就完全不是原来的意思了。所以建议有问题的地方对照英文原版进行语义上的理解。

没有时间读原作的同学可以参考我这篇文章。


34 用enum代替int常量

每当需要一组固定常量,并且在编译时就知道常量分别都是什么时,就要用枚举


在枚举出现之前,大家都是用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_BLOOD = 2;

上述做法有很多不足:
1. 不安全且没有任何描述性
如果将apple传入了想要orange的方法里,不会报错,还会用==运算符比较 Apple 与 Orange

2. 使用int 枚举的程序很脆弱
因为 int 枚举是编译时常量,所以它们的int 值被编译到使用它们的客户端中,如果与int 枚举关联的值发生更改,则必须重新编译其客户端

3. 很难将 int 枚举常量转换为可打印的字符串
就算将其打印出来了,所看到的也只是一个数字,没什么意义

4. 没有可靠的方法来遍历所有 int 枚举常量


除了int枚举模式之外,还有String枚举模式(String enum pattern),它同样也有很多缺点:

1. 导致初级用户将字符串常量硬编码到客户端代码中,就是常说的魔法值

在这里插入图片描述
2. 会依赖字符串的比较操作故有很大的性能问题


Java提供的枚举类型可以很好的解决上面的问题:

public enum Apple {
    FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange {
    NAVEL, TEMPLE, BLOOD }

Java的枚举本质上是int值,思想非常简单:通过public static final属性为每个枚举常量导出一个实例。由于没有可访问的构造方法,枚举类型实际上是 final 的。客户既不能创建枚举类型的实例也不能继承它,除了声明的枚举常量外,不能有任何实例。


有以下几条优点:
1. 保证了编译时的类型安全
如果声明参数的类型为Apple,它就能保证传到该参数上的任何非空的对象引用一定是FUJIPIPPINGRANNY_SMITH之一

2. 具有相同名称常量的多个枚举类型可以共存
因为每个类都有其自己的名称空间

3. 枚举类型还允许添加任意方法和属性并实现任意接口
提供了所有 Object 方法,实现了Comparable 和 Serializable 接口


比如太阳系的八颗行星,每个行星都有质量和半径,从这两个属性可以计算出它的表面重力

每个枚举常量之后的括号中的数字是传递给其构造方法的参数

package com.wjw.effectivejava1;

public enum Planet {
   
    MERCURY(3.302e+23, 2.439e6),
    VENUS(4.869e+24, 6.052e6),
    EARTH(5.975e+24, 6.378e6),
    MARS(6.419e+23, 3.393e6),
    JUPITER(1.899e+27, 7.149e7),
    SATURN(5.685e+26, 6.027e7),
    URANUS(8.683e+25, 2.556e7),
    NEPTUNE(1.024e+26, 2.477e7);

    private final double mass; // In kilograms
    private final double radius; // In meters
    private final double surfaceGravity; // In m / s^2
    // Universal gravitational constant in m^3 / kg s^2
    private static final double G = 6.67300E-11;

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

    public double mass() {
   
        return mass;
    }

    public double radius() {
   
        return radius;
    }

    public double surfaceGravity() {
   
        return surfaceGravity;
    }

    public double surfaceWeight(double mass) {
   
        return mass * surfaceGravity; // F = ma
    }
}

可以根据物体在地球上的重量,打印出该物体在所有8颗行星上的重量:

public class WeightTable {
   
    public static void main(String[] args) {
   
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight / Planet.EARTH.surfaceGravity();
        for (Planet p : Planet.values())
            System.out.printf("Weight on %s is %f%n", p, p.surfaceWeight(mass));
    }
}

运行结果:
在这里插入图片描述

如果一个枚举具有普适性,他就应该成为一个顶层类,如果他只是被用在一个特定的顶层类里,他就应该成为该顶级类的
成员类,例如java.math.RoundingMode表示小数部分的舍入模式。BigDecimal类使用了这些舍入模式,但他们却不属于BigDecimal类的一个抽象。让RoundingMode成为一个顶层类,以鼓励让任何需要舍入模式的程序员重用。


有时我们需要更多的方法,加入正在编写一个枚举类,表示计算器的加减乘除操作,还需要提供一个方法来执行每个常量所表示的运算:

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

    // Do the arithmetic operation represented by this constant
    public 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("Unknown op: " + this);
    }
}

这段代码能用,但不优雅,如果添加了新的枚举常量,却忘记给switch添加相应的条件就会失败。


有一种更好的方法可以将不同的行为与每个枚举常量关联起来:在枚举类型中声明一个抽象的apply方法,并在特定于常量的类主体(constant-specific class body)。这种方法被称为特定于常量的方法实现(constant-specific method implementation):

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

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

特定于常量的方法实现可以与特定于常量的数据结合使用。toString 方法返回与操作关联的符号:

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

    private final String symbol;

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

    @Override
    public String toString() {
   
        return symbol;
    }

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

上述代码可以很容易地打印算术表达式:

public static void main(String[] args) {
   
       double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        for (Operation op : Operation.values())
                System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}

2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000


特定于常量的方法实现有

评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王曾是少年

如果对你有帮助,欢迎支持我

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值