现在的位置: 首页 > 综合 > 正文

读《Effective java 中文版》(5)

2013年12月10日 ⁄ 综合 ⁄ 共 8011字 ⁄ 字号 评论关闭

FROM 竹笋炒肉 http://hedong.3322.org/

读《Effective java 中文版》(5)

第40条:三种可抛出结构的使用
  对于可恢复的条件,使用被检查的异常;对于程序错误,使用运行时异常;错误往往被JVM保留用于指示资源不足、约束失败、或其它使程序无法继续执行的条件。

  对于一个方法声明要抛出的每一个被检查的异常,它是对API用户的一种潜在指示:与异常相关联的条件是调用这次个方法的一种可能结果。
  两种未被检查的可抛出结构:运行时异常和错误,在行为上相同的,它们都不需要、也不应该被捕获的抛出物。你所实现的所有未被检查的抛出结构都应是RuntimeException的子类。定义一个非Exception、RuntimeException或Error子类的抛出物是可行的,但从行为意义上它等同于被普通的被检查异常(即Exception子类而非RuntimeException子类).
  异常是个完全意义上的对象,在其上可以定义任意的方法。因被检查的异常往往指示了可恢复的条件,所以可通过定义方法,使调用者可获得一些有助于恢复的信息。

 

读《Effective java 中文版》(42)
第41条:避免不必要地使用被检查的异常
  与返回代码不同,被检查的异常强迫程序处理例外的情况,从而大大地提高了程序的可靠性。而过分地使用被检查的异常,则增加了不可忽视的负担。如果正确地使用API并不能阻止这种异常条件的产生,并且一旦产生了异常,使用API的程序可以采取有用的动作,那么这种负担被认为是正当的。

  }catch(TheCheckedException e){
e.printStackTree();
System.exit(1);
  }
  如果使用API的程序员无法做得比这更好,那么未被检查的异常可能更为合适。在实践中,catch几乎总有断言失败的特征。
  “把被检查的异常变成未被检查的异常”的一种技术是,把这个要抛出异常的方法分成两个方法,第一个方法返回一个boolean以指明是否要抛出异常,另一个执行真正的功能,如果条件不满足就抛异常。如下:
//Invocation with checked exception
try{
obj.action(args);
}catch(TheCheckedException e){
//Handle exception condition
}
  转换为:
//Invocation with state-testing method and unchecked exception
if(obj.actionPermitted(args)){
obj.action(args));
}else{
//handle exception condition
}
  当然这种转换并不总是合适的,例如一对象将在缺少外部同步的情况下被并发访问,或者可被外界改变状态,那么这种转换将是不合适的。

 

读《Effective java 中文版》(43)
第42条:尽量使用标准的异常
  java平台库中讫今为止最常被重用的异常如下:
异常 使用场合
IllegalArgumentException 参数值不合适
IllegalStateException 对于这个方法调用而言,对象的状态不合适(如初始化不恰当)
NullPointerException 在null被禁止的情况下参数值为null
IndexOutOfBoundsException 下标越界
ConcurrentModificationException 在禁止并发修改的情况下,对象检测到并发修改
UnsupportedOperationException 对象不支持客户请求的方法
  
  其它的异常也可以使用,只要确保抛出异常的条件与文档要求一致即可。

 

读《Effective java 中文版》(44)
第43条:抛出的异常要适合于相应的抽象
  高层的实现,应该捕获低层的异常,同时抛出一个可以按照高层抽象进行解释的异常,这种做法叫做异常转译(exception translation)。即如:
//exception translation!
try{
//use lowlevel abstraction to do our bidding
...
}catch(LowerLevelException e){
throw new HigherLevelException(...);
}
  低层的异常被高层的异常保存起来,且高层的异常提供一个公有的访问方法来获得低层的异常,这种做叫做异常链接(exception chaining)。
//Exception chaining.
try{
//use lower-level abstraction to do our bindding
...
}catch(LowerLevelException e){
throw new HigherLevelException(e);
}

  异常链的实现非常简单,在1.4及以后版本中,可以通过Throwable来获得支持。
//Exception chaining in release 1.4 or later
HigherLevelException(Throwable t){
super(t);
}
  如果是在早期java版本中,则需要先将其保存:
//Exception chaining prior to release 1.4
private Throwable cause;
HigherLevelException(Throwable t){
cause=t;
}
public Throwable getCause(){
return cause;
}
  处理来自低层的异常,

最好的做法是,在调用低层方法之前通过一些检查等手段来确保它们会成功执行;

其次的做法是,让高层处理这些异常,从而将高层方法的调用者与低层的问题隔离开;

一般的做法是使用异常转译;

如果低层方法的异常对高层也是合适的,则将其从低层传到高层。

 

 

 

 

