[转]effective java 笔记3

from:http://blog.csdn.net/ilibaba/archive/2009/02/06/3866657.aspx

NO.18 优先考虑静态成员类

嵌套类只为它的外围类提供服务。

嵌套类分为四种:静态成员类、非静态成员类、匿名类和局部类(后面三种称为内部类),如果希望一个嵌套类的实例可以独立于它的外部类的实例而存在,则这个嵌套类应该设置成静态成员类,但是静态成员类不能访问非静态的外围类对象。非静态的内部类拥有一个指向外围对象的引用,所以可以访问外围类的所有成员,包括被声明为私有的成员。

匿名类的应用情境:

①创建一个函数对象;

②创建过程对象;例如Thread、Runnable、TimeTask

③在静态工厂方法的内部使用;

④在复杂的类型安全枚举类型(它要求为每个实例提供单独的子类)中,用于公有的静态final域的初始化器中;

总结:四种不同的嵌套类都有自己不同的用途,如果一个嵌套类需要在单个方法之外仍然是可见的,或者它太长,不适合放在一个方法内部,那么应该使用成员类。如果成员类的每个实例都需要一个指向其外围实例的引用,则把成员类做成非静态的;否则就做成静态的。假设一个嵌套类属于一个方法的内部,如果你只需要在一个地方创建它的实例,并且已经有了一个预先存在的类型可以说明这个类的特征,则把它做成匿名类;否则就做成局部类。

 

注:以下几点(NO.19 ~ NO.22)是讲解关于C语言结构的替代方案,由于本人只从事java的开发,所以跳过此章节,为了保证笔记的完整性,从其他地方转载NO.19 ~ NO.22的笔记。

NO.19 用类代替结构
     JAVA刚面世的时候,很多C程序员都认为用类来代替结构现在太复杂,代价太大了,但是实际上,如果一个JAVA的类退化到只包含一个数据域的话,这样的类与C语言的结构大致是等价的。
      比方说下面两个程序片段:

  1. class Point 
  2. private float x; 
  3. private float y; 

      实际上这段代码和C语言的结构基本上没什么区别,但是这段代码恐怕是众多OO设计Fans所不齿的,因为它没有体现封装的优异性,没有体现面向对象设计的优点,当一个域被修改的时候,你不可能再采取任何辅助的措施了,那我们再来看一看采用包含私有域和共有访问方法的OO设计代码段:

  1. class Point 
  2. private float x; 
  3. private float y; 
  4. public Point(float x,float y) 
  5.   { 
  6. this.x=x; 
  7. this.y=y; 
  8.   } 
  9. public float getX(){retrun x;} 
  10. public float getY(){return y;} 
  11. public void setX(float x){this.x=x;} 
  12. public void setY(float y){this.y=y;} 

        单从表面上看,这段代码比上面那个多了很多行,还多了很多函数,但是仔细想一下,这样的OO设计,似乎更人性化,我们可以方面的对值域进行提取,修改等操作,而不直接和值域发生关系,这样的代码不仅让人容易读懂,而且很安全,还吸取了面向对象程序设计的灵活性,试想一下,如果一个共有类暴露它的值域,那么想要在将来的版本中进行修改是impossible的,因为共有类的客户代码已经遍布各处了。
需要提醒一点的是,如果一个类是包级私有的,或者是一个私有的嵌套类,则直接暴露其值域并无不妥之处。

 

NO.20 用类层次来代替联合
我们在用C语言来进行开发的时候,经常会用到联合这个概念,比如:

  1. typedef struct{ 
  2. double length; 
  3. double width;   
  4. }rectangleDimensions_t; 

那我们在JAVA里面没有联合这个概念,那我们用什么呢?对!用继承,这也是JAVA最吸引我的地方之一,它可以使用更好的机制来定义耽搁数据类型,在 Bruce Eckel的Thinking in java里面也多次提到了一个和形状有关的例子,我们可以先笼统的定义一个抽象类,即我们通常所指的超类,每个操作定义一个抽象的方法,其行为取决于标签的值,如果还有其他的操作不依赖于标签的值,则把操作变成根类(继承的类)中的具体方法。
这样做的最重要的优点是:类层次提供了类型的安全性。其次代码非常明了,这也是OO设计的优点。而且它很容易扩展,即使是面向多个方面的工作,能够同样胜任。最后它可以反映这些类型之间本质上的层次关系,从而允许更强的灵活性,以便编译时类型检查。

 

NO.21 用类来代替enum结构
Java程序设计语言提出了类型安全枚举的模式来替代enum结构,它的基本思想很简单:定义一个类来代表枚举类型的单个元素,并且不提供任何公有的构造函数,相反,提供公有静态final类,使枚举类型中的每一个常量都对应一个域。 例如:

public class Suit{

    private final String name;

    private Suit(String name){this.name=name;}

    public String toString(){return name;}

    public static final Suit CLUBS=new Suit(“clubs”);

    public static final Suit DIAMONDS=new Suit(“diamonds”);

}

