工作中书写java代码也有达到一定代码量了,由于环境问题一直无法对自己的代码质量有较好的的改。因此决定阅读这本驰名已久的书籍来规范自己的代码,改进自己的不足。这篇博客就用于读书笔记,记录一些比较重要的知识点。

引言

创建和销毁对象

1.考虑用静态工厂方法代替构造器

2.遇到多个构造器参数时要考虑用构建模式

3.用私有构造器或者枚举类型强化Singlenton属性

4.通过私有构造器强化不可实例化的功能

5.避免创建不必要的对象

6.消除过期的对象引用

7.避免使用终结方法

对于所有对象都通用的方法

8.覆盖equals时请遵循通用约定

重写equals方法时,需要遵循以下规范:

  • 自反性:对于任何非null的引用对象x,x.equals(x)必须返回true;(例如集合添加时会进行contains判断);
  • 对称性:对于任何非null的引用对象x、y,x.equals(y)返回truey.equals(x)返回true;
  • 传递性:对于任何非null的引用对象x、y、z,x.equals(y)并且y.equals(x)返回truex.equals(z)返回true;
  • 一致性:对于任何非null的引用对象x、y,只要对象并未发生改变则多次调用equals方法一致地返回true或一致地返回false;
  • 非空性:对于任何非null的引用对象x, x.equals(null)必须返回false;

重写equals诀窍

  1. 使用==操作符检查“参数是不是this对象的引用”;
  2. 使用instanceof操作符检查“参数是否为正确的类型”;
  3. 把参数转换成正确的类型;
  4. 对于该类型中的每个“关键”成员变量,检查参数中的成员变量是否与该对象中对应的成员变量相匹配;

9.覆盖equals时总要覆盖hashCode

10.始终要覆盖toString

11.谨慎地覆盖clone

12.考虑实现Comparable接口

类和接口

13.使类和成员的可访问性最小化

14.在公有类中使用访问方法而非公有域

15.使可变性最小化

16.复合优先于继承

17.要么为继承而设计,并提供文档说明,要么就禁止继承

18.接口优于抽象类

19.接口只用于定义类型

20.类层次由于标签类

21.用函数对象表示策略

22.优先考虑静态成员类

泛型

23.请不要再新代码中使用原生态类型

24.消除非受检警告

25.列表优先于数组

26.优先考虑泛型

27.优先考虑泛型方法

28.利用有限通配符来提升API的灵活性

29.优先考虑类型安全的异构容器

枚举和注解

30.用enum代替int常量

  • 使用枚举类型取代int枚举模式
  • 枚举类型允许添加任意类型的方法和成员变量,并实现任意接口
  • 枚举类型内的属性修饰符定为private final
  • 枚举类型使用values()方法遍历
  • 枚举类型与switch配合使用
  • 在枚举类型中声名一个抽象方法, 并在特定的枚举值中进行覆盖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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;
    }
    };

    private final String symbol;

    Operation(String symbol) {
    this.symbol = symbol;
    }
    abstract double apply(double x, double y);
    }
  • 枚举类型中可以重写toString方法,但是如果重写了toString方法, 最好添加一个fromString方法,将定制的字符串表示变回相应的枚举。(可以考虑)
  • 策略枚举的使用(枚举的属性设为枚举类型,来对枚举进行分类,对其调用的方法进行分类)
  • EnumSetEnumMap的使用, 这两个类操作集合比用ListMap操作高效。在实际开发中,用到枚举集合时,优先考虑这两个

31.用实例域代替序数

  • 不要根据枚举的序数导出与它相关联的值,而是将它保存在一个实例域中。即不要用ordinal()方法去获取枚举相关联的值
  • 如果不是编写类似EnumMap这种基于枚举的数据结构,最好完全避免使用ordinal()方法

32.使用EnumSet代替位域

33.使用EnumMap代替序数索引

34.用接口模拟可伸缩枚举

35.注解优先于命名模式

36.坚持使用Override注解

37.用标记接口定义类型

方法

38.检查参数的有效性

39. 必要时进行保护性拷贝

40.谨慎设计方法签名

41.慎用重载

42.慎用可变参数

43.返回零长度的数组或者集合,而不是null

44.为所有导出的API元素编写文档注释

通用程序设计

45. 将局部变量的作用域最小化

  • 要使局部变量的作用域最小化,最好的方法就是在第一次使用的地方进行声明
  • 几乎每个局部变量的声明都应该包含一个初始化表达式,循环时则在循环最开始处初始化循环中要用的变量:
    1
    2
    3
    for (int i = 0 , k = getK(); i < k; i++) {
    doSomething(i);
    }

46. for-each循环优先于传统的for循环

  • 尤其当对多个集合进行嵌套式迭代时,优先考虑使用for-each循环
  • 在一下情况下无法使用for-each循环: 过滤(循环过程中删除指定元素)、 转换(遍历时取代部分值)、平行迭代(内外循环平行的进行迭代)

