- 机构级别:普通会员
- 信用等级:
资料认证
未通过身份证认证
未通过办学许可认证
- 学校浏览人次:次
- 加盟时间:2017年03月10日
【西安尚学堂】Effective Java学习笔记
20.用类代替结构
C语言的结构体(struct),用类(class)来代替。
一个退化到只包含一些数据域,就大体上类似C语言的结构了。而且使用类的好处还有就是可以通过封装带来数据保护的好处(这不就成了JavaBean了吗)。进而Java中有这么一条告诫“公有类不应该直接暴露数据域”。
21.用类和接口来代替函数指针
在设计模式中的策略模式也是一个典型的应用场景。C语言程序通常使用函数指针来实现策略模式。Java中没有指针,我们可以使用类和接口来替代之。
22.检查参数的有效性
当你编写一个方法或构造函数时,应该考虑对于它的参数都有哪些限制。你应当写到文档中,并在方法的开头进行检查。
23.需要时使用保护性拷贝
如果一个方法或构造函数允许可变对象进/出,那么就要考虑一下使用者是否有可能改变它。如果是的话,那你必须对该对象进行保护性拷贝,使进入方法内部的对象是外部时的拷贝而不它本身(因为外部的对象有可能还会被改变)。
24.慎设计方法的原型
a.谨慎选择方法的名字
你选用的名字应该遵循标准命名习惯,选择易懂的、并和他人保持风格一致的名字。
b.不要过于追求提供便利的方法
方法太多会是一个很难实现(对于接口而言)、学习、使用、文档化、测试和维护的。
c.避免长长的参数列表
d.参数类型优先使用接口而不是类
e.谨慎地使用函数对象
换句话说,就是作者不建议经常使用函数回调。频繁的创建函数对象,并且将它们从一个方法传递到另一个方法,这种程序设计风格并非主流.
25.谨慎地使用重载
一个安全而保守的策略是永远不要导出两个具有相同参数数目的重载方法
26.返回零长度的数组而不是null
避免给调用者带来过的的保护性检查的麻烦。返回空list而不是null。
27.为所有导出的API元素编写文档注释
为了正确地编写API文档,你必须在每一个被导出的类、接口、构造函数、方法和域声明之前加上文档注释。每一个方法的文档注释应该简洁地描述出它和使用者之间的约定。
28.了解和使用库
应该熟悉java.lang和java.util,还有一些java.io的内容。
29.如果要求精确的答案,请避免使用float和double
Java给我们准备了BigDecimal
30.将局部变量的作用域最小化
将局部变量的作用域最小化,可以增加代码的可读性和可维护性,并降低出错的可能性。
31.如果其他类型更合适,请尽量避免使用字符串
这确实一个表示文本的很好的数据类型,所有的类型都有toString方法来返回一个文本。但是,它并不适合代替其他的值类型。
32.了解字符串连接的性能
涉及到多处对字符串的修改时可以尝试使用StringBuilder
33.通过接口引用对象
应该优先使用接口而不是类来引用对象。也就是说,当我们在考虑引入一个引用变量的时候,应该首先考虑的是,我们需要一个具有什么样功能的变量,也就是选择一个什么接口。之后才是在众多的实现类中选择一个合适的实现进行初始化。
如果没有合适的接口存在的话,那么,用类而不是接口来引用一个对象,是完全合适的。
34.接口优于反射机制
如果使用反射机制,就意味着:
a.损失了编译时类型检查的好处;
b.要求执行反射的代码非常笨拙和冗长,意味着阅读这些代码也很困难;
c.性能损失;
以反射的方式创建一个实例后,最好通过它的接口或超类以正常的方式访问这些实例。
35.谨慎地使用本地方法
使用JNI是有着相当的弊端的,失去了Java的平台无关性
36.谨慎地进行优化
a.任何优化都存在风险,有时候弄不好反而带来其他的问题
b.并不是 性能 优先。努力编写好的程序而不是快的程序。
c.对前人,尤其是类似于Java API这样的成熟代码,进行优化,是不明智的(要是能优化,人家早就做了)其实,不仅仅是优化。随着项目的进展,尤其是到了后期和交付后,再做任何代码的改动都是非常危险的,必须十分小心,还要之后的大量、充分地测试。不论改动的目的是 需求变更/增加 还是 Debug 或是 性能优化。
37.遵守普遍接受的命名惯例
a.具体到Java的一系列命名惯例,首先是包名。Java中的package是为了区别不同人、不同公司编写的可能名字相同的类用的,其实这句话应该反着说,因为是Java首先这么用的。Sun推荐包名按照域名的逆序来书写,而且全是小写字母,每一层尽量是一个英文单词(名词最好)。尽量一个单词,而且尽量不大于8个字母,所以鼓励使用缩写。例如,使用util而不是utilities(有人也用utils)
b.类和接口(接口其实也是类,Java中万物皆类)的名字采用 Pascal命名法,即每个单词的首字母均大写,各个单词间无连接符。对于一些英文缩写,也推荐除首字母以外都小写。如,HttpUrl,而不是HTTPURL。这里尤其要注意的是仅有两个字母的缩写,如IO,ID,IP,最好还是写成DiskIo,UserId,TerminalIp。
c.类成员(属性和方法)名、局部变量名要用 驼峰命名法,即除首单词的首字母要小写外,其他同Pascal命名法。
d.常量,用全大写,各个单词见用下划线“_”相连接。
e.getter/setter方法,getter/setter其实就是普通的方法,只是一种特定用途罢了。它们用于将被封装的私有属性对外提供访问的方法。通常是在属性名的前面加上 get 和 set ,再将属性名的首字母变大写。这里的一个特殊地方是boolean型变量,除了之上的方法,也可以把 get 改为 is,如果属性名本身已经是以 is 开头了,就省掉这个 is 。这是 JavaBean 的规范,广义上讲,也可以用一个名词或名词短语,如:size,hashCode.
f.特殊方法和属性:
静态工厂方法:valueOf 和 getInstance 前者广泛用于对值类的类型转换,后者则出现在非值类的单例模式中
类型转换方法:toType,如 toString,toArray
返回当前对象的一个不同的视图:asType,如:asList,常用 Arrays.asList 来将数组转换成List
一些省略了开头的“is”的boolean型属性,如 initialized 和 composite
一些常用的通用属性,如:height,digits,size 等
一些常用的通用方法,如:flush,isEmpty 等
h.在对英文单词的选择上,也尽量复合大多数人的习惯。
避免使用一些蹩脚的单词,而是使用常见的单词,而且最好是其他人也大多使用的单词。在对两个单词模棱两可时,可以在Javadoc中搜索一下,看看哪一个被类库使用的更多。例如,当你拿不定主意用 delete 还是 remove 时,到Javadoc中搜一下,你会发现 remove 的出现次数远比 delete 要多得多,可能仅仅是在物理上删除如文件或数据库中的记录的时候才用delete,一般对变量中内容的删除都使用remove。
38.只针对不正常的条件才使用异常
39.对于可恢复的条件使用被检查的异常,对于程序错误使用运行时异常
如果希望使用者可以恢复,继续执行程序,应该使用checkedException;而如果是程序错误,遇到这个错误后顶多是写个日志、报告个MessageBox,就只能停止程序,就应该使用runtimeException。
两种异常的“特征”就是,是否在调用方法时必须俘获它可能抛出的异常。对于checkedException是必须的,而对于runtimeException是不强求的。同样,在方法的描述中对可能抛出的checkedException要求用throws关键字予以明示,而runtimeException则不需要。但,请切记,这两点区别,只是两者表现出来的不同特征。而绝不是以要不要写某些代码来决定使用哪种异常的。
40.避免不必要地使用被检查的异常
解决方案是:
a.尽量在API内部处理掉checkedException。如输出错误信息或写日志后,结束当前程序。
b.对于那些本来就是该向外抛出异常来表明内部的某种失败的情况下,调用者尽量在调用之前确保可以成功再调用。
c.在适当的时候,使用RuntimeException。如果API不能做的更好,也许RuntimeException会更为适合。
41.尽量使用标准的异常
代码重用,是程序员们“千百年来”所追求的目标,同样Exception也是代码,也该尽量被重用。所以,应该尽量使用标准的异常,而不是轻易地使用自造的异常。
IllegalArgumentException参数的值不合适。方法的开始处的参数检查,一旦有问题就该抛出这个
IllegalStateException对于这个方法调用而言,对象状态不合适。同样也是开始处检查
NullPointerException空指针
IndexOutOfBoundsException下标越界
ConcurrentModificationException 禁止并发的情况下,被检查到并发的修改
UnsupportedOperationException 不支持这种方法,如接口/抽象父类中已经定义,但尚未实现的方法
42.抛出的异常要合适于相应的抽象
如果一个方法抛出的异常与它所执行的任务没有明显关联关系的话,会使人感到不知所措。为了避免这个问题,应该在高层的实现中俘获底层抛出的“低级”异常,并同时用另一“高级”异常继续向外抛。
例如,如我们在方法中,有一段循环,一个一个地取集合的一下元素。当catch到一个 NoSuchElementException 或 NullPointerException 时,说明已经到头儿了。这时我们可以 new 一个 IndexOutOfBoundsException 并向上抛出。对于此方法的调用者来说,这个异常显然比前两个更有实际意义。这被称为异常转译。
43.每个方法抛出的异常都要有文档
通过@throws标签,逐个地声明每一个被检查的异常,并写清每一个异常被抛出的条件(这个是通过throws语句所了解不到的)。如果一个方法可能抛出多个异常,不要用它们共通的父类异常来表示。甚至,有人连会抛出哪些异常都不关,就干脆 throws Exception (更有甚者 throws Throwable)。这种极端的做法,可能是初学者偷懒的做法,对Java的checkedException深恶痛绝的程序员。但无论如何,不要这么做。
44.在细节消息中包含失败-捕获信息
为了在异常发生后捕捉失败原因,一个异常的字符串表示应该包括所有“对该异常有贡献”的参数和域的值。也就是说在异常所能“携带”的信息中,尽量多的加入对调试人员有用的信息。
一个例子是 IndexOutOfBoundsException ,在这个下标越界的异常中,提示出了,最小小标、最大下标和当前下标的值,这样调试人员就可以很清楚的知道到底是上溢出了,还是下溢出了,溢出了多少。
45.努力使失败保持原子性
所谓失败的原子性,就是在一个方法失败之后,使对象保持“它在被调用之前的状态”。因为,尤其是CheckedException发生后,一般是希望程序可以从异常中恢复过来的。
对于可变对象,常见的方法是在对其进行处理之前,先做参数有效性的检查,如果参数有问题,马上就先抛出一个异常。这时候对象的状态还未改变。
另一种方法是,将处理的过程调整一下顺序,让可能抛出异常的处理写在前面,而会改变对象状态的处理写在后面。
还有一种不太常用的方法,是编写一段恢复代码,发生失败时,可以使对象回滚到操作开始之前的状态。
最后一种方法是,在对象的一份临时拷贝上执行操作,当操作正确结束后,再把临时拷贝中的结果复制给原来的对象。如果一旦失败,不进行这个复制,也就保持了原对象的状态。
总之,作为方法规范的一部分,任何一个异常都不应该改变对象调用该方法之前的状态。
46.不要忽略异常
这样的代码并不十分罕见,这有可能是在代码堆砌阶段的“简单写法”,也可能是程序员“偷懒”的写法,当然也有可能是连“何为Exception”都不得而知的“高人”的作品。
这里犯了两个错误。首先,就是本条要强调的,它忽略了捕获来的异常。相当于是把异常抓来,然后“杀”掉它,也就是“装没看见”。
另一个错误是,不该使用异常的超类Exception,而应该“具体异常,具体分析”。
47.对共享可变数据的同步访问
当多个线程共享可变数据的时候,每个读或者写数据的线程必须获得一把锁。