构造函数是私有的,客户没有办法创建或者扩展这个类。所以除了通过这些公有的静态final域导出的Suit对象之外,不会有其他对象存在,即使这个类没有被申明为final,也没有办法对它扩展,因为子类的构造函数必须调用超类的构造函数。
      类型安全枚举类型的一个缺点是,装载枚举类的和构造常量对象时,需要一定的时间和空间开销,除非是在资源很受限制的设备比如蜂窝电哈和烤面包机上,否则在实际中这个问题不会被考虑。
     总之,类型安全枚举类型明显优于int类型,除非实在一个枚举类型主要被用做一个集合元素,或者主要用在一个资源非常不受限的环境下,否则类型安全枚举类型的缺点都不成问题,依次,在要求使用一个枚举类型的环境下,我们首先应考虑类型安全枚举类型模式。

 

NO.22 用类和接口来代替函数指针
众所周知,JAVA语言和C的最大区别在于,前者去掉了指针,小生第一次接触JAVA的时候觉得好不习惯,因为突然一下子没了指针,觉得好不方面啊,C语言的精髓在于其指针的运用,而JAVA却把它砍掉了,让人好生郁闷,不过随着时间的推移,我渐渐明白了用类和接口的应用也同样可以提供同样的功能,我们可以直接定义一个这样一个类,他的方法是执行其他方法上的操作,如果一个类仅仅是导出这样一个方法,那么它实际上就是一个指向该方法的指针,举个例子:

  1. class StringLengthComprator{ 
  2. public int compare(String s1,String s2) 
  3. return s1.length()-s2.length(); 

这个类导出一个带两个字符串的方法,它是一个用于字符串比较的具体策略。它是无状态的,没有域,所以,这个类的所有实例在功能上都是等价的,可以节省不必要的对象创建开销。但是我们不好直接把这个类传递给用户使用,因为用户无法传递任何其他的比较策略。相反,我们可以定义一个接口,即我们在设计具体策略类的时候还需要定义一个策略接口:

  1. public interface Comparator{ 
  2. public int compare(Object o1,Object o2); 

具体的策略类往往使用匿名类声明。
      在JAVA中,我们为了实现指针的模式,声明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类,如果一个具体策略只被使用一次的话,那么通常使用匿名类来声明和实例化这个具体策略类,如果一个策略类反复使用,那么它的类通常是一个私有的的静态成员类。

NO.23 检查参数的有效性

非公有的方法我们应该用断言的方法来检查它的参数,而不是使用通常大家所熟悉的检查语句来检测。如果我们使用的开发平台是JDK1.4或者更高级的平台,我们可以使用assert结构;否则我们应该使用一种临时的断言机制。

有些参数在使用过程中是先保存起来,然后在使用的时候再进行调用,就必须做好检查工作,否则程序可能会抛出一些异常让你摸不着头脑(如常见的空指针异常),也不能马上定位问题的所在位置,构造函数正是这种类型的一种体现,所以我们通常对构造函数参数的有效性检查是非常仔细的。

总之,当编写一个方法或者构造函数的时候,应该考虑对应它的参数有哪些限制,并且要把这些限制写到文档中,在方法体的起始处,通过显示的检查来实施这些限制。

 

NO.24 需要时使用保护性拷贝

假设类的使用者会尽一切手段来破坏这个类的约束条件,在这样的前提下,你必须保护性地设计程序。面对客户的不良行为时仍然能保持健壮性的类。
对于一个非可变类,可以考虑对其构造函数的可变参数采用保护性拷贝,如

  1. public period(Date start, Date end){ 
  2. this.start = new Date(start.getTime()); 
  3. this.end = new Date(start.getTime()); 
  4. // 接着做其他逻辑(保护性拷贝要在其他逻辑之前进行,并且有效性检查是针对拷贝后的对象,而不是原始对象)

对获取参数的get方法也要采用clone的方式返回,如:

  1. public Date getStart(){ 
  2. return (Date)start.clone(); 

保护性拷贝是在检查参数有效性之前进行的,检查的是拷贝后的对象,避免另外一个线程改变原始的参数对象。

记住非零长度的数组总是可变的,尽量使用非可变的对象作为内部组件,这样就不必关心保护性拷贝问题.

 

NO.25 谨慎设计方法的原型

1、谨慎选择方法的名字

     ① 选择易于理解的,并且与同一个包中的其他名字风格一致;

     ② 选择与大众认可的名字一致;

2、不要过于追求提供便利的方法。过多的方法会增加类的学习和使用成本,只有当一个操作被用得非常频繁的时候,才考虑为他提供一个快捷方法。

3、避免过长的参数列表。太长的参数不便于使用者使用,尤其是参数类型相同的时候,很容易产生参数传递错误的问题。避免此类错误的方法:

     ① 可以把一个方法分解成多个方法;

     ② 可以创建一个辅助类(helper class)。将参数组织成一个类作为参数传入;

4、对于类型参数,优先使用接口,而不是类。如参数为Map的时候,该方法可以接收Hashtable、HashMap、TreeMap等类型的参数。

5、谨慎使用函数对象

posted @ 2010-10-20 02:43  irischan  阅读(372)  评论(0编辑  收藏  举报