导图社区 Effective java知识分享
《Effective Java》是一本经典的 Java 学习宝典,值得每位 Java 开发者阅读。笔者将书中和平日工作较密切的知识点做了部分总结。
编辑于2022-12-05 14:15:35 浙江省Effective java
创建和销毁对象
1. 考虑用静态工厂方法代替构造器
优势1:有名称
优势2:不必在每次调用它的时候都创建一个新的对象
优势3:可以返回子类型对象
优势4:在创建参数化类型的时候,可以是代码变得更加简洁
静态工厂方法缺点1:类如果不含公有的或者受保护的构造器,就不能被子类话
静态工厂方法缺点2:它们与其他的静态方法实际上没有任何区别
2. 遇到多个构造器参数的时候考虑用构造器
多个构造参数的问题
重叠构造器,难写难读
JavaBean模式在构造过程中可能处于不一致的状态
JavaBean模式阻止了把类做成不可变的可能
builder模式
如果类的构造器或者静态工厂有多个参数,设计这种类的时候,Builder模式就是不错的选择
3. 用私有的构造器或者枚举类型强化Singleton属性
4. 通过私有构造器强化不可实例化的能力
5. 避免创建不必要的对象
如果对象是不可变的,他就始终可以被重用
6. 清除过期的对象引用
只要类是自己管理内存,程序员应该警惕内存泄露问题
内存泄露的另一个常见来源是缓存
内存泄露的第三个常见来源是监听器和其他回调
7. 避免使用终结方法
除非是作为安全网或者为了终结非关键的本地资源,否则不要使用终结方法
使用终结方法,就要调用super.finalize
终结方法与公有的非final类关联起来,考虑使用终结方法守卫者
对于所有对象都通用的方法
8. 覆盖equals时请遵守通用约定
不用覆盖equals方法的情况
类的每个实例本质上都是唯一的
不关心类是否提供了“逻辑相等”的测试功能
超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的
类是私有的或者是包级私有的,可以确定它的equals方法不会被调用
约定
自反省
对称性
传递性
一致性
任何非null的引用x,x.equals(null)必须返回false
高质量equals的诀窍
使用==操作符检查“参数是否为这个对象的引用”
使用instanceof操作符检查“参数是否为正确的类型”
把参数转化为正确的类型
对于该类中的每个“关键域”,检查参数中的域是否与该对象中对应的域相匹配
float和double域使用Float.compare和Double.compare方法
可为空的对象引用域的比较 (field == null ? o.field == null : field.equal(o.field) 或者(field == o.field || (field != null && field.equals(o.field)))
当你编写完成了equal方法之后,应该问自己三个问题:它是否对称的,传递的,一致的
覆盖equal方法时总要覆盖hashCode
不要企图让equals方法过于智能
不要将equals声明中的Object对象替换为其他的类型,这样是重载,不是覆盖
9. 覆盖equals时总要覆盖hashCode
相等的对象必须拥有相等的散列码
result = result * 31 + c
hashCode()开销大时可以缓存
不要试图从散列码计算中排除掉一个对象的关键部分来提高性能
10. 始终要覆盖toString()方法
toString()方法应该返回所有值得关注的信息
对象太大的时候返回一个摘要信息
无论你是否要指定格式,都要应该在文档中明确的表明你的意图,要指定格式,就要严格地这样去做
无论是否指定格式,都为toString方法返回值中包含的所有信息提供一种编程式的访问途径
11. 谨慎的覆盖clone
同构造器一样,clone方法不应该在构造的过程中调用任何非final的方法
12. 考虑实现Comparable接口
类和接口
13.使类和成员的可访问性最小化
14.在公有类中使用访问方法而非公有域
15. 使可变性最小化
不可变类
不提供任何会修改对象状态的方法
保证类不会被扩展
使所有的域都是final的
使所有的域都成为私有的
确保对于任何可变组件的互斥访问
保护性拷贝
16.复合优先于继承
安全的继承
同一个包下,由同一个程序员控制
专门为了继承而设计,并且有很好的文档
继承打破了封装性
自用性
超类升级
复合/转发
包装类
decorator
17.要么为继承而设计,要么提供文档说明,要么就禁止继承
文档说明可覆盖的方法的自用性
方法或构造器调用了那些可覆盖的方法,调用顺序,调用结果,后续影响
那些情况下会调用可覆盖的方法
对于为了继承而设计的类,唯一的测试方法就是编写子类
构造器决不可调用可被覆盖的方法
18.接口优于抽象类
现有的类很容易被更新,以实现新的接口
接口是定义mixin的理想选择
不严格的讲,mixin是指这样的类型,类除了实现他的基本类型(primary type)之外,还可以实现这个mixin类型,以表明它提供了某些可供选择的行为
接口允许我们构造非层次结构的类型框架
!抽象类的演变要比接口容易的多
接口一旦被发行,并且已经被广泛实现,再想改变这个接口几乎是不可能的
19.接口只用于定义类型
常量接口会污染命名空间
20.类层次优于标签类
21.用函数对象表示策略
22.优先使用静态成员类
四种嵌套类(nested class)
静态成员类
非静态成员类
匿名类
局部类
序列化
74. 谨慎的实现Serializable接口
三个代价
大大降低了“改变这个类的实现”的灵活性
增加了bug和安全漏洞的可能性
随着类发行新的版本,相关的测试负担也增加了
Date,BigInteger这样的值类应该实现Serializable,大多数的集合类也应该如此。
代表活动实体的类,比如线程池,一般不应该实现Serializable
75. 考虑使用自定义的序列化形式
如果一个对象的物理表示法等同于它的逻辑内容,可能就适合于使用默认的序列化形式。
当一个对象的屋里表示法与他的逻辑数据内容有实质性的区别时,使用默认的序列化形式会有以下4个缺点
它使这个类的导出API永远的束缚在该类的内部表示上
它会消耗过多的空间
它会消耗过多的时间
它会引起栈溢出
不使用默认的序列化形式
writeObject
readObject
transient
不管选择了那种序列化的形式,都要为自己编写的每个可序列花的类声明一个显式的序列版本UID
76. 保护性的编写readObject方法
77. 对于实例控制,枚举类型优于readResolve
对于一个正在被反序列化的对象,如果它的类定义了一个readResolve方法,并且具备正确的声明,那么在反序列化之后,新建对象上的readResolve方法就会被调用,然后,该方法返回的对象引用将被返回,取代新建的对象
如果依赖readResolve进行实例控制,带有对象引用类型的所有实力域都必须为transient。
78. 考虑序列化代理代替序列化实例
writeReplace方法
并发
《Java Concurrency in Practice》
66. 同步访问共享的可变数据
同步
互斥
通信
volatile
i++不是原子操作
java.util.concurrent.atomic.AtomicLong
两种失败
活性失败(程序无法进行)
安全性失败(程序计算出错误的结果)
67. 避免过度同步
为了避免活性失败或者安全性失败,在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制
应该在同步区域内做尽可能少的工作
获得锁,检查共享数据,根据需要转换数据,然后放掉锁
如果一个可变的类要并发使用,应该使这个类变成事线程安全的,通过内部同步,你还可以获得明显比外部锁定整个对象更高的并发性。否则,就不要在内部同步,让客户在必要的时候从外部同步
在同步区域之外被调用的外来方法被称作“开发调用”
68. executor和task优先于线程
Executor Framework
java.util.concurrent.Executors
69. 并发工具优先于wait和notify
java.util.concurrent中的工具分成三类
Executor Framework
Concurrent Collection(并发集合)
Synchronizer(同步器)
70. 线程安全性的文档化
线程安全级别
不可变的
String
Long
BigInteger
无条件的线程安全
Random
ConcurrentHashMap
有条件的线程安全
Collections.synchronized包装返回的集合,他们的迭代器要求外部同步
非线程安全
ArrayList
HashMap
线程对立的
71. 慎用延迟初始化
对于实例域,使用双重检查模式
对于静态域,使用Lazy initialization holder class idiom
对于可以接受重复初始化的实力域,也可以考虑使用单重检查模式
72. 不要依赖线程调度器
任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的
73. 避免使用线程组
异常
57. 只针对异常的情况才使用异常
永远不应该用于正常的控制流
设计良好的API不应该强迫它的客户端为了正常的控制流而使用异常
58. 对可恢复的情况使用受检异常,对编程错误使用运行时异常
大多数运行时异常都表示提前违例
59. 避免不必要的使用受检异常
60. 优先使用标准的异常
常用异常
IllegalArgumentException
IllegalStateException
NullPointerException
IndexOutOfBoundsException
ConcurrentModificationException
UnsupportedOperationException
61. 抛出与抽象对应的异常
异常转义,异常链
62. 每个方法抛出的异常都要有文档
要为每个受检异常提供单独的throws子句,不要为未受检异常提供throws子句
63. 在细节信息中包含能补获失败的信息
64. 努力使失败保持原子性
不可变对象
执行操作之前检查参数有效性,可能会失败的计算部分都在对象状态被修改之前发生
编写恢复代码,临时拷贝
65. 不要忽略异常
通用程序设计
45.将局部变量的作用域最小化
在第一次使用它的地方声明
46.for-each循环优于传统的for循环
for-each可以遍历集合和数组以及任何实现了iterable接口的对象
无法使用for-each的地方
过滤
转换
平行迭代
47.了解和使用类库
java.lang
java.util
48.如果想要精确的答案,请避免使用float和double
货比计算使用BigDecimal,int或者long
49.基本类型优于装箱基本类型
50.如果其他类型更适合,则尽量避免使用字符串
字符串不适合代替其他值类型
字符串不合适代替枚举类型
字符串不适合代替聚集类型
字符串不适合代替能力表
51.当心字符串连接的性能
52.通过接口引用对象
53.接口优于反射机制
丧失了编译时类型检查的好处
执行反射访问所需要的代码非常笨拙和冗长
性能损失
54.谨慎的使用本地方法
55.谨慎的进行优化
很多计算上的过失都被归咎于效率(没有必要达到的效率),而不是任何其他的原因--甚至包括盲目的做傻事 --William A. Wulf
不要去计较效率上的一些小小的损失,在97%的情况下,不成熟的优化才是一切问题的根源 --Donald E. Knuth
在优化方面,我们应该遵守两条规则:规则1:不要进行优化。 规则二:(仅针对专家):还是不要进行优化--也就是说,在你还没有绝对清晰的未优化方案之前,请不要进行优化 --M. A. Jackson
不要费力去编写快的程序,应该努力编写好的程序
56.遵守普遍接受的命名惯例
方法
38. 检查参数的有效性
公有的方法,参数限制要写文档,参数无效抛出IllegalArgumentException,IndexOutOfBoundsException,NullPointerException的异常
非公有的方法通过断言来检查他们的参数
构造器的参数有效性检查是非常重要的
例外:检查昂贵,不切实际,隐含在计算过程中
39. 必要时进行保护性拷贝
TOCTOU攻击
对于参数类型可以被不可信任方子类化的参数,请不要使用clone方法进行保护性拷贝
40. 谨慎的设计方法签名
谨慎的选择方法的名称
不要过于追求提供便利的方法
避免过长的参数列表
方法分解
辅助类
从对象创建到方法调用都采用builder模式
对于参数类型,要优先使用接口而不是类
对于boolean类型的参数,要优先使用两个元素的枚举类型
41. 慎用重载
42. 慎用可变参数
printf和反射机制从变长参数中大大的受益
会影响一些性能
43. 返回0长度的数组或集合,而不是null
44. 为所有导出的api编写文档注释
枚举和注解
30. 用enum代替int常量
int枚举模式类型不安全,不方便使用
String枚举类型不安全,有性能问题
枚举类型可以带构造器,带方法,可以带抽象方法还可以嵌套
31. 用实例域代替序数
32. 用EnumSet代替位域
Enum.ordinal很少用到
33. 用EnumMap代替序数索引
34. 用接口模拟可伸缩的枚举
35. 注解优于命名模式
JUnit新旧版本
命名方式缺点
文字拼写错误会导致失败
无法确保它们只用于相应的程序元素上
没有提供将参数值与程序元素关联起来的好方法
注解
元注解
标记注解
36. 坚持使用Override注解
37. 用标记接口定义类型
泛型
术语: 1. 参数化的类型 List 2. 实际类型参数 String 3. 泛型 List 4. 形式类型参数 E 5. 无限制通配符类型 List 6. 原生态类型 List 7. 有限制类型参数 8. 递归类型限制 > 9. 有限制通配符类型 List 10. 泛型方法 static List asList(E[] a) 11. 类型令牌 String.class
23.请不要在新代码中使用原生态类型
声明中有一个或多个类型参数(type parameter)的类或者接口,就是泛型(generic)类或者接口
无限制的通配符类型(?),不能讲任何元素(null除外)放到Collection<?>中,否则会有编译时异常
例外
类文字(class literal)中必须使用原生态类型
List.class, String[].class, int.class合法,List<String>.class,List<?>.class不合法
instanceof 操作符
24.消除非首检警告
25.列表优于数组
数组是协变的,泛型则是不可变的
26.优先考虑泛型
使用泛型比使用需要在客户端进行转换的类型来的更加安全,也更加容易。在设计新的类型的时候,要确保它们不需要这种转换就可以使用。
27.优先使用泛型方法
静态工具方法尤其适合于泛型化
public static HashMap newHashMap() { return new HashMap(); } Map> aMap = newHashMap();
泛型单例工厂
递归类型限制
28. 利用有限制的通配符来提升API的灵活性
PECS
29. 优先考虑安全的异构容器