Effictive Java 不彻底学习笔记
这里只是基于本身的理解作的一些记录, 一些对本身来讲已是一种常识, 将再也不列出.
因为做者是sun jdk collection framework的主要贡献者, 所以书中不少的effective rule, 大多数是对collection framework实现的一个总结(不少例子都是拿Collection中的东东在说事儿), 站在JDK API的高度来思考的, 这些考虑是很是严谨的, 而咱们平时的开发可能不会注意到这些细节, 或者可能没有做者所说的场景, 所以能够根据须要选择性的理解.
第5条 消除过时的对象引用
这里讲到了一个由于过时引用致使的内存泄漏的问题, 而平时咱们也很难注意到这个问题, 这个例子是一个Stack的实现, 在Stack内部用一个数组来缓存堆栈中的全部元素, 在其pop方法里面这样写:
Java代码
- public Object pop(){
- if (size == 0){
- throw new EmptyStackException();
- }
- return elements[--size];
- }
当弹出一个元素的时候, elements其实还存在对这个元素的引用. 正确的作法应该是
Java代码
- Object result = elements[--size];
- element[size]=null;
- return result;
可是对于java来讲, 每次对再也不使用的对象手工强制设置为null这不是一种好的处理方式, 之因此要这么作的前提是:须要对象本身来管理内存, 即对于再也不使用的对象, 须要明确的告诉垃圾收集器再也不使用, 不然就会形成内存泄漏.
第8条 改写equals时老是要改写hashCode
hashCode对HashMap这种须要用到HashCode的容器具备很是重要的意义.
这里做者举了个电话号码的例子, 若是一个电话号码包括区号, 号码和分机号, 对象为PhoneNumer("0571", "88155188", "1234"), 可是未实现hashCode()方法;在hashMap中用PhoneNumber做为key, 用户名做为value这样保存, hashMap.put(new PhoneNumer("0571", "88155188", "1234"),"jenny"), 若是用PhoneNumer("0571", "88155188", "1234")到hashMap中取得的倒是null
计算hashCode, 17是一个不错的初始值, 37是一个不错的乘数因子, 计算hashcode, 通常采用这种写法:
Java代码
- result = 17;
- result = result * 37 + property1 hashcode;
- result = result * 37 + property2 hashcode;
- result = result * 37 + property3 hashcode;
第12条 使类和成员的可访问能力最小化
若是须要声明一个共有的静态final数组时, 应该将其声明为私有, 而后提供一个非可变的 final List, 好比下面的作法:
Java代码
- private static final type[] PRIVATE_VALUES = {...};
- public static final List VALUES = Collections.unmodiableList(Arrays.asList(PRIVATE_VALUES));
还有一个作法是经过一个公共的方法, 而后返回数组的clone, 即保护性拷贝
第14条 复合优于继承
这是一个老生常谈的话题, 继承的优势是有利于重用, 可是却带来安全的问题.
在一个包内的继承是能够接受的, 由于包内的继承几乎能够被认为是在同一个程序员的控制范围以内, 还有只有那些专门为继承而设计的基类, 这样的继承基本上也是安全的, 而对于普通的具体类使用继承的一个问题就是破坏了原有类的封装性, 从而给程序埋下了bug的种子.
这里做者举了继承HashSet实现计数功能的例子, 计数针对add和addAll方法来进行, 可是addAll内部是调用add方法来添加元素的, 而子类的计数功能必须依赖父类这个并无承诺的实现细节, 若是父类addAll方法的实现细节发生改变, 将致使子类的计数功能失败. 为了保证子类的计数功能不依赖父类的实现, 一般不得已的作法是重写父类的相关代码.
盲目的继承会存在各类问题, 而优于继承的复合却能克服这些问题, 做者针对上面的问题用复合来举例子, 让用于计数的HashSet实现Set接口, 内部持有一个真正的HashSet实例, 而后将全部的方法转发到HashSet实例上, 这样整个计数Set彻底不用依赖HashSet内部的实现细节, 除了带来健壮性外, 还带来了灵活性
只有两个类A和B之间存在is-a关系的时候(B确实是一个A), 才应该采用继承.
第15条
好的API文档应该描述一个方法作了什么工做, 而并不是描述它是如何作到的.
构造函数最好不要调用(或依赖)可被改写的方法, 这个主要是处于初始化的考虑, 由于父类的构造函数会在子类构造函数初始化以前被调用, 而被改写的方法又依赖于子类是否初始化, 这样致使初始化失败.
第16条 接口优于抽象类
抽象类主要处理具备层次关系的架构(在这种场景下, 抽象类可能优于接口了), 而接口则能够不受这个应用场景的限制, 他能够为类mixin新的功能.
第17条 接口只能用于定义类型
这个主要是做者针对在接口中定义常量的作法的控诉, 我以为没那么严重, 只要没有让须要使用常量的类继承常量接口便可, 这样常量接口跟在类中定义常量没有区别.
第18条 优先考虑静态成员类
若是你声明的成员类不要求访问外围实例, 那么请记住把static修饰符放到成员类的声明中.
非静态成员类的每个实例都隐含的与外围类的一个实例紧密的关联在一块儿, 这样非静态成员类的实例才能调用外围类实例的方法.
做者举了几个Collection中的例子.一个是List中的Iterator, Map中的keySet, valueSet, 由于他们本身都不缓存数据, 须要访问外部类实例中数据, 所以必须是非静态的内部类.而对于HashMap中的每一对键值(key-value)的包装类Entry来讲, 虽然一个Entry须要与一个具体的Map关联, 可是它的getValue, setValue等方法并不须要访问外部类HashMap, 而是在建立每个Entry的时候就已经从Map中拿到key, value并塞到Entry中去了.
对于匿名类, 主要用来封装那些很是简短的逻辑, 若是代码超过20行, 则采用匿名类就不是很理想了.
第28条 为导出的API编写文档
为API编写的JavaDoc文档应该简洁的描述它与客户之间的约定, 应该说明它作了什么, 而不是描述它如何作的, 若是是方法的话, 最好描述它的前置条件(调用这个方法必须知足的条件)和后置条件(成功调用以后, 哪些条件必须知足), 通常状况下, @throws中隐含的非检查异常就是前置条件 ,由于每个非检查异常就意味着一个不知足的条件, 有时候也在@param中来讲明前置条件.
此外还应该描述方法的反作用, 也就是对其余相关内容形成的影响.
在写方法的JavaDoc的时候, 通常@param和@return后面应该接一个名词, 而@throws 应该以"若是"开头来代表异常将在什么状况下抛出来.
对于方法, 类, 属性的描述有一些不成为的规律:
对于方法和构造函数的概要描述, 应该是一个动词短语, 好比
ArrayList(int)描述为:用指定的初始容量构造一个空的列表
Collection.size() 返回集合中元素的数目
对于类, 接口和属性, 概要描述应该是一个名词短语,好比
TimerTask:能够被一次调度的一项任务.
第29条 将局部变量的做用域最小化
这里说明了for循环比while要好的例子.
由于while须要在循环以外定义变量, 而for循环则被包含在了循环内部, 这样就能够避免那些偷懒拷贝代码带来隐患
第30条 了解和使用库
主要是为了说明避免重复发明轮子.
每一个程序员应该熟悉java.lang, java.util甚至java.io类库
另外apache的一些库也是咱们每一个人应该熟悉的:)
第31条 若是要求精确计算, 请避免使用float和double
就是说用float, double来进行涉及到小数的计算是不靠谱的, 应该转换成int和long或者使用BigDecimal.
第39条 只针对不正常的条件才使用异常
异常永远不能用于正常的控制流
第40条 对于可恢复的条件使用被检查的异常, 对于程序错误使用运行时异常
其实咱们的程序中, 抛出的异常绝大部分是不可恢复的.所以应该避免使用被检查的异常
第42条 尽可能使用标准的异常
IllegalStateException, 这个异常应该属于经常使用的异常, 若是在一个对象被正确初始化以前被调用, 那么应该抛出这个异常.
通常来讲, 全部错误方法的调用能够归结为非法的参数和非法的状态, 可是咱们通常不能笼统的抛出IllegalArgumentException, 而应该使用更明确说明异常缘由的异常, 好比NullPointerException, IndexOutOfBoundsException等.
第48条 对共享可变数据的同步访问
这里举了个很好的使用同步的例子:使用双检查, 将同步缩小在最小范围
延迟初始化, 通常会这样写:
Java代码
- private static Foo foo = null;
- public synchronized static Foo getFoo(){
- if (foo == null){
- foo = new Foo();
- }
- return foo;
- }
可是能够将同步进一步缩小, 这里采用双检查模式, 在foo初始化以后, 不用再被同步(可是可能存在部分初始化的问题):
Java代码
- public static Foo getFoo(){
- if (foo == null){
- synchronized(Foo.class){
- if (foo == null){
- foo = new Foo();
- }
- }
- }
- return foo;
- }
可是更精妙的作法是彻底不用同步:
Java代码
- private static class FooHolder{ static final Foo foo = new Foo();}
- public static Foo getFoo(){return FooHolder.foo;}
他主要利用了java语言中的"只有当一个类被用到的时候才被初始化"
欢迎关注本站公众号,获取更多信息