读《Effective java 中文版》(48)
第47条:不要忽略异常
  异常的目的是强迫你处理不正常的条件,空的catch块会使异常达不到应有的目的,至少catch块中也应包含一条说明,用来解释为什么忽略掉这个异常。这对被检查的异常和未被检查的异常都适用。
  简单地将一个未被检查的异常传播到外界至少会使程序迅速地失败,从而保留了有助于调试该失败条件信息,比异常被忽略后的一个不可预测的时刻程序失败这种情况要强。

 

读《Effective java 中文版》(49)
第48条:对共享可变数据的同步访问
  同步,不仅可以阻止一个线程看到对象处于不一致的状态中,它还可以保证通过一系列看似顺序执行的状态转变序列,对象从一种一致的状态变迁到另一种一致的状态。
  synchronized关键字可以保证在同一时刻,只有一个线程在执行一条语句,或者一段代码块。java语言保证读或写一个变量是原子的,除非这个变量的类型是long或double.

  java的内存模型决定,为了在线程之间可靠地通信,以及为了互斥访问,对原子数据的读写进行同步是需要的。看一个可怕的例子:
//Broken - require synchronization!
private static int nextSerialNumber=0;
public static int generateSerialNumber(){
return nextSerialNumber++;
}
  对其改进,只需要在generateSerialNumber的声明中增加synchronized修饰符即可。
  为了终止一个线程,一种推荐的做法是让线程轮询某个域,该域的值如果发生变化,就表明此线程就应该终止自己。下面的例子就是这个思路,但在同步出了问题。
//Broken - requires synchronization
public class StoppableThread extends Thread{
private boolean stopRequested=false;
public void run(){
boolean done=false;
while(!stopRequested && !done){
...//do what needs to be done in the thread
}
}
public void requestStop(){
stopRequested=true;
}
}
  对其改进如下:
//Properly synchronized cooperative thread temination
public class StoppableThread extends Thread{
private boolean stopRequested=false;
public void run(){
boolean done=false;
while(!stopRequested() && !done){
...//do what needs to be done in the thread
}
}
public synchronized void requestStop(){
stopRequested=true;
}
private synchronized boolean stopRequested(){
return stopRequested;
}
}
  另一种改进是,将stopRequested声明为volatile,则同步可以省略。

  再来看迟缓初始化(lazy initialization)问题,双重访问模式并不一定都能正常工作,除非被共享的变量包含一个原语值。看例子:
//The double-check idion fro lazy initialization - broken!
private static Foo foo=null;
public static Foo getFoo(){
if (foo==null){
synchronized(Foo.class){
if(foo==null)foo=new Foo();
}
}
return foo;
}
  最容易的修改是省去迟缓初始化:
//normal static initialization (not lazy)
private static finall Foo foo=new Foo();
public static Foo getFoo(){
return foo;
}
  或者使用正确的同步方法,但可能增加少许的同步开销:
//properly synchronized lazy initialization
private static Foo foo=null;
public static synchronized Foo getFoo(){
if(foo==null)foo=new Foo();
return foo;
}
  按需初始化容器模式也不错,但是它只能用于静态域,不能用于实例域。
//The initialize-on-demand holder class idiom
private static class FooHolder(){
static final Foo foo=new Foo();
}
public static Foo getFoo(){ return FooHolder.foo;}
  简而言之,无论何时当多个线程共享可变数据的时候,每个读或写数据的线程必须获得一把锁。如果没有同步,则一个线程所做的修改就无法保证被另一个线程所观察到。

 

读《Effective java 中文版》(52)
第51条:不要依赖于线程调度器
  不能让应用程序的正确性依赖于线程调度器。否则,结果得到的应用程序既不健壮也不具有可移植性。作为一个推论,不要依赖Thread.yield或者线程优先级。这些设施都只是影响到调度器,它们可以被用来提高一个已经能够正常工作的系统的服务质量,但永远不应用来“修正”一个原本并不能工作的程序。
  编写健壮的、响应良好的、可移植的多线程应用程序的最好办法是,尽可能确保在任何给定时刻只有少量的可运行线程。这种办法采用的主要技术是,让每个线程做少量的工作,然后使用Object.Wait等待某个条件发生,或者使用Thread.sleep睡眠一段时间。

 

读《Effective java 中文版》(53)
第52条:线程安全性的文档化
  每个类都应该清楚地在文档中说明它的线程安全属性。在一个方法的声明中出现synchronized修饰符,这是一个实现细节,并不是导出的API文档的一部分。

  一个类为了可被多个线程安全地使用,必须在文档中清楚地说明它所支持的线程安全性级别。

非可变性(immutable)-这个类的实例对于其它客户而言是不变的,不需要外部的同步。参见13条。

线程程安全的(thread-safe)-这个类的实例是可变的,但是所有的地方都包含足够的同步手段,这些实例可以被并发使用无需外部同步。

