Effective Java 读书笔记
工作中书写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)
返回true
则y.equals(x)
返回true
; - 传递性:对于任何非
null
的引用对象x、y、z,x.equals(y)
并且y.equals(x)
返回true
则x.equals(z)
返回true
; - 一致性:对于任何非
null
的引用对象x、y,只要对象并未发生改变则多次调用equals
方法一致地返回true
或一致地返回false
; - 非空性:对于任何非
null
的引用对象x,x.equals(null)
必须返回false
;
重写equals
诀窍
- 使用==操作符检查“参数是不是this对象的引用”;
- 使用
instanceof
操作符检查“参数是否为正确的类型”; - 把参数转换成正确的类型;
- 对于该类型中的每个“关键”成员变量,检查参数中的成员变量是否与该对象中对应的成员变量相匹配;
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
21public enum Operation{
PLUS("+") {
double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
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
方法,将定制的字符串表示变回相应的枚举。(可以考虑) - 策略枚举的使用(枚举的属性设为枚举类型,来对枚举进行分类,对其调用的方法进行分类)
EnumSet
和EnumMap
的使用, 这两个类操作集合比用List
和Map
操作高效。在实际开发中,用到枚举集合时,优先考虑这两个
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
3for (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
。(大致原因是字符串与其他对象拼接时调用StringBuilder
的append()
方法。 形具体原因键《Tinking in java》一书字符串章节)
52.通过接口引用对象
- 用接口作为参数类型, 可以使程序更加灵活。你可以随时更换你的具体实现
- 如果原来的代码实现了某种特殊的功能,而接口又不具备,则应在新的实现提供同样的功能
- 如果没有合适的接口存在,完全可以使用类而不是接口来引用对象
53.接口优先于反射机制
54. 谨慎地使用本地方法
- 尽量避免使用本地方法,因为本地方法是不安全的
55. 谨慎地进行优化(?)
56. 遵守普遍接受的命名惯例
- 包名以组织(公司)的
Internet
域名开头,并将顶级域名放在前面。例如:com.sunyard
- 用户创建的包绝对不能以
java
和javax
开头 - 类名、方法名使用驼峰写法,方法名首字母小写
- 常量应用全部大写
- 泛型参数名称通常由单个字母组成。
- T : 表示任意类型
- E : 表示集合类型
- K、V : 表示映射的键和值类型
- X : 表示异常
异常
57.只针对异常的情况才使用异常
58.对可恢复的情况使用受检异常,对编程错误使用运行时异常
59.避免不必要地使用受检的异常
60.优先使用标准的异常
61.抛出与抽象相对应的异常
62.每个方法抛出的异常都要有文档
63.在细节消息中包含能捕获失败的信息
64.努力使失败保持原子性
65.不要忽略异常
并发
66.同步访问共享的可变数据
java
语言规范保证读写一个变量是原子性的,除非这个变量的类型是long
或者double
- 多线程的情况下要考虑操作的原子性。对于普通的共享变量,不同线程对其的修改不一定能被其他线程看到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public 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;
}
} - 可以使用
volatile
关键字或者加锁的方式解决可见性问题1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public 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;
}
} - 增量操作(++)不是原子性的,需要使用
synchronized
进行保护。或者直接使用Atomic
类替代1
2
3
4private static final AtomicLong nextLong = new AtomicLong();
public static long generate(){
return nextLong.incrementAndGet();
} - 当多个线程共享可变数据时,每个读写数据的线程都必须执行同步。
67.避免过度使用
68.executor和task优于线程
69.并发工具优先于wait和notify
70.线程安全性的文档化
71.慎用延迟初始化
72.不要依赖于线程调度器
73.避免使用线程组
序列化
74.谨慎地实现Serizalizable接口
75.考虑使用自定义的序列化形式
76.保护性得编写readObject方法
77.对于实例控制,枚举类型优先于readResolve
78.考虑用序列化代理代替序列化实例
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 气象先生!