《Effective in java》 读书笔记
来源:互联网 发布:gdb调试java 编辑:程序博客网 时间:2024/04/29 10:55
第一章 创建和销毁对象
第一条 考虑使用静态工厂方法(static factory method)代替公有的构造方法
客户需要得到一个类的实例方法有二。提供一个公有的构造函数,或者是使用静态工厂方法。静态工厂方法是一个静态的方法它返回的是一个类的实例。
好处:1.容易阅读,静态工厂方法有名字,eg,GeometryFactory.getInstance();
public class Foo{ private Foo instance = null; private Foo(){}//私有的构造方法 public static Synchronizeed Foo getInstance(){ if(instance == null){ instance = new Foo(); } return instance; } }
List<String> list = java.util.Collections. EMPTY_LIST ;
public static final List EMPTY_LIST = new EmptyList(); private static class EmptyList extends AbstractList<Object> implements RandomAccess, Serializable { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8842843931221139166L; public int size() {return 0;} public boolean contains(Object obj) {return false;} public Object get(int index) { throw new IndexOutOfBoundsException("Index: "+index); } // Preserves singleton property private Object readResolve() { return EMPTY_LIST ; } }
缺点:1.如果不含有公有的或者受保护的公有构造函数就不能被子类化。既不能被继承。因为子类的构造函数需要调用父类的构造函数,但是父类的构造函数是私有的不能被外部方法调用,只能被本类方法调用。推荐使用复合结构。
第三条 通过私有构造函数强化不可实例的能力
public class Collections { // Suppresses default constructor, ensuring non-instantiability. private Collections () {}
第四 避免创建重复对象
通过静态工厂方法和构造函数私有化都可以做到避免创建重复对象。
1)重用非可变对象。在使用非可变类(如:String) 时,应该做到重复使用同一对象,String str="Kill";而避免使用String str=new String("Kill");
2)对可变类的使用也可以重复使用其对象
public Date isbirthday(){ Calendar c=new Calendar(); Date beginDate=c..... Date endDate=c.... return beginDate.compareTo(endDate);} 改进 使用静态化的初始化器 public static final Date BEGINDATE; public static final Date ENDDATE; static { Calendar c=new Calendar(); beginDate=c..... endDate=c....}public Date isbirthday(){ return beginDate.compareTo(endDate);}
这样 日期对象c,beginDate endDate在任何时刻只是实例化了一次,而不是每次在调用方法isbirthday()时被实例化一次。
第五点 消除过期对象引用
public synchronized E pop() { E obj; int len = size(); obj = peek(); removeElementAt (len - 1); return obj; } public synchronized void removeElementAt (int index) { modCount++; if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } else if (index < 0) { throw new ArrayIndexOutOfBoundsException(index); } int j = elementCount - index - 1; if (j > 0) { System.arraycopy(elementData, index + 1, elementData, index, j); } elementCount--; }
public synchronized void removeElementAt(int index) { modCount++; if (index >= elementCount) { throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount); } else if (index < 0) { throw new ArrayIndexOutOfBoundsException(index); } int j = elementCount - index - 1; if (j > 0) { System.arraycopy(elementData, index + 1, elementData, index, j); } elementCount--; elementData[elementCount] = null; /* to let gc do its work */ }清理过期对象引用的好处是:如果在以后又被使用到该引用,最会抛下NullPointException而不是让程序继续错误的运行下去,尽可能早的监测出程序中的错误总是有好处的。
方法:重新使用这个已经指向一个对象的引用,或结束其生命周期。
一般而言当一个类自己管理起内存,很容易引起内存泄漏。比如stack 。
避免使用终结函数finalize(),首先说一下finalize()和system.gc()的区别,前者是回收栈中的引用,而后者是回收堆中的对象。JVM 调用垃圾回收器(system.gc())释放被heap中没用的对象占有的资源。如果当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。由于终结函数的执行是在对象在释放之前被调用,也就是说在这个对象被认为没用到释放是有时间段的。有可能还没来得及执行终结函数,程序就已经当掉了。所以时间关键的任务不应该由终结函数来完成。
那么一个类封装了的资源如线程,文件确实要回收,我们应该怎么办才能不需要编写终结函数呢。提供显式的终结方法,如流中,文件中的close(),线程中的cancel()。显式的终止方法常与try-finally结构结合起来使用,以确保其及时终止。
第二章 对所有对象都通用的方法
equals(),hashCode(),toString(),clone(),finalize()
(1) equals方法一般用于“值类”的情形,比如Integer,Date目的是为了比较两个指向值对象的引用的时候,希望
它们的逻辑是否相等而不是它们是否指向同一个对象。(和equals本意不符啊。)
约定
a 自反性 对任意的对象必须和它自身相等。对值引用x x.equals(x) 一定返回true
b 对称性 对任意的值引用x,y,如果x.equals(y) 一定有y.equals(x)
典型的例子
public Point{ private int x; private int y; public boolean equals(Object o){ if(o==this){ return true; } if(!(o instanceof Point)){ return false; } Point p=(Point)o; return p.x==x&&p.y==y; }) } public ColorPoint extends Point{ private Color color; public ColorPoint(int x,int y,Color color){ super(x,y); this.color=color; } public boolean equals(Object o){ if(this==o)return true; if(!(o instanceof ColorPoint)) return false; ColorPoint cp=(ColorPoint)o return super.equals(o)&&cp.color==color; } }存在问题
如果 Point p=new Point(1,2);
ColorPoint cp1=new ColorPoint(1,2,Color.RED);
p.equals(cp1) //虽然忽视了Color属性但还是返回true。调用的是Point 的equals方法。
cp1.equals(p)//实参类型 p instanceof ColorPoint不一致返回false;
public boolean equals(Object o) {if (this == o)return true;if (!(o instanceof Point))return false;//只是坐标点比较,忽略颜色if (!(o instanceof ColorPoint))return o.equals(this);//带颜色比较ColorPoint cp = (ColorPoint) o;return super.equals(o) && cp.color == color;}
p.equals(cp2) 返回true
但是 cp1.equals(cp2) 返回false; 违反了传递性。
c 传递性 对任意的值引用x,y,z,如果x.equals(y),y.equals(z) 一定有x.equals(z)
结论:要想在扩展一个可实例化的类的同时,即要保证增加新的特性,又要保证equals约定,
建议复合优于继承原则。重新设计ColorPoint类
若类和类是 a kind of 关系则用继承
若类和类是 a part of 关系则用组合(复合)
public class ColorPoint{ private Point point; private Color color; boolean equals(Object o){ ...... ColorPoint cp=(ColorPoint)o; return cp.point.equals(point)&&cp.color.equals(color); } }一个完整的equals()方法
public class A{ private property1; private property2; public boolean equals(Object o){ if(o==this) return true; if(!(o instanceof A)) return false; A a=new A(); return a.property1==property&&a.property2==property2; } }(2) hashCode()
相等的对象必须要有相等的散列码,如果违反这个约定可能导致这个类无法与某些散列值得集合结合在一起使用
所以在改写了equals方法的同时一定要重写hashCode方法以保证一致性。
重写hashCode()规则:
1.把某个非零常数值(如17)保存在一个叫result的int类型的变量中;
2.对于对象中每个关键字域f(指equals方法中考虑的每一个域),完成以下步骤:
为该域计算int类型的散列码c:
1).如果该域是bloolean类型,则计算(f?0:1)
2).如果该域是byte,char,short或int类型,则计算(int)f
3).如果该域是long类型,则计算(int)(f^(f>>>32))
4).如果该域是float类型,则计算Float.floatToIntBits(f)
5).如果该域是double类型,则计算Double.doubleToLongBits(f)得一long类型值,然后按前述计算此long类型的散列值
6).如果该域是一个对象引用,则利用此对象的hashCode,如果域的值为null,则返回0
7).如果该域是一个数组,则对每一个数组元素当作单独的域来处理,然后安下一步的方案来进行合成
3.利用下面的公式将散列码c 组合到result中。
result=37*result+c;
比如一个Employee 对象的两个域,id,name在equals中都可虑了
public int hashCode(){
int result = 17;
result = 37*result + id;
result = 37*result + name.hashCode();
}
(3) toString() 重写toString以包含有价值的信息
(4) Comparable 接口
信息隐藏(封装)可以有效的解除一个系统中各个模块之间的耦合关系。
1)使得各个模块可以独立的开发、测试、优化、修改、测试
2)模块可以并行的开发,加快开发速度
2.1 非可变类
一个非可变类是一个简单的类,它的实例不能被修改。每个实例中包含的所有信息都必须在该实例被创建的时候提供出来,并且在该类的整个生命周期内(lifetime)内固定不变,如:String BigDecimal.非可变类运算返回的是一个新的实例,而不是对原来实例的修改,这种做法称为函数的做法,因为这些方法返回了一个函数的结果。其对应的是过程的做法,方法内部有一个过程的作用在它们的操作数上使得它的状态发生了改变。要成为非可变类的原则是:a,不提供修改该对象的方法。
(3)接口优于抽象
(4)常量接口,在接口的设计中有一种接口,只有静态常量我们称为常量接口。
缺点:如果实现这个常量接口的实现类改变了,而不需要这个接口里的值。但这为了维护一致性还得修改这个接口。
如果要导出常量方法有三
a,把常量添加到和它紧密相关的类或接口中。
b,定义一个安全枚举类
c,定义一个工具类
public class Physical{ private Physical(){} private static final double MAX_VALUE=10.0; private static final double MIN_VALUE=12.0; }
第四章 方法
方法的参数有某些限制,如,索引必须是非负数,对象引用必须不能为null。对公有方法通过@throws 记录被抛出的异常
eg:
@param @return @throws IllegalArgumentException if x<0 public BigDecimal(int x){ if(x<0){ throw IllegalArgumentException("参数错误"); } }
比如:Collections.sort(list).列表中的所有对象必须是可以相互比较的,在排序列表的过程中
每个列表中的每个对象与其它对象进行比较,如果对象是不可比较的,则抛出ClassCastExcrption.所以提前检查列表中的元素是否可比较是没有什么意义的。
在对象本身不提供帮助的情况下,另一个类要想修改这个对象的内部状态是不可能的。但又些情况下提供这种帮助又非常容易的。
eg:
import java.util.Date;import java.lang.IllegalArgumentException;public class Period{ private final Date start; private final Date end;//(1) public Period(Date start,Date end){ if(start.compareTo(end)>0){ throw new IllegalArgumentException("参数错误"); } this.start=start; this.end=end; } //(2)对构造方法的可变参数进行保护性克隆 public Period(Date start,Date end){ this.start=new Date(start.getTime());//是用新对象 this.end=new Date(end.getTime()); if(start.compareTo(end)>0){ throw new IllegalArgumentException("参数错误"); } public static void main(String []args){ Date start=new Date(); Date end=new Date(); Period p=new Period(start,end); System.out.println(p.start+""+p.end); end.setYear(78);//由于Date对象是可变的,这是最关键的,所以导致Period对象改变了 System.out.println(p.start+""+p.end); }}
a,避免长的参数列表,尤其是参数相同的参数列表。
b,对参数类型使用接口,而不是接口的实现类。
c,谨慎使用重载。
import java.util.Set;import java.util.HashSet;import java.util.List;import java.util.ArrayList;import java.util.HashMap;import java.util.Collection;public class CollectionsClassifier{ public static String classify(Set s){ return "Set"; } public static String classify(List List){ return "List"; } public static String classify(Collection c){ return "collections"; } public static void main(String []args){ Collection colles[]=new Collection[]{new HashSet(),new ArrayList(),new HashMap().values()}; for(int i=0;i<colles.length;i++){ System.out.println(classify(colles[i])); } }}运行结果: collections
collections
程序的意图:期望编译器根据参数的运行时类型自动将调用分发给适当的重载方法。显然方法重载是不能满足客户
要求。
改写classify()方法
public static String classify(Collection c){ if(c instanceof Set) return "Set"; if(c instanceof List) return "List"; if(c instanceof Collection) return "Collection";}注意:对for循环的三次迭代,参数的编译类型都是相同的:Collection。所以每次迭代调用都是classify(Collection)方法
而每次运行时的类型是不一样的。
补充:重载(overloaded method) 选择的是静态的。选择的依据是参数类型
重写(oveeridden method) 选择的依据是被调用方法所在对象的运行时的类型。
class A{ String getName(){return "A";} } class B extends A{ String getName(){return "B";} } class C extends A{ String getName(){return "C";} } public class Overriden{ public static void main(String []args){ A ArrayTest[]=new A[]{new A(),new B(),new C()}; for(int i=0;i<ArrayTest.length;i++){ System.out.println(ArrayTest[i].getName()); } }}运行结果:A B C。虽然编译的类型都是A但调用方法是改对象的类型是不一样的。
原则:对于多个具有相同参数数目的方法来说,尽量避免重载方法。
第五章 通用设计方法
(1) 将局部变量的作用域最小化
a.在第一次使用这个变量的地方声明它。以防止带来可读性差和作用域的问题。
b.几乎每个作用域的声明都应该包含一个初始化表达式。
for(Iterator iterator = c.iterator;iterator.hasNext();) { doSomething(iterator.next());}Iterator iterator = c.iterator;while(iterator.hasNext()) { doSomething(iterator.next());}Iterator iterator1 = c.iterator;while(iterator.hasNext()) { //BUG。 一不小心,用了上一个变量iterator。 doSomething(iterator1.next()); }
(3) 对数量大的字符串连接使用StringBuffer而不是String前者速度快。
(1) 异常只应用于不正常的条件,它们永远不能应用于正常的控制流程。如果一个类有一个“状态相关的方法”
即只有在特定的不可预知的条件下才可以使用的方法,那么这个类也往往有一个单独的“状态测试方法”。如Iterator
有一个状态相关方法next(),那么对应就有一个状态测试方法 hasNext().
(2) 异常分类
非检查型异常 (run-time Exception,Error) NullPointException IndexOutOfBoundsException ArrayOutOfBoundsException IllegalArgumentExcepton ClassCastException
检查型异常 IOException FileNotFoundException ClassNotFoundException NoSuchMethodException SQLException 必需要有try-catch捕获或者传播到外面(抛出)
使用原则:如果期望用户能够恢复,则使用检查性异常,通过catch捕获后处理(如IO流没关闭,找不到文件,则可以在捕获后关闭流和创建文件).
如果一个程序捕获到一个非检查型异常或者一个错误则往往是不可恢复的情况,继续执行下去有害无益。
保持异常的原子性:当一个对象抛出一个异常的时候,我们总是希望这个对象仍然能够保持在一种良好的可用状态之中。
eg:
public Object pop(){ Object result=element[--size]; element[size]=null;//垃圾回收; return result; }
解决方法:对计算处理顺序的调整,使得任何可能失败的计算部分都发生在对象状态被修改之前。
public Object pop(){ if(size==0){ throw new EmptyStackException(); } Object result=element[--size]; element[size]=null;//垃圾回收; return result; }
第七章 线程
在JAVA中建立线程并不困难,所需要的三件事:执行的代码、代码所操作的数据和执行代码的虚拟CPU。
(1)两个简单实例
eg:Thread public class ThreadDemo extends Thread{ private String str=""; //1.代码操作的数据 public ThreadDemo(String str){ this.str=str; } public void run(){ System.out.println("str="str); //2.执行代码 } public static void main(String []args){ ThreadDemo td=new ThreadDemo("线程1");//3.cpu td.start(); } } eg:Runnable public class RunnableDemo implements Runnable{ private String str=""; public RunnableDemo(String str){ this.str=str; } public void run(){ System.out.println("pp"+str); } public static void main(String []args){ RunnableDemo rd=new RunnableDemo("hello"); Thread t=new Thread(rd); t.start(); } }
请注意,当使用 runnable 接口时,您不能直接创建所需类的对象并运行它;必须从 Thread 类的一个实例内部运行它。
(2)synchronized 关键字可以保证在同一时刻,只有一个线程在执行一条语句,或者一段代码.
Object.wait()
Object.notify()
Object.notifyAll()
Thread.yield()
Thread.sleep()
第八章 序列化
对象序列化的本质就是把一个对象编码成一个字节流。一旦一个对象被序列化以后,它们编码可以从一个正在运行的虚拟机被传递到另一个虚拟机上或者被存储到磁盘上面,以便以后反序列化使用。
- 《Effective in java》 读书笔记
- 《Effective in java》 读书笔记
- 《Effective Java》读书笔记之一
- 《Effective Java》读书笔记
- Effective Java读书笔记
- Effective Java 读书笔记
- 《Effective Java》读书笔记
- 《Effective Java》读书笔记之一
- Effective java 读书笔记
- Effective Java读书笔记
- Effective java 读书笔记
- Effective Java读书笔记
- Effective Java读书笔记一
- Effective Java读书笔记二
- Effective Java读书笔记三
- Effective Java读书笔记四
- Effective Java读书笔记五
- Effective Java读书笔记六
- video4linux--4
- String详解
- projecteuler.net解题记录,参考了肥猫的(第14题)
- A Simple PE Test
- 字符设备文件转
- 《Effective in java》 读书笔记
- JAVA排序算法实现代码-快速(Quick Sort)排序
- 爱你的人在等待~~~
- 生命的特度
- c# foreach语句循环取各对象
- JAVA排序算法实现代码-堆(Heap)排序
- [ZT]泛化,关联,聚合,合成,依赖的关系
- JAVA排序算法实现代码-选择(Select)式排序
- JAVA排序算法实现代码-二叉树排序