有条件的线程安全(conditionally thread-safe)-这个类(或关联的类)包含有某些方法,它们必须被顺序调用,而不能受到其它线程的干扰,除此之外,这种线程安全级别与上一种情形相同。为了消除被其他线程干扰的可能性,客户在执行此方法序列期间,必须获得一把适当的锁。如HashTable或Vector,它们的迭代器要求外部同步。如:
Hashtable h=...;
synchronized(h){
for(Enumeration e=h.keys();e.hasMoreElements();)
f(e.nextElement());
}

线程兼容的(thread-compatible)-在每个方法调用的外围使用外部同步,此时这个类的实例可以被安全的并发使用。如ArrayList或HashMap

线程对立的(thread-hostile)这个类不能安全地被多个线程并发使用,即使所有的方法调用都被外部同步包围。通常情况下,线程对立的根源在于,这个类的方法要修改静态数据,而这些静态数据可能会影响到其它的线程。

  对于有条件的线程安全类,在文档中指明“为了允许方法调用序列以原子方式执行,哪一个对象应被锁住”.

 

读《Effective java 中文版》(54)
第53条:避免使用线程组
  除了线程、锁和监视器之外,线程系统还提供了一个基本的抽象,即线程组(thread-group)。然而线程组并没有提供太多有用的功能。
  一个例外是,当线程组中的一个线程抛出一个未被捕获的异常时,ThreadGroup.uncaughtException方法会被自动调用。“执行环境”使用这个方法,以便用适当的方式来响应未被捕获的异常。

 

 

读《Effective java 中文版》(57)
第56条:保护性地编写readObject方法
  编写一个类的readObject方法,相当于编写一个公有的构造函数,无论给它传递一个什么样的字节流,它都必须产生一个有效的实例。下面是缩写健壮的readObject方法的指导原则:

 

对于对象引用域必须保持为私有的类,对“将被保存到这些域中的对象”进行保护性拷贝。非可变类的可变组件就属于这一类别。

对于具有约束条件的类,一定要检查约束条件是否满足,如果不满足的话,则抛出一个InvalidObjectException异常。这些检查应跟在所有的保护性拷贝之后。

如果在对象图被反序列化之后,整个对象图必须都是有效的,则应该使用ObjectInputValidation接口。

无论是直接方式还是间接方式,都不要调用类中可被改写的方法。

  readResolve方法有可能取被用来替代保护性的readObject方法。

  不严格地说,readObject是一个“用字节流作为唯一参数”的构造函数。当面对一个人工伪造的字节流的时候,readObject产生的对象会违反它所属的类的约束条件。初步的方法,是在readObject方法进行约束性检查,如下例:
private void readObject(OjbectInputStream s) throws IOException, ClassNotFoundException{
s.defaultReadObject();
//Check that our invariants are satisfied
if(start.compareTo(end)>0) throw new InvalidObjectException(start+" after "+ end);
}
  对上述的防范仍可进行攻击:伪造一个字节流,这个字节流以一个有效的Period实例所产生的字节流作为开始,然后附加上两个额外的引用,指向Period实例中的两个内部私有Date域,攻击者通过引用攻击内部域。所以,当一个对象被反序列化的时候,对于客户不应该拥有的对象引用,如果哪个域包含了这样的对象引用,则必须要做保护性拷贝,这是非常重要的。如下例:
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException{
s.defaultReadObject();
start=new Date(start.getTime());
end=new Date(end.getTime());
if(start.compareTo(end)>0) throw new InvlaidObjectException(start+" after "+end);
}

 

读《Effective java 中文版》(58)
第57条:必要时提供一个readResolve方法
  无论是singleton,或是其他实例受控(instance-controlled)的类,必须使用readResolve方法来保护“实例-控制的约束”。从本质上来讲,readResovle方法把一个readObject方法从一个事实上的公有构造函数变成一个事实上的公有静态工厂。对于那些禁止包外继承的类而言,readResolve方法作为保护性的readObject方法的一种替代,也是非常有用的。

  如下sigleton类:
public class Elvis{
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
...
}
...//remainder omitted
}
  如果Elvis实例序列化接口,则下面的readResolve方法足以保证它的sigleton属性。
private Object readResolve() throws ObjectStreamException{
//return the one true elvis and let the GC take care of the Elvis impersonator
return INSTANCE;
}
  不仅仅对于singleton对象是必要的,readResolve方法对于所有其它的实例受控类(如类型安全枚举类型)也是必需的。
  readResolve方法的第二个用法是,就像在第56条建议的那样,作为保护性的readObject方法的一种保守的替代选择。此时,第56条中的readObject方法可以下例的例子替代:
//the defensive readResolve idiom
private Object readResolve() throws ObjectStreamException(){
return new Period(start,end);
}
  对于那些允许继承的类,readResolve方法可能无法替代保护性的readObject方法。如果超类的readResolve方法是final的,则使得子类实例无法被正常地反序列化。如果超类的readResolve方法是可改写的,则恶意的子类可能会用一个方法改写它,该方法返回一个受损的实例。

抱歉!评论已关闭.