47.了解和使用类库

  • 活用java类库,尽量少重新发明轮子

48.如果需要精确的答案,请避免使用float和double

  • double 与 float 不能用于进行货币计算
    关于double进行精确计算时出现的精度问题,可以看这里

49.基本类型优先于装箱基本类型

  • 基本类型与装箱类型有三点区别:

    • 基本类型只能有值,装箱基本类型除了拥有它对应基本类型的功能值外,还有个非功能值null
    • 基本类型只有值,而装箱基本类型有与值不同的同一性,即值相同时并不代表”==”返回true
    • 基本类型比基本装箱类型更节省时间和空间
  • 两个装箱基本类型进行比较(>或<)时,会进行自动拆箱

  • 自动拆/装箱其实是调用了valueOf()intValue()方法, 因此对null对象进行自动拆/装箱会报NullPointerException

50.如果其他类型更合适,则尽量避免使用字符串

  • 字符串不适合代替其他的值类型: 例如某个数据它是一个“是-否-是”问题的答案,就应该被转型为boolean类型
  • 字符串不适合代替枚举类型
  • 字符串不适合代替聚集类型(见书p195)。 考虑用类或者内部来代替
  • 字符串不适合代替能力表(日后仔细阅读)

51.当心字符串连接的性能

  • 当对多个字符串进行拼接式,应尽量避免字符串的直接”+”操作,转而使用StringBuilder(非同步)代替String。(大致原因是字符串与其他对象拼接时调用StringBuilderappend()方法。 形具体原因键《Tinking in java》一书字符串章节)

52.通过接口引用对象

  • 用接口作为参数类型, 可以使程序更加灵活。你可以随时更换你的具体实现
  • 如果原来的代码实现了某种特殊的功能,而接口又不具备,则应在新的实现提供同样的功能
  • 如果没有合适的接口存在,完全可以使用类而不是接口来引用对象

53.接口优先于反射机制

54. 谨慎地使用本地方法

  • 尽量避免使用本地方法,因为本地方法是不安全的

55. 谨慎地进行优化(?)

56. 遵守普遍接受的命名惯例

  • 包名以组织(公司)的Internet域名开头,并将顶级域名放在前面。例如:com.sunyard
  • 用户创建的包绝对不能以javajavax开头
  • 类名、方法名使用驼峰写法,方法名首字母小写
  • 常量应用全部大写
  • 泛型参数名称通常由单个字母组成。
    • T : 表示任意类型
    • E : 表示集合类型
    • K、V : 表示映射的键和值类型
    • X : 表示异常

异常

57.只针对异常的情况才使用异常

58.对可恢复的情况使用受检异常,对编程错误使用运行时异常

59.避免不必要地使用受检的异常

60.优先使用标准的异常

61.抛出与抽象相对应的异常

62.每个方法抛出的异常都要有文档

63.在细节消息中包含能捕获失败的信息

64.努力使失败保持原子性

65.不要忽略异常

并发

66.同步访问共享的可变数据

  1. java语言规范保证读写一个变量是原子性的,除非这个变量的类型是long或者double
  2. 多线程的情况下要考虑操作的原子性。对于普通的共享变量,不同线程对其的修改不一定能被其他线程看到。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     public class StoThread {
    // 共享变量
    private static boolean stop;

    public static void main(String[] args) throws Exception{
    Thread a = new Thread(() -> {
    int i = 0;
    // 实际运行时此循环不会结束
    while (!stop) {
    i++;
    }
    });

    a.start();
    TimeUnit.SECONDS.sleep(1);
    stop = true;
    }
    }
  3. 可以使用volatile关键字或者加锁的方式解决可见性问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     public class StoThread {
    private volatile static boolean stop;

    public static void main(String[] args) throws Exception{
    Thread a = new Thread(() -> {
    int i = 0;
    while (!stop) {
    i++;
    }
    });

    a.start();
    TimeUnit.SECONDS.sleep(1);
    stop = true;
    }
    }
  4. 增量操作(++)不是原子性的,需要使用synchronized进行保护。或者直接使用Atomic类替代
    1
    2
    3
    4
    private static final AtomicLong nextLong = new AtomicLong();
    public static long generate(){
    return nextLong.incrementAndGet();
    }
  5. 当多个线程共享可变数据时,每个读写数据的线程都必须执行同步。

67.避免过度使用

68.executor和task优于线程

69.并发工具优先于wait和notify

70.线程安全性的文档化

71.慎用延迟初始化

72.不要依赖于线程调度器

73.避免使用线程组

序列化

74.谨慎地实现Serizalizable接口

75.考虑使用自定义的序列化形式

76.保护性得编写readObject方法

77.对于实例控制,枚举类型优先于readResolve

78.考虑用序列化代理代替序列化实例