Effective JAVA 总结

来源:互联网 发布:linux系统驱动开发 编辑:程序博客网 时间:2024/06/06 20:38
一、创建和销毁对象。
1.考虑用静态工厂方法代替构造器。
1)静态工厂方法与构造器不同的第一大优势在于,它们有名称。
2)静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新的对象。3)静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。4)静态工厂方法与构造器不同的第四大优势在于,在创建参数化类型实例的时候,他们使代码变得更加简洁。
5)静态工厂的主要缺点在于,类如果不含公有的后者受保护的构造器,就不能被子类化。
6)静态工厂方法的第二个缺点在于,它们与其他的静态方法实际上没有任何区别。
2.遇到多个构造器参数时要考虑用构造器。
1)普通构造
2)setPrity
3)public Builder sodium(int va){carbor=va;return this;}
3.用私有构造器或者枚举类型强化SingLeton属性。
1)公有静态成员是个final域。
2)公有的成员是个静态工厂方法。
3)包含单个元素的枚举类型。
4.通过私有构造器强化不可实例化的能力。
1)私有化默认构造方法。
5.避免创建不必要的对象。
6.消除过期的对象引用。
1)=null。无意识的对象保存。
2)缓存。
3)监听器和其他回调。
7.避免使用终结方法。
1)隐士终结。
2)显示终结。
二、对于所有对象都通用的方法。
8.覆盖equels时请遵守通用约定。
1)自反性。x.equals(x)
2)对称性。y.equals(x)=x.equals(y)
3)传递性。x.equals(y)=y.equals(z)=x.equals(z)
4)一致性。
5)非空性。
9.覆盖equals时总要覆盖hashCode

