Effective Java
2017-01-23 17:17:09 0 举报
AI智能生成
Effective Java
作者其他创作
大纲/内容
第1章 引言<br>
最有效的使用Java语言及其类库:java.lang、java.util,java.util.concurrent,java.io
第2章 创建和销毁对象
第1条:考虑用静态工厂方法替代构造器
静态方法可以有特定的名称
构造函数只能通过参数来区分,静态方法更易读
不必在每次调用时创建一个新的对象
避免重复新建对象
能为重复的调用返回相同的对象,可以对实例严格控制
保证单例化或不可实例化
保证不可变类不会存在两个相等的实例(当且仅当a==b,才有a.equals(b)为TRUE)
可以返回原返回类型的任何子类对象
这种做法使得API可以返回对象又不会使得对象的类公有化。<br>
同时暴露出来的是接口声明,用户不需要关心得到的对象的类,只需要关心这个是声明接口的子类(实现类)即可。<br>
这主要是为了提升软件的可维护性和性能,因为返回的对象也会随着软件版本的变更而不同。这种模式也是服务提供者框架(Service Provider Framework)的基础。<br>
创建参数化类型实例的时候,创建代码更加简洁
Map<String,List<String>> m= new HashMap<String, List<String>>();<br>
<div>public static <k,v> HashMap<k,v> newInstance(){</div><div> return new HashMap<k,v>();</div><div>}</div><div><br></div><div>Map<String,List<String>> m= HashMap.newInstance();</div>
它与其他静态方法没有区别,使用约定的命名习惯来规避这个缺点
<div>valueOf——不太严格地讲,该方返回的实例与它的参数具有相同的值。这样的静态工厂方法实际上是类型转换方法。 </div><div>of——valueOf的一种更为简洁的替换,在EnumSet中使用并流行起来。 </div><div>getInstance——返回的实例是通过方法的参数来描述的,但是不能够说与参数具有同样的值。对于Singleton来说,该方法没有参数,并返回唯一值。 </div><div>newInstance——像getInstance一样,但newInstance能够确保返回每个实例都与把有其他实例不同。 </div><div>getType——像getInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型。 </div><div>newType——像newInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型。</div>
类如果不含有public或者protected的构造器,就不能被子类化
第2条:遇到多个构造器参数时考虑构建器
重叠构造器模式
当你想要创建实例时,就利用参数列表最短的构造器。虽然重叠构造器模式可行,但是当有许多参数的时候,客户端代码会行难编写,并且难以阅读,随着参数的增加,它很快就失去控制。<br>
JavaBean模式
创建实例容易,产生的代码读起来也容易
构造过程被分到几个调用中,在构造过程中JavaBean可能不一致
JavaBean不能做成不可变类,需要更加保证它的线程安全
Builder模式
像重叠构造器的安全性,也有JavaBean的易读性
不直接生成想要的对象,让客户端用需要的参数调用构造器(或静态工厂),得到一个builder对象
比重叠构造器更加冗长,适合多个参数使用
第3条:用私有构造器或者枚举类型强化Singleton属性
公有化静态成员
私有化静态成员,并提供静态工厂返回实例的引用
最佳方式:单个元素的枚举类型
第4条:通过私有构造器强化不可实例化的能力
私有化构造器,缺点是无法子类化
第5条:避免创建不必要的对象
能够重用的对象尽量不要创建新对象,如数据库连接池
不可变的部分可以用静态来声明
第6条:消除过期的对象引用
<img src="http://img.blog.csdn.net/20150628151703131">
第一种是类自己管理内存,程序员就应该警惕内存泄露问题;一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。<br>
第二种是来源于缓存;<br>
如果你正好要实现这样的缓存:只要在缓存之外存在对某个项的键的引用,该项就有意义,那么就可以用WeakHashMap(弱键映射,允许垃圾回收器回收无外界引用指向象Map中键)代表缓存;当缓存中的项过期之后,它们就会自动被删除。记住只有当所要的缓存项的任命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用外。<br>
另外,随着时间的推移,早些存入的项会变得越来越没有价值,在这种情况下,缓存应该时不时地清除掉没用的项。我们可以使用的是LinkedHashMap来实现,可以在给缓存添加新条目的时候顺便进行清理<br>
第三种是来源于监听器和其他回调。常见的解决办法是使用弱应用(weak reference),例如,只将它们保存成WeakHashMap中的键。<br>
消除过期引用最好的方法是让包含该引用的变量结束其生命周期。<br>
第7条:避免使用终结方法
终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的。当对象不再被使用,应采取使用显式终止,例如InputStream的close方法。如果子类覆盖了超类的终结方法,那么子类的终结方法必须显式调用超类的终结方法,否则超类的终结方法则永远不会被调用。<br>
第3章 对所有对象都通用的方法
第8条:覆盖equals时请遵守通用约定
不覆盖equals的情况
类的每个实例本质上都是唯一的
代表实体而不是值,如Thread
不关心类是否提供了逻辑相等(logical equality)的测试功能
java.util.Random 覆盖equals用来判断生成的随机数序列是否相同,理论上不会有这种需要
超类已经覆盖了equals,从超类继承过来的行为对子类也是合适的
Set 从 abstractSet继承equals
List 从 abstractList
Map 从 abstractMap
类是私有的或包私有的,可以确定它的equals方法永远不会被调用
可以不覆盖,或者覆盖为抛异常
覆盖的情况
对象有逻辑相等的概念(不等同于对象等同)
超类没有覆盖equals
值类(Value vlass) Integer、Date
例外:值类,实例受控,每个值至多只存在一个对象。枚举类型。 对于这种类型,逻辑相同和对象等同是一回事
覆盖要遵守的约定
自反性。对任何非null的引用值x,x.equals(x)返回true
对称性。非null的x、y。当且仅当x.equals(y)为true时,y.equals(x)为true
传递性。非null的x、y、z。x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)返回true
一致性。<span style="color: rgb(0, 0, 0); font-family: "microsoft yahei"; font-size: 14px;">对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致的返回false。</span>
非null的x,x.equals(null)返回false
高质量equals方法的诀窍
1.使用==操作符检查“参数是否为这么对象的引用”
2.使用instantOf检查“参数是否为正确的类型”
3.把参数转换为正确的类型
4.对于该类中的每个“关键”域,检查参数中的域是否与该对象中的对应域想匹配
<p style="margin: 10px auto; padding: 0px;"><font color="#000000" face="Verdana, Arial, Helvetica, sans-serif"><span style="font-size: 15px;">不是float和double的基本类型域:直接使用==</span></font></p><p style="margin: 10px auto; padding: 0px;"><font color="#000000" face="Verdana, Arial, Helvetica, sans-serif"><span style="font-size: 15px;">引用域:递归调用equals方法</span></font></p><p style="margin: 10px auto; padding: 0px;"><font color="#000000" face="Verdana, Arial, Helvetica, sans-serif"><span style="font-size: 15px;">float域:Float.compare方法</span></font></p><p style="margin: 10px auto; padding: 0px;"><font color="#000000" face="Verdana, Arial, Helvetica, sans-serif"><span style="font-size: 15px;">double域:Double.compare方法</span></font></p>
5.当你编写完成equals方法后,应该问三个问题,是否是对称的、传递的、一致的
告诫
覆盖equals时总要覆盖hashCode
不要企图让equals过于智能
不要将equals声明中的Object对象替换为其它类型
public boolean equals(Myclass o) {...}
没有覆盖Object.equals,而是重载了equals。
当两种方法返回的结果相同,这种覆盖是可以接受的。
某种情况下,可以稍微改善性能,但与增加的复杂性相比,这种做法是不值得的。
@Override注解的用法一致,可以防止这种错误。
第9条:覆盖equals总要覆盖hashCode
Object.hashCode的约定。违反第二条约定,相等的对象必须拥有相等的散列码(hash code)
导致所有基于散列的类(HashSet、HashMap、HashTable)不能正常工作,同一个对象产生不同的hashcode
浅谈java中的hashCode方法 http://www.cnblogs.com/dolphin0520/p/3681042.html
hashCode方法
<p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;">1、 把某个非零的常数值,比如说17(值17是任选的,因为即使2.a步骤中计算出的散列值为0初始域也会影响到散列值,这样会大大的避免了冲突的可能性,所以这个一般是一个非零的常数值),保存在一个名为result的int的类型变量中。</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;">2、 对于对象中每个键域f(指equals方法中涉及的每个域),完成以下步骤:</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> a. 为该域计算int类型的散列码c:</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> I、 如果该域是boolean类型,则计算(f ? 1 : 0)。</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> II、 如果该域是byte、char、short或者int类型,则计算(int)f。</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> III、如果该域是long类型,则计算(int)(f ^ (f >>> 32))。</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> IV、 如果该域是float类型,则计算Float.floatToIntBits(f),即将内存中的浮点数二进制位看作是整型的二进制,并将返回整型结果。</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> V、 如果该域是dobule类型,则计算Double.doubleToLongBits(f),然后按照步骤2.a.III。</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> VI、 如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals方式来比较这个域,则同样为这个域递归地调用hashCode。如果这个域的值为null,则返回0。</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> VII、如果该域是一个数组,则要把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤2.b中的做法把这些散列值组合起来。如果数组的每个元素都需要求,则可以使用1.5版本发行的Arrays.hashCode方法。</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> b. 按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中:</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> result = 31 * result + c;</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;"> 步骤2.b中的乘法部分使得散列值依赖于域的顺序,如果一个类包含多个包含多个相似的域,这样的乘法运算就会产生一个更好的散列函数。例如,如果String散列函数省略了这个乘法部分,那么只要组成该字符串的字符是一样的,而不管它们的排列的顺序,则会导致只要有相同字符内容的字符串就会相等的问题,而String的equals方法是与字符排序顺序有关的。另外,之所以选择31,是因为它是一个奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会全丢失,因为与2相乘等价于移位运算。31还有个很好的特性,即用移位和减法来代替乘法,可以得到更好的性能:31 * i = i ^32 - i = (i << 5) – i,现代的VM可以自动完成这种优化。</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;">3、 返回result。</p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt 18pt; padding: 0px; line-height: normal; text-indent: -18pt; word-break: normal; text-size-adjust: auto;">4、 写完了hashCode方法后,问问自己“相等的实例是否都具有相等的散列码”。</p>
第10条:始终要覆盖toString
返回“简洁,但内容丰富,并易于阅读的表达形式”
在文档中写明 实现toString是否指定返回值的格式。
指定格式,对于值类(电话号码),易于阅读
但是如果此格式大量应用,就要一直保持这种格式,缺乏灵活性
第11条:谨慎地覆盖clone
如果一个类实现cloneable接口,那么Object的clone方法就返回该对象的逐域拷贝,否则抛出CloneNotSupportedException异常。
深拷贝 浅拷贝 容易出现问题
第12条:考虑实现comparable接口
类内部有明确的顺序关系,且需要排序
第4章 类和接口
第13条:使类和成员的可访问性最小化
尽可能地使每个类或者成员不被外界访问
<span style="color: rgb(85, 85, 85); font-family: "microsoft yahei"; font-size: 14px;">私有的东西将不被智能提示和导出到API文档中,这是内部的实现,而不是给外部暴露用,为了不使用户(使用这个类的人)疑惑和增加类的可变性,类暴露的接口,不是private修饰的都是可以被使用的,那么如果别人使用了你不该使用的部分,而你这个类下个版本更新,却删除了这个方法,将不止导致用户对于你的类的厌恶,而且会破坏原有的代码,所以就尽量私有化。</span><br style="box-sizing: border-box; color: rgb(85, 85, 85); font-family: "microsoft yahei"; font-size: 14px;"><span style="color: rgb(85, 85, 85); font-family: "microsoft yahei"; font-size: 14px;">类的可访问性,缩小到最小,除非有必须暴露这部分的理由,否则就该是private,Effective c++中也提到过。</span>
实例域决不能是公有的
如果公有,就放弃了对存储在此域中的值进行限制的能力,放弃了域不可变的能力
包含公有可变类的域不是线程安全的
第14条:在公有类中使用访问方法而非公有域
退化类,用公有访问方法(getter)和私有域替代
对可变类,用私有域和公有设值方法(setter)替代
如果类是包级私有或者是私有的嵌套类,直接暴露它的数据域并没有本质错误
第15条:使可变性最小化
1.不要提供任何会修改对象状态的方法
2.保证类不会被扩展
3.使得所有的域都是final的
4.使所有的域都成为私有的
5.确保对于任何可变组件的互斥访问
不可变对象本质上是线程安全的,它们不要求同步
第16条:复合优先于继承
<font color="#555555" face="microsoft yahei"><span style="font-size: 14px;">继承代表着类的作者承认子类 is - a 父类的关系,在后续的API中能够这样使用,而 复合——将其他东西作为这个类的成员来调用只是承诺 has - a的关系,继承会破坏封装,导致父类和子类的关系耦合,修改也麻烦,一改就是两个或者更多…</span></font><br>
由于超类在未来新的发行版本做出修改,如果子类在这时和超类发生了冲突,那将无法通过编译,这里我们会引入复合的概念。复合既是把需要用到的超类封装到一个新的包装类,包装类的成员中包含了这个需要使用的超类,这样使得该包装类保留了超类的功能特性,同时也提供了包装类后续继承的扩展性。<br>
<table cellspacing="0" cellpadding="2" width="1047" border="0" style="margin: 0px; padding: 0px; border-collapse: collapse; border-spacing: 0px; max-width: 850px; border: 1px solid silver; word-break: break-word; color: rgb(51, 51, 51); font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;"><tbody style="margin: 0px; padding: 0px;"><tr style="margin: 0px; padding: 0px;"><td valign="top" width="483" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">组 合 关 系</td><td valign="top" width="562" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">继 承 关 系</td></tr><tr style="margin: 0px; padding: 0px;"><td valign="top" width="483" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立</td><td valign="top" width="562" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性</td></tr><tr style="margin: 0px; padding: 0px;"><td valign="top" width="483" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">优点:具有较好的可扩展性</td><td valign="top" width="562" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">缺点:支持扩展,但是往往以增加系统结构的复杂度为代价</td></tr><tr style="margin: 0px; padding: 0px;"><td valign="top" width="483" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象</td><td valign="top" width="562" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">缺点:不支持动态继承。在运行时,子类无法选择不同的父类</td></tr><tr style="margin: 0px; padding: 0px;"><td valign="top" width="483" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口</td><td valign="top" width="562" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">缺点:子类不能改变父类的接口</td></tr><tr style="margin: 0px; padding: 0px;"><td valign="top" width="483" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">缺点:整体类不能自动获得和局部类同样的接口</td><td valign="top" width="562" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">优点:子类能自动继承父类的接口</td></tr><tr style="margin: 0px; padding: 0px;"><td valign="top" width="483" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">缺点:创建整体类的对象时,需要创建所有局部类的对象</td><td valign="top" width="562" style="margin: 0px; padding: 3px; border: 1px solid silver; border-collapse: collapse;">优点:创建子类的对象时,无须创建父类的对象</td></tr></tbody></table>
第17条:要么为继承而设计,并提供文档说明,要么就禁止继承
第16条提醒我们,继承自一个不是为了继承而设计、并且没有文档说明的“外来”类是危险的。
<div>如果一个类是专为了继承而设计的,要求有哪些呢?</div><div>首先,该类的文档必须精确地描述覆盖每个方法所带来的影响。换句话说,该类必须有文档说明它可覆盖的方法的自用性(self-use)。对于每个公有的或受保护的方法或者构造器,它的文档必须指明该方法或者构造器调用了哪些可覆盖的方法,是以什么顺序调用的,每个调用的结果以是如何影响后续的处理过程的(如在哪些情况下它会调用可覆盖的方法,必须在文档中说明)。</div><div>按照惯例,如果方法调用到了可覆盖的方法,在它的文档注释的末尾该包含关于这些调用的描述信息。这段描述信息要以这样的句子开头:“This implementation.(该实现...)”。这样的句子不是在表明该方法可能会随着版本的变迁而改变,而是表明了该方法的内部工作情况。</div>
<p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt; padding: 0px; line-height: normal; word-break: normal; text-size-adjust: auto;"><font color="#000000" face="Verdana, Arial, Helvetica, sans-serif">下面是摘自java.util.AbstractCollection的规范:</font></p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt; padding: 0px; line-height: normal; word-break: normal; text-size-adjust: auto;"><font color="#000000" face="Verdana, Arial, Helvetica, sans-serif"> </font></p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt; padding: 0px; line-height: normal; word-break: normal; text-size-adjust: auto;"><font color="#000000" face="Verdana, Arial, Helvetica, sans-serif">public boolean remove(Object o):</font></p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt; padding: 0px; line-height: normal; word-break: normal; text-size-adjust: auto;"><font color="#000000" face="Verdana, Arial, Helvetica, sans-serif">从此 collection 中移除指定元素的单个实例(如果存在)(可选操作)。更正式地说,如果该 collection 包含一个或多个满足 (o==null ? e==null : o.equals(e)) 的元素 e,则移除 e。如果该 collection 包含指定的元素(或等价元素,如果该 collection 随调用的结果发生变化),则返回 true。</font></p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt; padding: 0px; line-height: normal; word-break: normal; text-size-adjust: auto;"><font color="#000000" face="Verdana, Arial, Helvetica, sans-serif">此实现在该 collection 上进行迭代,查找指定的元素。如果找到该元素,那么它会使用迭代器的 remove 方法从该 collection 中移除该元素。注意,如果此 collection 的 iterator 方法所返回的迭代器无法实现 remove 方法,并且此 collection 包含指定的对象,那么此实现会抛出 UnsupportedOperationException。</font></p><p class="MsoNormal" align="justify" style="margin: 0cm 0cm 0pt; padding: 0px; line-height: normal; word-break: normal; text-size-adjust: auto;"><font color="#000000" face="Verdana, Arial, Helvetica, sans-serif">该文档清楚地说明了,覆盖iterator方法将会影响remove方法的行为。而且,它确切地描述了iterator方法返回的Iterator的行为将会臬影响remove方法的行为。与此相反,在第16条的情形中,程序员在子类化HashSet时,并没有说明覆盖add方法是否会影响addAll方法的行为。</font></p>
第18条:接口优于抽象类
现在类可以很容易的更新,以实现新的接口
无法更新现有的类来扩展抽象类,如果要扩展,就要将抽象类放到类层次的高处
实现mixin的理想选择
需要增加此功能的类只要implement一个interface即可
接口允许我们构建非层次结构的类型框架
接口可以使第16条的封装类(wrapper class)模式更加强大,安全地增加类的功能
第19条:接口只用于定义类型
第20条:类层次优于标签类
第21条:用函数对象表示策略
第22条:优先考虑静态成员类
嵌套类
静态成员
作为公有类的辅助
非静态成员
匿名类
局部类
第5章 泛型
第23条:请不要在新代码中使用原生态类型
如果使用原生态类型(如List)就失去了泛型(如List<String>)在安全性和表述性方面的优势
使用List<Object>来允许插入任意对象是允许的,但是不要使用List。
List逃避了泛型检查,List<Object>明确告诉编译器,它可以持有任意对象。
可以将List<String>传递给List,但是List<String>不能传递给List<Object>。因为泛型的子类化规则,List<String>是List的子类型,而List<String>不是List<Object>的子类型。
使用List就失掉了类型安全性,而使用List<Object>则不会。
不在乎集合中元素类型时,可能会使用原生态类型(Set s1),但是不安全,使用无限制通配符(Set<?> s1)替代
例外:使用类文字时应使用原生态类型,List.class、String[].class是合法的,List<String.class>、List<?>.class是不合法的
例外:instanceof操作符只能使用?通配符,但使用原生态类型和使用?对instanceof没有影响,使用?是多余的
<div> //利用泛型使用instanceof的首选方法</div><div> </div><div> if (o instanceof Set) //Raw type</div><div> { Set<?> m = (Set<?>) o; //Wildcard type</div><div> }</div>
第24条:消除非受检警告
容易消除的 <br>Set<Lark> exalation = new HashSet(); <br>warning : unchecked conversion<br>found : HashSet, required Set<Lark>
Set<Lark> exalation = new HashSet<Lark>();
尽可能消除每一个非受检警告
消除所有警告,可以确保代码是类型安全的,不会出现ClassCastException
如果无法消除警告,同时可以证明引起警告的代码是类型安全的<br>只在这种情况下使用@SuppressWarnings("unchecked")注解来禁止这条警告
在尽可能小的范围使用,不要在整个类上使用@SuppressWarinings
第25条:列表优先于数组
数组是协变的(covariant)
如果Sub是Super的子类型,那么数组Sub[]就是Super[]的子类型
合法的<br>Object[] objectArray = new Long[1];<br>objectArray[0] = "not fit"; //Throws ArrayStoreException
泛型是不可变的(invariant)
任意两个不同类型Type1和Type2,List<Type1>既不是List<Type2>的子类型,也不是List<Type2>的超类型
不合法的<br>List<Object> ol = new ArrayList<Long>();<br>ol.add("not fit");
数组是具体化的(reified)
在运行时才知道并检查它们的类型约束
泛型是通过擦除(erasure)来实现的
只在编译时强化它们的类型,并在运行时擦除它们的元素类型信息
优于根本上的原因,数组和泛型不能很好地混用,<br>如创建泛型、参数化类型或者类型参数的数组是<br>非法的(new List<E>[]、new List<String>[]和new E[]),<br>这些在编译时会导致一个generic array creation错误
第26条:优先考虑泛型
第27条:优先考虑泛型方法
第28条:利用有限制通配符来提升API灵活性
第29条:优先考虑类型安全的异构容器
第6章 枚举和注解
第30条 用enum代替int常量
严重缺陷:int枚举模式
//The int enum pattern. Severely deficient<br>public static final int APPLE_FUJI = 0;<br>public static final int APPLE_PIPPIN = 1;<br><br>public static final int ORANGE_NAVEL = 0;<br>public static final int APPLE_TEMPLE = 1;<br>
String枚举模式
enum
public enum Apple {FUJI, PIPPIN}<br>public enum Apple {NAVEL, TEMPLE}<br>
Java枚举本质上是int值
一个枚举如果是普适性的,那么就应该是顶层类;如果只是用在某个特定的顶层类中,它就应该是该顶层类的成员类
可以为枚举添加数据与方法
第31条:用实例域代替序数
第32条:用EnumSet代替位域
第33条:用EnumMap代替序数索引
第34条:用接口模拟可伸缩的枚举
第35条:注解优先于命名模式
第36条:坚持使用@Override注解
第37条:用标记接口定义类型
如果标记是应用到任何程序元素而不是类或者接口,就必须使用注解
如果标记只应用到类和接口
编写多个只接受有这种标记的方法
第7章 方法
第38条:检查参数的有效性
方法体开头检查参数
对于公有方法,@throw标签描述违反参数限制会抛出的异常
未导出的方法(unexported method),assertion,通过assert xxxx
断言,声称为真,如果失败抛出AssertionError
有些参数,方法本身没用到,却被保存起来以后使用,如构造器。检查参数有效性尤为重要
在方法执行它的计算任务之前,应检查它的参数
例外:当检查代价巨大或者不切实际,并且计算过程隐含检查过程的
Collections.sort(List)
不加选择的使用这种方法,将会失去失败原子性(failure atomicity)
不是对参数的任何限制都是好事
在设计时,尽可能使参数通用,并符合实际要求。
方法对于它能接受的参数值都能完成合理的工作,对参数的限制应该越少越好
第39条:必要时进行保护性拷贝
第40条:谨慎设计方法签名
谨慎地选择方法的名称
易于理解,风格一致
大众认可的名称
不要过于追求提供便利的方法
每个方法都应该尽其所能,方法太多使类难以学习、使用、文档化、测试和维护
对于类和接口所支持的每个动作,都提供一个功能齐全的方法
只有当一个动作被经常用到的时候,才考虑为它提供快捷方式。如不确定,还是不提供为好。
避免过长的参数列表
四个参数或者更少
相同类型的长参数序列格外有害
使用Builder模式
对于参数类型,优先使用接口而不是类
对于boolean参数,要优先使用两个元素的枚举类型
第41条:慎用重载
第42条:慎用可变参数
第43条:返回零长度的数组或者集合,而不是null
返回数组或者集合的方法没理由返回null,而不是返回一个零长度的数组或者集合
第44条:为所有导出的API元素编写文档注释
Javadoc利用特殊格式的文档注释(doc comment),根据源码自动产生API文档
为了正确的编写API文档,必须在每个被导出来的类、接口、构造器、方法和域声明之前增加一个文档注释
为了编写代码的可维护性,还应为未被导出的类、接口...编写文档注释
如果类是可序列化的,应为类的可系列化形式编写文档
方法的文档注释应当简洁的描述出它和客户端之间的约定
这个方法做了什么,而不是它如何完成这项工作的
列举出这个方法的所有前提条件和后置条件
为了使客户端能够调用,要满足什么条件。
@throws标签 每个未受检的异常都对应一个前提违例
@param标签 对受影响的参数指定前提条件
调用成功完成之后,哪些条件必须要满足
每个方法还应描述它的副作用
系统状态中可以观察到的变化,如,方法启动了后台线程
描述类或者方法的线程安全性
每个参数都有一个@param标签<br>一个@return标签,除了void<br>抛出的每一个异常都有一个@throws标签
@param、@return后应为名词短语,描述这个参数或者返回值所表示的值
@throws 开头应为 if,描述这个异常在什么情况下会被抛出
sample<br>...
第8章 通用程序设计
第45条: 将局部变量的作用域最小化
在第一次使用它的地方声明
几乎每个局部变量的声明,都应该包含一个初始化表达式
循环提供特殊的机会是局部变量的作用域最小化
循环终止后不再需要循环变量,for优于while
使方法小而集中
第46条:for-each循环优先于传统的for循环
for (Element e : elements) {<br> doSomething(e);<br>}
for-each循环不仅可以遍历集合和数组,还让你遍历任何实现Iterable接口的对象
无法使用for-each循环的情况
过滤
如果需要遍历集合,并删除选定元素,<br>就要使用显示的迭代器,以便调用它的remove方法。
转换
如果需要遍历列表或者数组,并取代它的部分或者全部的元素值,<br>就需要列表迭代器或者数组索引,以便设定元素的值。
平行迭代
如果需要并行的遍历多个集合,<br>就需要显示的控制迭代器或索引变量,以便所有迭代器或者索引变量能够同步前移。
第47条:了解和使用类库
通过使用标准类库,可以充分利用这些编写标准类库的专家的知识,以及在你之前的其他人的使用经验。
不必浪费时间为那些与工作不太相关的问题提供特别的解决方案。(把时间花在应用程序上,而不是底层细节上。)
标准类库的性能往往随着时间推移而不断提高
标准类库也会随着时间推移增加新功能
可以使自己的代码融入主流
在每个重要的发行版本中,都会有许多新的特性加入到类库中,所以与这些新特性保持同步是值得的
java.lang、java.util、java.io,其他根据需要
jdk1.2 Collections Framework集合框架加入java.util<br><br>jdk1.5 java.util.concurrent加入了一组并发实用工具
不要重新发明轮子
第48条:如果需要精确的答案,请避免使用float和double
执行二进制浮点运算,没有提供完全精确的结果,不适用于需要精确结果的场合
尤其不适合用于货币计算
BigDecimal
int、long
第49条:基本类型优先于装箱基本类型
基本类型<br>(primitive)
int、double、long...
引用类型<br>(reference type)
装箱基本类型<br>(boxed primitive)
Integer、Double、Long
...
1.基本类型只有值,而装箱基本类型则具有与它们的值不同的同一性。
2.基本类型只有功能完备的值,而每个装箱基本类型除了它对应基本类型的所有功能值之外,<br>还有个非功能值:null
3.基本类型通常比装箱基本类型更节省时间和空间
例外1.集合中的元素、键、值,必须使用装箱基本类型
jdk1.5 自动装箱、自动拆箱
例外2.反射的方法调用时,使用装箱基本类型
第50条:如果其它类型更合适,则尽量避免使用字符串
字符串不适合代替其它的值类型
数据进入到程序中,除非数据本身确实是文本信息,<br>否则转换成对应的int、float,<br>以及“是-或者-否”转换成boolean<br>如果存在适当的值类型,不管是基本类型还是对象引用,大多应该使用这种类型<br>如果不存在这样的类型,就应该编写一个类型
字符串不适合替代枚举类型
字符串不适合替代聚集类型
字符串也不适合代替能力表(capabilities)
第51条:当心字符串连接的性能
不要使用字符串操作符来合并多个字符串,除非性能无关紧要,相反使用StringBuffer替代String
使用字符数组
或者每次只处理一个字符串,而不是将它们组合起来
第52条:通过接口引用对象
如果有合适的接口类型存在,那么对于参数、变量、返回值和域来说,就都应该使用接口类型进行声明
如果没有合适的接口存在,完全可以用类而不是接口来引用对象
第53条:接口优先于反射机制
核心反射机制(core reflection facility) java.lang.reflect
反射允许一个类使用另一个类,即使当前者被编译的时候后者还不存在
丧失了编译时类型检查的好处,<br>包括异常检查,如果程序调用了不存在或者不可访问的方法,在运行时它将会失败,除非做了特别的预防措施。
执行反射访问所需要的代码非常笨拙和冗长
性能损失
第54条:谨慎地使用本地方法
使用本地方法来提高性能的做法不值得提倡
本地语言是不安全的
本地语言与平台相关,不可自由移植
第55条:谨慎地进行优化
格言
很多计算上的过失都被归咎于效率(没有必要达到的效率),而不是任何其他的原因——甚至包括盲目的做傻事。 ——William A. Wulf[Wulf72]
不要去计较效率上的一些小小的损失,在97%的情况下,不成熟的优化才是问题的根源。 <br> ——Donald E. Knuth[Knuth74]
在优化方面,我们应该遵守两条规则:<br>规则1:不要进行优化。<br>规则2(仅针对专家):还是不要进行优化——也就是说,在你还没有绝对清晰的优化方案之前,请不要进行优化。 ——M.A.Jackson[Jackson75]
不要因为性能而牺牲合理的结构。要努力编写好的程序而不是快的程序。
努力避免那些限制性能的设计决策。必须在设计过程中考虑到性能问题。
要考虑API设计决策的性能后果。
第56条:遵守普遍接受的命名惯例
第9章 异常
第57条:只针对异常的情况才使用异常
异常是为了在异常情况下使用而设计的。不要将它们用于普通的控制流,也不要编写迫使它们这么做的API。
第58条:对可恢复的情况使用受检异常,对编程错误使用运行时异常
子主题
第59条:避免不必要的使用受检的异常
第60条:优先使用标准的异常
第61条:抛出与抽象相对应的异常
第62条:每个方法抛出的异常都要有文档
第63条:在细节消息中包含能捕获失败的信息
第64条:努力使失败保持原子性
第65条:不要忽略异常
第10章 并发
第66条:同步访问共享的可变数据
synchronized关键字
第67条:避免过度同步
第68条:executor和task优先于线程
第69条:并发工具优先于wait和notify
从jdk1.5开始,Java提供了更高级的并发工具
更高级工具三类
Executor Framework
第68条
并发集合(Concurrent Collection)
为标准的集合接口(如List、Map、Queue)提供了高性能的并发实现
ConcurrentHashMap
为了提供高并发性,这些实现在内部自己管理同步(见67条)<br>因此,并发集合中不可能排除并发活动,将它锁定没有什么作用,只会使程序的速度变慢
同步器(Synchronizer)
一些使线程能够等待另一个线程的对象,允许它们协调动作<br>最常见的synchronizer是Countdown Latch和semaphore<br>较不常用的是CyclicBarrier和Exchanger
Countdown Latch(倒计数锁存器)是一次性障碍,允许一个或多个线程等待一个或多个其它线程来做某些事情<br>Countdown Latch的构造器带有一个int类型的参数,这个int是指允许所有在等待的线程被处理前,必须在锁存器上调用Countdown方法的次数。
第70条:线程安全性的文档化
通过查看文档中是否出现了synchronized修饰符,可以确定一个方法是否是线程是安全的
在一个方法生命中出现的synchronized修饰符,这个是实现细节,并不是导出API的一部分,它并不一定表明这个方法是线程安全的
线程安全不是要么有要么无的,实际上线程安全有多种级别。<br>一个类为了可被多个线程安全地使用,就要在文档中清楚地说明它所支持的线程安全性级别。
常见的安全性级别
(immutable)不可变的
这个类的实例是不变的。所以不需要外部的同步。<br>这样的例子包括String、Long、BigInteger(见第15条)。
(unconditionally thread-safe)无条件的线程安全
这个类的实例是可变的,但是这个类有足够的内部同步。<br>所以,它的实例可以被并发使用,无需任何外部同步。<br>其例子包括Random、ConcurrentHashMap
(conditionally thread-safe)有条件的线程安全
除了有些方法为进行安全的并发使用而需要外部同步外,这种线程安全级别与无条件的线程安全级别相同。<br>这样的例子包括Collections.synchronized包装返回的集合,它们的迭代器(iterator)要求外部同步。
(not thread-safe)非线程安全
这个类的实例是可变的。为了并发的使用它们,客户必须利用自己选择的外部同步包围每个方法调用(或调用序列)。<br>这样的例子包括通用的集合实现,如ArrayList和HashMap。
(thread-hostile)线程对立的
这个类不能安全地被多个线程并发使用,即使所有的方法调用都被外部同步包围。<br>线程对立的根源通常在于,没有同步地修改静态数据。<br>没有人会有意地编写一个线程对立的类,这种类是由于没有考虑到并发性而产生的后果。<br>幸运的是在Java类库中,线程对立的类或者方法非常少,System.runFinalizersExit方法是线程对立的,但已经被废除了。
第71条:慎用延迟初始化(lazy initialization)
lazy initialization是延迟到需要域的值时才将它初始化的行为。<br>如果永远不需要这个值,这个域就永远不会被初始化。这种方法即适用于静态域,也适用于实力域。
虽然延迟初始化主要是一种优化,但它也可以用来打破类和实例初始化中的有害循环。
就像大多数优化一样,对于延迟初始化,最好建议“除非绝对必要,否则就不要这么做”(见第55条)。
它降低了初始化类或者创建实例的开销,却增加了访问被延迟初始化的域的开销。
测量类在用和不用延迟初始化时的性能差别。选择进步进行延迟初始化。
当有多个线程时,延迟初始化是有技巧的。<br>如果两个或多个线程共享一个延迟初始化的域,采用某种形式的同步是很重要的,否则就可能造成严重的Bug(见第66条)。
在大多数情况下,正常的初始化要优于延迟初始化
如果出于性能考虑而需要对<b>静态域</b>使用延迟初始化,就使用lazy initialization hold class模式(也被称作initialize-on-demand holder class idiom),保证了类要到被用到的时候才会被初始化。
如果出于性能考虑而需要对<b>实例域</b>使用延迟初始化,就使用双重检查模式(double-check idiom)。<br>这种模式避免了在域被初始化之后访问这个域时的锁定开销(见第67条)。
对于可以接受重复初始化的实力域,可以考虑使用单重检查(single-check idiom)
第72条:不要依赖于线程调度器(thread schedule)
任何一个<b>操作系统</b>在做线程调度的时候,都会努力做到公正。但是所采用的策略却大相径庭。
任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的。
如果线程没有在做有意义的工作,就不应该运行。
线程优先级(thread priority)是Java平台上最不可移植的特性。
某一程序不能工作,不要企图调用Thread.yield来“修正”程序。Thread.yield没有可测试的语义(testable semantic)。<br>多大多数人来说,Thread.yield的唯一用途是在测试期间人为地增加程序的并发性。
第73条:避免使用线程组(thread group)
它的初衷是作为一种隔离applet的机制,当然是出于安全的考虑。但是它们重来没有真正履行这个承诺。
如果你设计的一个类需要处理线程的逻辑组,或许就应该使用线程池executor(见第68条)。
第11章 序列化
第74条:谨慎地实现Serializable接口
要想使一个类序列化,只需要在类的声明中加上implements Serializable即可。<br>正因为太容易了,所以存在一种误解,认为程序员毫不费力就可以实现序列化。<br>实际的情形复杂的多。<br>虽然使一个类可序列化的直接开销非常低,但是为了序列化而付出的长期开销是实实在在的。
实现Serializable而付出的最大代价是,一旦一个类被发布了,就大大降低了“改变这个类的实现”的灵活性。
接受了默认序列化的形式,这个类中的私有的和包级私有的实力域将都变成导出API的一部分。<br>这不符合“最低限度地访问域”(见第13条)的实践标准。
实现Serializable的第二个代价是,它增加了出现Bug和安全漏洞的可能性。
反序列化机制(deserialization)是一个“隐藏构造器”,因为反序列化机制中没有显示构造器,很容易忘了确保“反序列化过程必须也要保证由真正构造器建立起来地约束关系”。
使用默认的反序列化机制,很容易使对象的约束关系遭到破坏,以及遭受到非法访问(见第76条)。
实现Serializable的第三个代价是,随着类发行新的版本,相关的测试负担也增加了
实现Serializable接口并不是一个很轻松就可以做出的决定。
为了继承而设计的类(见第17条),应该尽可能少的去实现Serializable接口,用户的接口也应该尽可能少的继承Serializable接口。
对于为继承而设计的不可序列化的类,你应该考虑提供一个无参构造器
内部类(inner class)不应该实现Serializable
内部类的默认序列化形式是定义不清楚的
第75条:考虑使用自定义的序列化形式
第76条:保护性的编写readObject方法
第77条:对于实例控制,枚举类型优先于readResolve
第78条:考虑用序列化代理代替序列化实例
收藏
收藏
0 条评论
下一页