Effevtive Java
以下是本人阅读Effevtive Java一书后的小结,该书是介绍一些Java编码的一些规范以及一些常见问题的解决放哪。如果没有阅读过本书的可能有些知识点看起来会有点疑惑,建议去阅读一下本书或者是在网上搜索关于知识点的介绍,以便理解。
书籍介绍
《Effective Java》(中文版第二版)
作者 约书亚·布洛克(英语:Joshua J. Bloch)1961年8月28日出生。美国著名程序设计师。他为JAVA平台设计并实作了许多的功能。在google工作,是google的首席JAVA架构师。
一、创建和销毁对象
1.静态方法替代构造方法
-
优势
-
有名称,可以使用名称区分不同构造参数的意义
-
不用在每次调用的时候都创建一个新的对象
-
可以返回原对象的子类
-
创建参数化实例,使代码变得简洁
-
2.使用构建器Builder替代构造方法
-
使用场景
-
类中有很多参数不一样的构造方法
-
-
如何使用
-
定义静态类Builder到实体类内部
-
私有化实体类的构造方法
-
Builder提供构造方法,不同参数生成返回不同的实例对象
-
实例类.Builder(String xxx)
-
3.使用单例模式
-
减少冗余的实例对象的创建
4.私有化构造方法
-
避免不必要的对象创建,比如工具类
5.在静态代码块实例化不变的类内部对象
避免不必要的对象创建
6.消除过期的对象引用
-
防止被引用的对象不被垃圾回收器回收,可能造成的内存溢出
7.避免使用终结方法
-
终结方法导致行为不稳定
-
不能保证终结方法能被调用
-
如果要使用,尽量放在try/catch/finally的语法结构中
-
-
性能下降
-
创建一个简单对象,大约为5.6ns,使用finalize时间增加到了2400ns,慢了大约430倍
-
二、对象的通用方法
1.覆盖equals时遵守约定
-
自反性
-
对于非空的引用,x.equals(x) 必须返回true
-
-
对称性
-
如果x.equals(y)返回true,那么y.equals(x) 也必须返回true
-
-
传递性
-
如果x.equals(y)为true,y.equals(z)为true 那么x.equals(z)也必须为true
-
-
一致性
-
对于多次调用同一个对象的比较x.equals(y),返回结果必须都一致
-
2.高质量编写equals方法
-
使用==操作符检查“参数是否为这个对象的引用” 如果在比较操作比较昂贵,且符合业务意义的时候
-
使用instanceof操作符判断类型是否正确,类型不正确提前返回false
-
把参数类型转换成正确的类型
-
对于该类的每个“关键域(signification)”,检查参数中的域是否与该对象中对应的域匹配
-
重写的方法满足自反、对称、传递、一致
-
覆盖equals方法时,总是要覆盖hashCode方法
-
不重写hashCode在Map或其他类使用的时候,会被认为是不同的对象
-
-
不要让equals方法过于智能化
-
不要将equals方法的入参改为其他类型 可以使用@override避免这种情况的发生
3.覆盖toString
-
便于输出关键信息
4.谨慎覆盖clone
-
克隆对于引用类型的值,是浅拷贝,只会复制引用对象的内部参数对象的地址,不会创建新的参数对象
5.考虑实现Comparable接口
-
实现了Comparable接口,它就可以跟许多的泛型算法和依赖于该接口的集合进行协作,轻松实现排序功能
三、接口和类
1.使类和成员的可访问性最小化
2.在公有类中使用访问方法而非公有域
-
参数不定义为public,而是用get/set方法访问
3.使可变性最小化
-
不可变类,比如String/Integer
-
不提供任何会修改类对象状态的方法
-
保证类不会被扩展
-
所有的域都是final的
-
所有的域都为私有的
-
确保对于任何可变组件的互斥访问
-
4.复合优先于继承
-
复合
-
public class A{ private B b; public A(){ b = new B(); }
private void method(){ b.method(); } }
-
-
如果类没有明确的继承关系,仅仅是为了减少代码耦合度,尽量使用复合
5.要么为继承而设计,并提供文档说明,要么就禁止继承
6.接口优于抽象类
-
Java类的单继承特性
7.接口只用于定义类型
-
接口常量类
-
只定义静态常量
-
如果将来常量用不上了,这个常量接口也不能移除,因为子类可能已经继承了该类
-
如果类是可序列化的,就算没有被继承,也会存在移除后反序列化异常的问题
-
8.类层次优于标签类
-
标签类
-
在一个类中,标识两种抽象对象,并用枚举参数标识,构造方法只用重构方法区分
-
-
优化
-
用继承的方式,抽象出一个父类,两种抽象类分别继承父类,独立出新的构造方法
-
更易区分功能,代码结构也更清晰
-
9.用函数对象表示策略
-
允许程序把特殊函数的能力存储起来并传递这种能力的接口,比如Comparator
10.优先考虑静态成员
-
嵌套类
-
静态成员类
-
非静态成员类
-
匿名内部类
-
局部类
-
-
使用非静态类,会额外创建该类的实例类,增加时间和内存消耗
-
由于引用的存在,还会在非静态没有使用后,使垃圾回收延后
四、泛型
1.不要在新代码中使用原生态类型
-
比如List,不带< >
2.消除非受检警告
-
@SuppressWarnming(“unchecked”)
-
尽量消除需要添加非受检的情况
-
如果不能消除,需要添加好注解说明
3.列表优先于数组
-
Object[ ] arr = new Long[1]; arr[0] = “String Type”;//合法不抛错
List<Object> list = new ArrayList<Long>( ); list.add(“String Type”);//编译不通过
4.利用有限制的通配符来提升API的灵活性
-
<? extends List>
五、枚举和注解
1.使用enum代替int常量
-
易读、安全、功能更强大
2.用实例域代替序数
-
将排序参数作为枚举类的构造方法参数传入
3.用EnumSet代替位域
4.用EnumMap代替序数索引
5.用接口模拟可伸缩的枚举
6.注解优先于命名模式
-
可以在编译阶段提前检测发现问题,而不是到运行阶段暴露出问题,而且问题可能不容易被排查发现
7.坚持使用Override注解
-
确保类重写了父类的方法
8.用标记接口定义类型
-
标记接口,没有定义方法实现的接口,比如Serializable
六、方法
1.检查参数的有效性
2.必要时进行保护性拷贝
-
方法传入的对象,只是引用,如果调用方改变了对象的值,可能对当前方法产生影响,这时候就需要拷贝一份数据供当前方法使用
3.谨慎设计方法签名
-
遵循方法标准命名习惯
-
驼峰式
-
看其名知其义
-
风格一致
-
-
不要过于追求提供便利的方法
-
接口方法定义不要过多,除非必要
-
-
避免过长的参数列表
-
尽量不超过4个,IDEA的建议是不超过7个
-
4.慎用重载
-
如果参数数量相同,并且重载方法的参数有继承关系的特别需要注意
5.慎用可变参数
-
如果没有传参数,不会在编译时暴露出来,会到运行期才会抛出错误
6.返回零长度的数组或集合,而不是null
-
有效避免调用方使用时没有判空而报错
7.为所有导出的API元素编写文档注释
七、通用程序设计
1.将局部变量的作用最小化
-
在第一次需要使用它的地方声明,让读者不分散注意力
-
不让后面的代码块误用
-
让垃圾回收器在变量使用完后能及时的回收处理
2.for-each循环优先于传统的for循环
-
嵌套循环的时候避免使用下标出错
-
三种情况无法使用foreach循环
-
需要对元素进行remove操作
-
需要更新对应位置的元素
-
需要并行遍历多个集合
-
3.了解和使用类库
-
对于可以使用类库的方法,通常稳定性、性能都要比自己再开发好
-
减少重复造轮子的工作
4.如果要精确的数字类型结果,避免使用float和double
-
使用BigDecimal
5.基本类型优先于装箱基本类型
-
对象类型可能为空
-
如果需要计算,装箱拆箱消耗时间成本
6.如果其他类型更适合,避免使用字符串
7.注意字符串连接的性能
-
使用StringBuilder等其他动态拼接的工具类
8.通过接口引用对象
-
List<Long> list = new Vector<>();
-
使用接口接住实现类,而不是直接使用实现类 更易与扩展修改
9.接口优先于反射机制
-
反射机制问题
-
丧失了编译时类型检查的功能
-
执行反射方法需要的代码更笨拙冗长
-
性能损耗
-
10.谨慎使用本地方法
-
使用本地方法的程序不再免受内存毁坏错误的影响
-
不再是可自由移植的,因为本地方法可能只在特定平台起作用
-
可读性降低
八、并发
1.尽量不定义可访问的共享数据
2.避免过度同步
-
同步会降低性能
-
可能产生死锁
3.executor和task优先于线程
-
有利于线程管理
4.并发工具优先于wait和notify
-
使用java.until.concurrent中的工具类实现线程通讯
-
Executor Framework
-
Concurrent Collection 并发集合
-
Sychronizer 同步器
-
5.线程安全性的文档化
6.慎用参数延迟初始化
-
尽量正常初始化
-
对于实例域可以使用双重检查,加锁初始化
-
对于静态域,可以使用lazy initialization holder class idiom
九、序列化
1.谨慎实现Serializable接口
-
降低了改变这个类的灵活性,代表这个类必须一直实现序列化
-
增加了出现BUG和安全漏洞的可能性
-
反序列化是一个隐藏的构造器,可能绕过原先构造器的约束
-
序列化版本号不一致,反序列化会报错
-
随着新版本迭代,测试负担和风险也会增加
-