EffectiveJava学习笔记
第二章 创建与销毁对象
1、考虑使用静态工厂方法代替构造器
优点:
静态工厂方法在方法命名上更具有可读性【Good】
静态工厂方法不需要每次在被调用的时候都构造一个新的对象【Good】
静态工厂方法还可以返回该类型的子类对象【Good】
静态工厂方法还可以简化参数化类型的对象创建【Not Good】
缺点:
如果在一个类中将构造函数设为priavte,只提供静态工厂方法来创建对象,那么就不能通过继承的方式来扩展该类;
解决办法:可以进行组合;【OK】
静态工厂方法与其他静态方法实际上没有任何区别;【Good】
解决办法:命名静态工厂方法的时候遵循一定的规则。
valueOf、of、getInstance、newInstance、getType、newType
2、遇到多个构造器参数时要考虑用构建器【Builder模式】
优点:
使用Builder Pattern。【Good,大于4个参数,使用这种方式不需要考虑线程安全问题】
缺点:
注重性能上的情况下,就会出现问题。(由于需要先创建构建器,再生成可用对象)
3、用私有构造器或者枚举类型强化Singleton属性
使用单元枚举类型实现Singleton。【Good】
4、通过私有构造器强化不可实例化的能力
优点:
工具类不希望被实例化,将构造器变成私有,并且构造器中防止被内部调用抛出一个异常。【Good】
缺点:
子类不能调用父类的构造器。
5、避免创建不必要的对象【改】【作用域】
对象为不可变的,就可以被重用。
静态代码块初始化,在类第一次使用时才进行初始化。【Good】
备注:优先使用基本类型而不是装箱类型。
维护对象池,特别是数据库连接池。
6、消除过期的对象引用【内存泄露问题】
注意1:只要类是自己管理内存,程序员就应该警惕内存泄漏问题,一旦元素被释放,则该元素包含的任何对象引用都应该被清空。
注意2:内存泄漏的另一个常见来源是缓存。(WeakHashMap代表缓存,该键的外部引用而不是由值决定时,WeakHashMap才有用)(缓存项的生命周期是否有意义,可用LinkedHashMap的removeEldestEntry)
注意3:内存泄漏的第三个常见来源是监听器和其他回调。(必须要有注册的和取消注册的,WeakHashMap中的键)
7、避免使用终结方法(finalize)
缺点:
在于不能保证会被及时地执行;
根本就不保证它们会被执行;
如果在终结方法中抛出异常,这种异常可以被忽略并且该对象的终结过程也会终止;
性能损失;(时间)
解决方法:优先使用try{} finally {} 方法;
本地对等体还没查阅。
第三章 对于所有对象都通用的方法
8、覆盖equals时请遵守通用约定(值类才需要覆盖,逻辑上要求相等而不是想了解他们是否指向同一个对象)
满足条件:
自反性、对称性x.equals(y)与y.equals(x)结果要一样、传递性、一致性,null返回fasle。
备注:
instanceof 左边的对象是否是它右边的类的实例或者是子类的一个实例。
Object中的equals方法是 this == object.
Object中的hashcode方法是返回32位内存地址。
结论:【Good】
1)使用==操作符检查“参数是否为这个对象的引用”
2)使用instanceof操作符检查“参数是否为正确的类型”(可以检查null)
3)把参数转换成正确的类型(强制类型转换)
4)对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应域相匹配(除了float与double的基本类型都可以用==进行比较,对于float与double可使用Float.compare与Double.compare方法,对象引用域使用equals,)
5)当你编写完成了equals方法之后,应该问自己三个问题:它是否对称的、传递的、一致的?
覆盖equals时总是要覆盖hashCode,不要将equals声明中的Object对象替换为其他的类型。
9、覆盖equals时总是要覆盖hashCode
(equals相等,hashCode必须相等;equals不相等,hashCode可能相等或者不相等)
结论:【Good】
1) 把某个非零分常数值,比如有说17,保存在一个名为result的int类型的变量
2) 对于对象中每个关键域f(指equals方法中涉及的每个域,排除冗余域),完成以下步骤:
A) 为该域计算int类型的散列码c:(不同类型处理不一样)
B) 将c代入下面公式得到hashCode的返回值:result = 31 * result + c
3) 返回result
4) 询问自己“相等的实例是否都具有相等的散列码”
10、始终要覆盖toString【Good】
要返回对象中包含的所有值得关注的信息。
11、谨慎地覆盖clone【无需构造器可以创建对象】
如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException异常。
备注:
clone方法就是另一个构造器,你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件。
(后面HashTable未看完)
12、考虑实现Comparable接口(允许同性比较,允许执行顺序比较)
一个值类,具有非常明显的内在排序关系,比如:按字母顺序、按数值顺序或者按年代顺序。
1) sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
2) (x.compareTo(y) > 0 && y.compareTo(z) > 0) 暗示着 x.compareTo(z) > 0
3) 建议:(x.compareTo(y) == 0 ) == (x.equals(y))
备注:
实现接口时,注意两个比较field是否存在溢出现象,如果不存在,则可以使用返回值为符号优化程序,否则,返回0,-1,1。
第四章 类与接口(接口都是public)
13、使类和成员的可访问性最小化(private default protected public)【Good】
1) 尽可能地使每个类【private与 public】或者成员不被外界访问;
2) 若类实现了serializable接口,这些类成员可能会被“泄漏”到到处的API中。
3) 规则限制:如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。
备注:
静态常量命名:这种域的名称由大写字母组成,单词之间用下划线隔开。
除了共有静态final域的特殊情形之外,公有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都是不可变的。
14、在公有类中使用访问方法而非公有域【Good】
多用getter与setter方法,别把类成员变成public。
15、使可变性最小化【搞成不可变类】
不可变类:实例不能被修改的类,即每个实例中包好的所有信息都必须在创建该实例时候就提供,并在对象的整个生命周期内固定不变。(例如String)
1) 不要提供任何会修改对象状态的方法
2) 保证类不会被扩展(一般做法:final)
3) 使所有的域都是final的
4) 使所有的域都成为私有(不建议)
5) 确保对于任何可变组件的互斥访问
备注:
不可变类的运算后,是创建并返回新的不可变类实例,而不是修改这个实例。(线程安全,只有创建时的状态)
构造器应该创建完全初始化的对象,并建立起所有的约束关系,不要在构造器或者静态工厂之外再提供公有的初始化方法。能加final关键字尽量加。
16、复合优先于继承
包内继承是安全的【Good】
复合:
新的类中增加一个私有域,它引用现有类的一个实例。(包装类和可重用的转发类)
备注:
包装类不适用在回调框架中。(回调,对象把自身的引用传递给其他对象,用于后续的调用)
继承:
违背封装原则,只有子类与父类之间确实存在父子类型关系时,使用继承才是恰当的。
17、要么为继承而设计,并提供文档说明,要么就禁止继承
参考AbstractList中的removeRange方法。
对于为了继承而设计的类,唯一的测试方法就是编写子类。(3个子类,无一使用protected的成员,就应该把它作为private)
构造器决不能调用可被覆盖的方法。
父类不应该实现了Cloneable与Serializabale接口,无论是clone还是readObject,都不可以调用可覆盖的方法,不管直接还是间接,若要实现Serializable,必须把readResolve或者writeReplace方法设为保护。
18、接口优于抽象类【Good】
(未读完)
19、接口只用于定义类型
错误:(不应该被用于导出常量)
正确:
如果这些常量与某个现有的类或者接口紧密相关,就应该把这些常量添加到这个类或者接口中。
比如:使用枚举类型【最好】。
20、类层次优于标签类【Good】
使用抽象类+子类办法,替代标签类。
备注:参考链接http://blog.csdn.net/hosterZhu/article/details/9834011
21、用函数对象表示策略【Good】
函数指针的主要用途就是实现策略模式。
接口+具体策略。
Eg:
22、优先考虑静态成员【Good,防止内存泄漏】【如果声明类成员不要求访问外围实例,就要始终把static修饰符放在它的声明当中。】
四种嵌套类:静态成员类(辅助类,外部类一起使用),非静态成员类、匿名类与局部类,后面三种为内部类。【嵌套类存在的目的应该只为了它的外围类提供服务,如果嵌套类将来可能用于其他的某个环境中,它就是顶层类。】
非静态成员类:每个实例都隐含着与外围类的一个外围实例相关联。(使用enclosingInstance.new MemberClass(args)构建)(例如:Adapter)
匿名类:动态地创建函数对象、Runnable、Thread或者TimerTask。
局部类:用得最少。
备注:参考链接,http://www.cnblogs.com/wangliyue/p/4468962.html
第五章 泛型
23、请不要在新代码中使用原生态类型【Good,因为失去了编译时类型检查的安全性,所以使用<>,编译时就发现】
泛型类和接口统称为泛型。
无限制通配符类型:?,可读做未知某个类型的集合,安全。
24、消除非受检警告【Good】(需要在声明中使用,尽可能在小的范围中使用)
如果无法消除警告,同时可以证明引起警告的代码是类型安全的,可以使用一个@SuppressWarnings(“unchecked”)注解来禁止这条警告。(必须添加注释说明为什么这样做是安全的)
25、列表优先于数组【Good,数组和泛型不能很好地混合使用,如果混合使用得到编译时错误或者警告,应用列表代替数组】
创建泛型数组是非法的,因为数组是不安全的。
数组是具体化的,运行时检查他们的类型信息;
泛型是不可具体化,运行时表示法包含的信息比编译时包含的信息更少的类型。
26、优先考虑泛型【GOOD】
【一看标题就知道】
27、优先考虑泛型方法【GOOD】【静态工具方法尤其适合于泛型化,具有类型推导作用】
(115-116未看)
28、利用有限制通配符来提升API的灵活性【广泛使用的类库】
29、优先考虑类型安全的异构容器
将Class对象作为键。
使用:
结果:
第六章 枚举与注解
30、用enum代替int常量(java枚举本质是int值,只有0,1,2常量时才进行替换)【GOOD】
优点:
1、 传递类型错误的值时,会导致编译时错误;
2、 toString方法将枚举转换成可打印字符串;
备注:枚举就是个单例类,不能被继承。Enum就是不可变类,所有域都是final。
【策略枚举还没看】
31、用实例域代替序数
错误:
正确:
避免使用ordinal,这是返回枚举类型的序数,除非是运用在EnumSet与EnumMap。
32、用EnumSet代替位域(位域就是用OR位运算将几个常量合并到一个集合中)
错误:
正确:
33、用EnumMap代替序数索引
由于编译器无法知道序数和数组索引之间的关系。
34、用接口模拟可伸缩的枚举
【跳过没看】
35、注解优先于命名模式
【使用注解得配合反射一起用更好】
36、坚持使用Override注解
表示:被注解的方法声明覆盖了超类型中的一个声明,编译过程给你报错误消息。
37、用标记接口定义类型【参考Serializable】(如果想要定义类型,一定要使用接口)
优于标记注解:
1、 标记接口定义的类型是由被标记类的实例实现的,标记注解则没有定义这样的类型。
2、 标记接口可以被更加精确地锁定。
标记注解优于标记接口的最大优点:通过默认的方式添加一个或者多个注解类型元素,给已被使用的注解类型添加更多信息。
使用场景:
注解:标记应用到任何元素而不是类或者接口;
接口:永远限制这个标记只用于特殊接口的元素。
第七章 方法
38、检查参数的有效性
养成检查方法参数有效性的习惯(eg.引用对象不为null,字符串长度大于0,不超基本类型的最大最小值。)
39、必要时进行保护性拷贝(因为java中传递对象是引用)
保护性拷贝是在检查参数的有效性之前进行,并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象。(万一多线程改变了该值呢?)
防御:
使返回可变内部域的保护性拷贝。
防御:
备注:
类具有从客户端得到或者返回到客户端的可变组件,类就必须保护性地拷贝组件。
40、谨慎设计方法签名
技巧:
1、 方法命名
2、 避免过长的参数列表
a) 少于四个,多于四个使用Builder
b) 频繁使用的,创建辅助类(纸牌这个对象,传入点数和花色即可)
41、慎用重载
【肯定在某种情况下不能使用重载,因此跳过没看】
42、慎用可变参数
【了解可变参数概念,因此跳过没看】
43、返回零长度的数组或者集合,而不是null
【GOOD,看标题就知道内容了】
44、为所有到处的API元素编写文档注释
【跳过不看,以后用到再看】
第八章 通用程序设计
45、将局部变量的作用域最小化
【GOOD,看标题就知道内容,跳过不看】
46、for-each循环优于传统的for循环
【一看标题就知道干嘛,因此跳过没看,只看结果】
有三种情况是无法使用for-each循环【删、改】
1、 过滤:遍历并删除指定的元素,就需要使用显示的迭代器,以便可以调用它的remove方法。
2、 转换:遍历并替换指定索引的元素。
3、 平行迭代:并行地遍历多个集合(即控制同步前移,还没有遇到这种情况)
47、了解和使用类库【java.lang、java.util、java.io】
通过使用标准类库,可以充分利用这些编写标准类库的专家的知识,以及在你之前的其他人的使用经验。
1、 在每个java发行版本都应该了解新的特性;
2、 每个程序员应该把时间花在应用程序上,而不是底层的细节上。
Eg:Random.nextInt à代替random.nextInt()。
48、如果需要精确的答案,请避免使用float和double【BigDecimal】
使用BigDecimal、int或者long进行货币计算;
使用BigDecimal类型代替double。
49、基本类型优先于装箱基本类型【考虑==操作符】
【GOOD,一看标题就知道结果,可跳过】
50、如果其他类型更合适,则避免使用字符串
场景:
1、 字符串不适合代替其他的值类型【当数据(网络、文件)传到程序之中,应该转换为适当的数值类型】
2、 字符串不适合代替枚举类型
3、 字符串不适合代替聚集类型
51、当心字符串连接的性能【使用StringBuilder代替String,在需要重复操作的情景下】
记住一点:想要禅城单独一行的输出,或者构造一个字符串来表示一个较小的、大小固定的对象是可以用+连接,但是不能运用在为连接n个字符串而重复使用+。需要n的平方级的时间【使用StringBuilder代替String】
52、通过接口引用对象
【GOOD,向上转型】
不能使用:
假设具体类中有我们需要使用的具体函数,那么不能使用。
否则:
一律使用接口或者抽象类,这样我们的程序更加灵活。
53、接口优于反射机制
【反射机制的使用场景:类浏览器、对象监视器、代码分析工具、解释型的内嵌式系统以及RPC(远程过程调用)】
54、谨慎地使用本地方法
【本地方法使用场景:访问底层的资源、遗留代码库,另外本地语言是不安全的】
55、谨慎地进行优化【努力编写好的程序而不是快的程序】
在你还没有绝对清晰的未优化方案之前,请不要进行优化。
使用性能剖析工具帮助优化,并且使用更优化的算法来替代旧的算法。
56、遵守普通接受的命名惯例【驼峰命名法】
包名称:不超过8个字符,鼓励使用有意义的缩写:字母的前缀4个或者首字母缩写;
类和接口的名称(包含枚举和注解):一个或者多个单词(第一个单词大写,尽量避免缩写,除非是通用的max或者min);
方法和域的名称:一个或者多个单词(第一个单词小写);
常量域:静态+final,所有单词大写+下划线分割;
局部变量名称:根据环境随意写;
备注语法命名:
1、接口可后面加able
2、is开头可用于返回boolean值
3、get用于获取属性的方法命名,to转换对象类型的方法(toString)。
第九章 异常
57、只针对异常的情况才使用异常
【不要将他们用于普通的控制流,数组越界的情况(为什么有人这样用,因为JVM每次都会去判断数组是否越界,以为这样会损耗性能,其实随着时间的推移,会被修复的,所以不必要担心)】
58、对可恢复的情况使用受检异常,对编程错误使用运行时异常
期望调用者能够适当地恢复:
抛出受检异常(电话费不足的例子)
不能恢复:
运行时异常+错误。
59、避免不必要地使用受检的异常
60、优先使用标准的异常
常用的异常:
备注:建立在语义的基础上,而不是建立在名称的基础上。
61、抛出与抽象相对应的异常
异常转译:
更高层的实现应该捕获底层的异常,同时抛出可以按照高层抽象进行解释的异常。
62、每个方法抛出的异常都要有文档
【一看标题就知道内容,不看】
63、在细节消息中包含能捕获失败的信息
【意思大概:看Log】
64、努力使失败保持原子性
失败原子性:
失败的方法调用应该使对象保持在被调用之前的状态。
三个办法:
1、 一个不可变的对象;
2、 在执行操作之前检查参数的有效性;
3、 临时拷贝。
65、不要忽略异常
【GOOD】
第十章 并发
66、同步访问共享的可变数据(使用volatile)
当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。【如果读和写的操作没有被同步,同步就不会起作用。】
虚拟机-->
回顾volatile,印象笔记。
另外,不要使用Thread.stop会使数据遭到破坏。
67、避免过度同步
可重入的锁简化了多线程的面向对象程序的构造,但是会将活性失败变成安全性失败。
CopyOnWriteArrayList通过重新拷贝整个底层数组,适用于观察者列表。【GOOD】
通常,你应该在同步区域内做尽可能少的工作,获得锁,检查共享数据,根据需要转换数据,然后放掉锁。如果你必须要执行某个很耗时的动作,则应该设法把这个动作移到同步区域外面。(避免死锁和数据破坏,千万不要从同步区域内部调用外来方法。)
68、executor和task优先于线程【使用线程池管理线程】
工作队列:
ThreadPoolExecutor:允许你控制线程池操作的几乎每个方面。
ScheduledThreadPoolExecutor代替Timer。
69、并发工具优先于wait和notify
Executor Framework:线程池
并发集合:
优先使用ConcurrentHashMap替换Collections.synchronizedMap或者Hastable。
阻塞操作:BlockingQueue,从队列中删除并返回了头元素,如果队列为空,就等待。(用于工作队列,生产者-消费者队列)
同步器:一些使线程能够等待另一个线程的对象。
常用: CountDownLatch和Semaphore。
(倒计数锁存器)CountDownLatch:一次性的障碍,允许一个或者多个线程等待一个或者多个其他线程来做某些事情。
始终应该使用wait循环模式来调用wait方法,永远不要再循环之外调用wait方法,循环会在等待之前和之后测试条件,优先使用notifyAll,而不是使用notify,若使用notify,要确保程序的活性。
疑问:在没有通知的情况下,等待线程也可能(但很少)会苏醒起来?【没碰到这种情况】
备注:
system.nanoTime:在间歇式的定时当中,始终应该优先使用。(替代System.currentTimeMills)
70、线程安全性的文档化
【未看】
71、慎用延迟初始化【正常初始化由于延迟初始化】
延迟初始化,如果永远不需要这个值,这个域就永远不会被初始化,适用于静态域、实例域。
若要用的话:
对于实例域,使用双重检查模式;
对于静态域,使用lazy initialization holder class idiom
特殊情况:对于实例域可接受重复初始化的,可考虑单重检查模式。
72、不要依赖于线程调度器
不依赖Thread.yield或者线程优先级。
73、避免使用线程组(初衷作为一种隔离applet(小程序)的机制)
使用线程池。