10.始终覆盖toString。
11.谨慎地覆盖clone。
12.考虑实现Comparable接口。
三、类和接口。
13.使类和成员的可访问性最小化。
14.在公有类中使用访问方法而非公有域。
15.使可变性最小化。
1)不要提供任何会修改队形状态的方法(也称为Mutator)。
2)保证类不会被拓展。
3)使所有的域都是final的。
4)使所有的域都为私有的。
5)确保对于任何可变组件的互斥访问。
16.复合优先于继承。?
17.要么为继承而设计,并提供文档说明,要命就禁止继承。?
18.接口由于抽象类。
1)现有的类可以很容易被更新,以实现新的接口。
2)接口是定义mixin(混合类型)的理想选择。
3)接口允许我们构造非层次机构的类型框架。
19.接口值用于定义类型。?
1)常量接口模式是对接口的不良使用。
2)类层次优于标签类。
20.类层次优于标签类。?
21.用函数对象标识策略。
22.优先考虑静态成员类。
四、泛型。
23.请不要在新代码中使用原生态类型。
24.消除飞受检警告。
@SuppressWarnings
25.列表优先于数组。
26.优先考虑泛型。
27.优先考虑泛型方法。
28.利用有限制通配符来提升API的灵活性。
public void pushAll(Iterable<? extends E> src){
for(E e:src){
push(e);
}
}
29.优先考虑类型安全的异构容器。?
五、枚举和注释。
30.用enum代替int常量。
1)采用int枚举模式的程序是十分脆弱的。
2)将int枚举常量翻译成可打印的字符串,并没有很便利的方法。
3)此规则同样适用于字符常量。
4)枚举常量是单例的泛型化。
5)枚举提供了编译时的类型安全。
31.用实例域代替序数。
public enum Ensemble{
SOLO(1);
}
32.用EnumSet代替位域。
用OR位运算将几个常量合并到一个集合中,称作位域(bit field);
1)当位域以数字形式打印时,翻译位域比翻译简单的int枚举常量要困难的多。甚至要遍历位 域锁表示的所有元素也没有很容易的方法。
2)EnumSet不可创建不可变的。
33.用EnumMaap代替序数索引。
Map<Herb.Type,Set<Herb>> herbsByType=new EnumMap<Herb.Type,Set<Herb>>(Herb.Type.class);
34.用接口模拟可伸缩的枚举。
35.注释优于命名模式。
36.坚持使用Override注解。
@Override public boolean eauals(Object o){}
在具体的类中,不必标注你确信覆盖率抽象方法声明的方法。
37.用标记接口定义类型。
1)如果想定义类型就一定要使用接口。
六、方法。
38.检查参数的有效性。
1)每当编写方法或者构造器的时候,应该考虑它的参数有那些限制。
39.必要时进行保护性拷贝。
40.谨慎设计方法签名。
1)谨慎地选择方法的名称。
2)不要过于追求提供遍历的方法。(当有意向操作被经常使用到的时候,才考虑为它提供快捷方式)3)避免过长的参数列表。(把方法分解成多个方法、创建辅助类、从对象构建到方法调用都采用Builder模式)
4)对于参数类型,要优先使用接口而不是)的枚举类型。
41.慎用重载。
1)永远不要导出两个具有相同参数数目的重载方法。
42.慎用可变参数。
static int min(int firstArg,int... remainingArgs){
int min=firstArg;
for(int arg:remainingArgs){
if(arg<min){
min=arg
}
return min;
}
}
43.返回零长度的数组或者集合,而不是null。
44.为所有导出的API元素编写文档注释。
javadoc
1)为了正确地编写API文档,必须在每个被导出的类、接口、构造器、方法和域声明之前增加一个文档注释。
2)方法的文档注释应简洁的描述出它和客户端之间的约定。
/**
*Returns the element at the specified position in this list.
*<p>This method is<i>not </i> guaranteed to run in constant time.</p>
*@param index 
*@return 
*@throws IndexOUtOfBound
*/
七、通用程序设计。
45.将举报变量的作用域最小化。
1)要使局部变量的作用域最小化,最有集合力的方法就是在第一次使用它的地方声明。
2)几乎每个局部变量的声明都应该包含一个初始化表达式。
46.for-each循环优先于传统的for循环。
for-each循环在简洁性和预防bug方面有着传统的for循环无法比拟的优势,并且没有性能损失。
47.了解和使用类库。
48.如果需要精确的答案,请避免使用float和double.
用long或者bigdecimal
49.基于类型优先于装箱基本类型。
int>Integer
50.如果其他类型更合适,则尽量避免使用字符串。
1)字符串不适合大体其他的值类型。
2)字符串不适合代替枚举类型。
3)字符串不适合代替聚合类型。
4)字符串也不适应代替能力表。
总而言之,如果可以使用更加合适的数据类型,或者可以编写更加适合的数据类型,就应该避免用字符串来表示对象。如使用不当,字符串会比其他的类型更加笨拙、更不灵活、速度更慢,更容易出错。经常被错误的用字符串来代替的类型包括基本类型、枚举类型、和聚集类型。
51.当心字符串连接的性能。
为连接n个字符串而重复的使用字符串连接操作,需要n的平方级的时间。
52.通过接口引用对象。
List<String> strlist=new Vector<String>();
1)通过接口引用对象,你的程序将更加灵活。
2)如果没有合适的接口存在,完全可以用类而不是接口来引用对象。
53.接口优先于反射机制。
1)反射机制丧失了编译时类型检查的好处。
2)反射机制执行反射访问所需要的代码非常笨拙和冗长。
3)放射造成性能损失。
4)如果只是以非常有限的形式使用反射机制,虽然也要付出少许代价,但是可以获得许多好处。
54.谨慎的使用本地方法。
Java Native Interface(JNI),从历史上看,本地方法主要有三种用途。
它们提供了“访问特定于平台的机制”的能力,比如访问注册表和文件锁。
它们还提供了访问遗留代码库的能力,从而可以访问遗留数据。
最后,本地方法可以通过本地语言,编写应用程序中注重性能的部分,以提高系统的性能。
1)使用本地方法来提高性能的方法不值得提倡。
2)本地语言不是安全的。
55.谨慎地进行优化。
1)优化的弊大于利,要努力编写好的程序而不是快的程序。
2)努力避免那些限制性能的设计策略。
56.遵守普遍接受的命名惯例。
1)包的名称应该是层次状的,用句号分割每个部分。例如:com.sun。用户创建的包名称绝不能以java和javax开头。
2)类和接口的名称,包括枚举和主食类型的名称,都应该包括一个或者多个单词,每个单词的首字母大写。例如:Timer
3)方法和域的名称与类和接口的名称一样,都遵守相同的字母惯例,只不过方法或者域的名称的第一个祖玛应该小写。例如:remove
4)上述规则的唯一例外是“常用域”,它的名称应该包括一个或者多个大写的单词,中间用下划线符号隔开。例如:VALUES_TAG
5)局部变量名称的字面命名惯例与成员名称类似,只不过它也允许缩写,单个字符和段字符序列的意义取决于举报变量所在的上下文环境,例如i/xref和houseNumber
6)类型的名称通常由单个字母组成。例如:T
八、异常。
57.只有针对异常的情况才使用异常。
针对异常的情况才使用异常,永远不应该用于正常的控制流程。
58.对可恢复的情况使用受检异常,对编程错误使用运行时异常。
59.避免不必要地使用受检的异常。
60.优先使用标准的异常。
61.抛出与抽象相对应的异常。
1)为了避免这个问题,更高层的实现应该捕获低层的异常,同时排除可以按照搞错抽象进行解释的异常。
2)尽管异常转移与不加选择地从低层传递异常的做法相比有所改进,但是它也不能被滥用。
62.每个方法抛出的异常都要有文档。
1)始终要单独地声明受检的异常,并且利用Javadoc的@throws,准确地记录下抛出每个异常的条件。
2)使用Javadoc的@throws标签记录下一个方法可能抛出的每个未受检异常,但是不要使用throws关键字将未受检的异常包含在方法的声明中。
3)如果一个类中的许多方法出于同样的原因而抛出同一个异常,在该类的文档注释中对这个异常建立文档,这是可以接受的,而不是为每个方法单独建立文档。
63.在细节消息中包含能捕获失败的信息。
1)为了捕获失败,异常的细节信息应该包含所有“对该异常有贡献”的参数和域的值。
64.努力使失败保持原子性。
65.不要忽略异常。
1)空的cathc块会使异常达不到应有的目的,即强迫你处理异常的情况。
2)有一种情形可以忽略异常,即关闭FileInputStream的时候。
九、并发。
66.同步访问共享的可变数据。
synchronized同步
1)Java语言规范保证读者写一个变量是原子的,除非这个变量的类型为long或者double.
2)你可能听说过,为了提高性能,在读或者写原子数据的时候,应该避免使用同步。这个建议是非常危险而错误的。
3)当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。
67.避免过度同步。
过度同步可能会导致性能降低、死锁,甚至不确定的行为。
通常,你应该在同步区域内做尽可能少的工作。
简而言之,为了避免死锁和数据破坏,千万不要从同步区域内调用外来方法。
68.executor和task优先于线程。
ExecutorService executro=Executors.newSingleThreadExecutor();
executor.execute(runnable);
executor.shutdown();
Executors.newFixedThreadPool();
有兴趣可以参考:《Java并发编程实战》
69.并发工具优先于wait和notify。?
70.线程安全性的文档化。
71.慎用延迟初始化。
1)它降低了初始化类或者创建事例的开销,却增加了访问被延迟初始化的域的开销。
2)降低了性能。
3)当有多个线程时。延迟初始化是需要技巧的。
72.不要依赖于线程调度器。?
1)任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的。
73.避免使用线程组。
十、序列化。
74.谨慎的实现Serializable接口。
1)实现Serializable接口而付出的最大代价是,一旦一个类被发布,就大大降低了“改变这个类的实现”的灵活性。
2)实现Serializable的第二个代价是,它增加了出现Bug和安全漏洞的可能性。
3)实现Serializable的第三个代价是,随着类发行新的版本,相关的测试副段也增加了。
4)实现Serializable接口并不是一个很轻松就可以做出的决定。
5)为了继承而设计的类应该尽可能少地去实现Serializable接口,用户的接口也应该尽可能少地继承Serializable接口。
6)内部类不应该实现Serializable。
75.考虑使用自定义的序列化形式。
1)如果没有先认真考虑默认的序列化形式是否合适,则不要贸然接受。
2)如果一个对象的物理表示法等同于它的逻辑内容,可能就适合于使用默认的序列化形式。
76.保护性的编写readObject方法。
77.对于实例控制,枚举类型优先于readResolve。
78.考虑用序列化代理代替序列化实例。