2、Effective Java 57条

孤街浪徒 提交于 2020-04-07 10:07:56

 第一条:考虑用静态工厂方法代替构造函数

1、优点:

1)、静态工厂方法的一个好处是,与构造函数不同,静态工厂方法具有名字。

2)、静态工厂方法的第二个好处是,与构造函数不同,它们每次被调用的时候,不要求非得创建一个新的对象;

3)、与构造函数不同,它们可以返回一个原返回类型的子类型对象;

2、缺点:

1)、类如果不含公有的或者受保护的构造函数,就不能被子类化;

2)、它们与其他的静态方法没有任何区别;

 

第二条:使用私有构造函数强化singleton属性

解释:singleton是指这样的类,他只能实例化一次,通常用来代替本质上具有唯一性的系统组件;

1)、提供公有静态成员是一个final域:

Public class Elvis{

Public static final Elvis INSTANCE = new Elvis();

 

Private Elvis(){

 ……

}

……

}

 

(2)、提供一个公有的静态方法,而不是公有的静态final域:

Public class Elvis{

Private static final Elvis INSTANCE = new Elvis();

 

Private Elvis(){

...

}

 

Public static Elvis getInstance(){

...

}

...

}

 

第三条:通过私有构造函数强化不可实例化的能力

1)、企图通过将一个类做成抽象类来强制该类不可被实例化,这是行不通的;

2)、只要让这个类包含单个显式的私有构造函数,则它就不可被实例化了;

 

第四条:避免创建重复的对象

第五条:消除过期的对象引用

1)、“清空对象引用”这样的操作应该是一种例外,而不是一种规范行为;

第六条:避免使用终结函数(finalizer

1)、时间关键的任务不应该有终结函数来执行;

2)、我们不应该依赖一个终结函数来更新关键性的永久状态;

3)、显示的终止方法通常与try-finally结构结合起来使用,以确保及时终止;

4)、终结函数用途:

      a.当一个对象的所有者忘记了调用前面段落中建议的显示终止方法的情况下,终结函数可以充当“安全网”。

     b.与对象的本地对等体有关。

5)、“终结函数链”并不会被自动执行。

 

第七条:在改写equals的时候请遵守通用约定

1)、一个类的每个实例本质上都是唯一的。

2)、不关心一个类是否提供了“逻辑相等”的测试功能。

3)、超类已经改写了equals,从超类继承过来的行为对于子类也是合适的。

4)、一个类是私有的,或者是包级私有的,并且可以确定他的equals方法永远也不会被调用。

-----高质量equals方法“处方”

1)、使用==操作符检查“实参是否为指向对象的一个引用”;

2)、使用instanceof操作符检查“实参是否为正确的类型”;

3)、把实参转换到正确的类型;

4)、对于该类中每一个“关键(significant)”域,检查实参中的域与当前对象中对应的域值是否匹配。

5)、当你编写完成了equals方法之后,应该问自己三个问题:他是否是对称的、传递的、一致的?

 

-----告诫-----------

(1)、当你改写equals的时候,总是要改写hashCode;

(2)、不要企图让equals方法过于聪明;

3)、不要使equals方法依赖于不可靠的资源;

4)、不要将equals声明中的Object对象替换为其他的类型;

 

第八条:改写equals时总是要改写hashCode

1)、因没有改写hashCode而违反的关键约定是第二条:相等的对象必须具有相等的散列码;

2)、不要试图从散列码计算中排除掉一个对象的关键部分以提高性能。

 

9条:总是要改写toString方法

1)、提供一个好的toString方法实现,可以使一个类用起来更加愉快;

2)、在实际应用中,toString方法应该返回对象中包含的所有令人感兴趣的信息;

3)、不管你是否决定指定格式,都应该在文档中明确地表明你的意图;

4)、为toString返回值中包含的所有信息,提供一种编程访问途径,这总是一个好的做法;

 

10条:谨慎地改写clone

(1)、如果你改写了一个非final类的clone方法,则应该返回一个通过调用super.clone而得到的对象。

2)、实际上,对于实现了Cloneable的类,我们总是期望它也提供了一个功能适当的公有clone方法。

3)、实际上,clone方法是另一个构造函数;你必须确保它不会伤害到原始的对象,并且正确地建立起被克隆对象中的约束关系。

4)、clone结构与指向可变对象的final域的正常用法是不兼容的。

5)、最好的做法是,提供某些其他的途径来代替对象拷贝,或者干脆不提供这样的能力。

6)、另一个实现对象拷贝的好办法是提供一个拷贝构造函数。所谓拷贝构造函数,它也是一个构造函数,其唯一的参数的类型是包含该构造函数的类。(可以提供一个静态工厂方法来产生)

 

11条:考虑实现Comparable接口

12条:使类和成员的可访问能力最小化

1)、经验表明,你应该尽可能地使每一个类或成员不被外界访问;

13:支持非可变性

——为了使一个类成为非可变类,要遵循下面五条规则:

1)、不要提供任何会修改对象的方法;

2)、保证没有可被子类改写的方法;

3)、使所有的域都是final的;

4)、使所有的域都成为私有的;

5)、保证对于任何可变组件的互斥访问;

——优点

1)、非可变对象比较简单;

2)、非可变对象本质上是线程安全的,它们不要求同步;

3)、非可变对象可以被自由的共享;

4)、你不仅可以共享非可变对象,甚至也可以共享它们的内部信息;

5)、非可变对象为其他对象——无论是可变的还是非可变的——提供了大量的构件;

6)、非可变类真正唯一的缺点是,对于每一个不同的值都要求一个单独的对象;

7)、坚决不要为每个get方法编写一个相应的set方法,除非有很好的理由要让你个类成为可变类,否则就应该是非可变的。

8)、如果一个类不能被做成非可变类,那么你仍然应该尽可能的限制他的可变性;

9)、构造函数应该创建完全初始化的对象,所有的约束关系应该在这时候建立起来;

14条:复合优先于继承

1)、与方法调用不同的是,继承打破了封装性;

15条:要么专门为继承而设计,并给出文档说明,要么禁制继承

1)、该类的文档必须精确地描述了改写每一个方法所带来的影响;

2)、一个类必须通过某种形式提供适当的钩子,以便能够进入到它的内部工作流程中,这样的形式可以是精心选择的受保护(protected)的方法;

3)、构造函数一定不能调用可被改写的方法;

4)、无论是clone还是readObject,都不能调用一个可改写的方法,不管是直接的方式,还是间接的方式;

5)、如果你决定为继承设计类,你必须使readResolve或者writeReplace成为受保护的方法,而不是私有方法;

6)、为了继承一个类,要求对这个类有一些实质性的限制;

7)、对于那些并非为了安全地进行子类化而设计和编写文档的类,禁制子类化;

16条:接口由于抽象类

1)、已有的类可以很容易被更新,以实现新的接口;

2)、接口是定义mixin(混合类型)的理想选择,一个mixin是只这样的类型:一个类除了实现“基本类型”之外,还可以实现这个mixin类型,以表明它提供了某些可供选择的行为。

3)、接口使得我们可以构造出非层次结构的类型框架;

4)、接口使得安全地增强一个类的功能成为可能;

5)、你可以把接口和抽象类的优点结合起来,对于你期望导出的每一个重要接口,都提供一个抽象的骨架实现(skeletal implementation)类。骨架实现被成为Abstract Interface

6)、使用抽象类来定义允许多个实现的类型,比使用接口有一个明显的优势:抽象类的演化比接口的演化要容易得多;

17条:接口只是被用于定义类型

1)、常量接口模式是对接口的不良使用

2)、总之,接口应该只是被用来定义类型的,他们不应该被用来导出常量;

18条:优先考虑静态成员类

1)、如果你声明的成员类不要求访问外围实例,那么请记住把static修饰符方法到成员类的声明中;

19条:用类代替结构

20条:用类层次来代替联合

1)、类层次提供了类型安全性;

2)、代码非常简洁明了;

3)、很容易扩展,即使是多方在独立地工作;

4)、它可以反映出这些类型之间本质上的层次关系;

21条:用类来代替enum结构

1)、 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");

     }

 

2)、

public abstract class Operation {

    private final String name;

    Operation(String name){

        this.name = name;

    }

    public String toString(){

        return name;

    }   

    abstract double eval(double x,double y);   

    public static final Operation PLUS = new Operation("+"){

        double eval(double x,double y){ return x+y;}

    };

}

----------

public class TestOperation {

    public static void main(String[] args) {       

        System.out.println(Operation.PLUS.eval(2, 3));

    }

}

 

22条:用类和接口来代替函数指针

23条:检查参数的有效性

24条:需要是使用保护性拷贝

1)、假设类的客户会尽一切手段来破坏这个类的约束条件,在这样的前提下,你必须保护性的设计程序;

2)、对于构造函数的每个可变参数进行保护性拷贝(defensive copy)是必要的;

Public  Period(Date start,Date end){

This.start = new Date(start.getTime());

This.end = new Date(end.getTime());

 

If(this.start.compareTo(This.end) > 0 )

Throw new IllegalArgumentException(start + " after " + end);

}

3)、保护性拷贝动作是检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是原始对象;

4)、对于”参数类型可以被不可信方子类化“的情形,请不要使用clone方法进行参数的保护性拷贝;

25条:谨慎设计方法的原型

1)、谨慎选择方法的名字;

2)、不要过于追求提供便利的方法;

3)、避免长长的参数列表;

4)、对于参数类型,优先使用接口而不是类。

5)、谨慎地使用函数对象;

26条:谨慎地使用重载

1)、对于重载方法选择的是静态的,而对于被改写的方法的选择是动态的;

2)、一个安全而保守的策略是,永远不要导出两个具有相同参数数目的重载方法;

27条:返回零长度的数组而不是null

1)、没有理由从一个取数组值(array-valued)的方法中返回null,而不是返回一个零长度的数组;

28条:为所有导出的API元素编写文档注释

1)、为了正确地编写API文档,你必须在每一个被导出的类、接口、构造函数、方法和域声明之前增加一个文档注释;

2)、每一个方法的文档注释应该简洁地描述出他和客户之间的约定;

29条:将局部变量的作用域最小化

1)、使一个局部变量的作用域最小化,最有利的技术是在第一次使用它的地方声明;

2)、几乎每一个局部变量的声明都应该包含一个初始化表达式;

3)、使方法小而其中;

30条:了解和使用库

1)、通过使用标准库,你可以充分利用这些编写标准库的专家的知识,以及在你之前其他人的使用经验;

2)、你不必浪费时间为那些与你的工作关系不大的问题提供特别的解决方法;

3)、他们的性能会不断提高,而无需你做任何努力。

4)、你可以使自己的代码融入主流;

5)、在每一个主要的发行版本中,都会有许多新的特性被加入到库中,所以与这些苦保持同步是值得的;

31:如果要求精确的答案,请避免使用floatdouble

32条:如果其他类型更合适,则尽量避免使用字符串

1)、字符串并不适合代替其他的值类型;

2)、字符串并不适合代替枚举类型;

3)、字符串不适合代替聚集类型;

4)、字符串也不适合代替能力表;

33:了解字符串连接的性能

1)、为了获得可接受的性能,请使用StringBuffer代替String

34条:通过接口引用对象

1)、如果你养成了使用接口作为类型的习惯,那么你的程序将会更加灵活;

2)、如果没有合适的接口存在的话,那么,用类而不是接口来引用一个对象,是完全合适的;

35条:接口优先于映像机制

——使用映像类的代价

1)、你损失了编译时类型检查的好处;

2)、要求执行映像访问的代码非常笨拙和冗长;

3)、性能损失;

——————

1)、映像功能是在设计时刻被使用的:通常,普通应用在运行时刻不应该以映像方式访问对象;

2)、如果只是在很多有限的情况下使用映像机制,那么虽然也会付出少许代价,但你可以获得许多好处;

36条:谨慎地使用本地方法

37:谨慎的进行优化

1)、优化更容易带来伤害,而不是好处,特别是不成熟的优化;

2)、努力编写好的程序,而不是快的程序;

3)、努力避免那些限制性能的设计决定;

4)、考虑你的API设计决定的性能后果;

5)、好的API设计也伴随着好的性能,为获得好的性能而对API进行修改,这是一个非常不好的想法;

6)、在每次试图做优化之前和之后,请对性能进行测量;

38条:遵守普遍接受的命名管理

39条:只针对不正常的条件才使用异常

1)、顾名思义,异常只应该被用于不正常的条件,他们永远不应该被用于正常的控制流;

2)、一个设计良好的API不应该强迫它的客户为了正常的控制流而使用异常;

40条:对于可恢复的条件使用被检查的异常,对于程序错误使用运行时异常

1)、如果期望调用者能够恢复,那么,对于这样的条件应该使用被检查异常;

2)、你所实现的所有未被检查的抛出结构都应该是RuntimeException的子类;

41条:避免不必要地使用被检查的异常

42条:尽量使用标准异常

43条:抛出的异常要适合于相应的抽象

1)、高层的实现应该捕获低层的异常,同时抛出一个可以按照高层抽象进行解释的异常;

2)、尽管异常转译比不加选择的传递低层异常的做法有所改进,但是它也不能被滥用;

44条:每个方法抛出的异常都要有文档

1)、总是要单独地声明被检查的异常,并且利用Javadoc@throws 标记,准确地记录下每个异常被抛出的条件;

2)、使用Javadoc@throws 标签记录下一个方法可能会抛出的每一个未被检查的异常,但是不要使用throws关键字将未被检查的异常包含在方法的声明中;

3)、如果一个类中的许多方法出于同样的原因而抛出同一个异常,那么在该类的文档注释中对这个异常做文档,而不是为每个方法单独做文档,这是可以接受的;

45条:在细节消息中包含失败-捕获信息

1)、为了捕获失败,一个异常的字符串表示应该包含所有“对该异常有贡献”的参数和域的值;

46条:努力使失败保持原子性

1)、一般而言,一个失败的方法调用应该使对象保持“他在被调用之前的状态”;

2)、作为方法规范的一部分,任何一个异常都不应该改变对象调用该方法之前的状态。如果这条规则被违反,则API文档应该清楚地致命对象将会处于什么样的状态,不幸的是,大量现有的API文档都未能做到这一点;

47条:不要忽略异常

48条:对共享可变数据的同步访问

1)、synchronized关键字可以保证在同一时刻,只有一个线程在执行一条语句或一段代码块;

2)、你可能听说过,为了提高性能,在读或写原子数据的时候,你应该避免使用同步,这个建议是非常危险而错误的;

3)、为了在线程之间可靠地通信,以及为了互斥访问,同步是需要的;

4)、一般情况下,双重检查模式并不能正确地工作;

5)、简而言之,无论何时当多个线程共享可变数据的时候,每个读或者写数据的线程必须获得一把锁;

6)、在某些特定的条件下,使用volatile修饰符可以提供另一种不同于普通同步机制的选择,但这是一项高级的技术;

49条:避免过多的同步

1)、为了避免死锁的危险,在一个被同步的方法或者代码块中,永远不要放弃对客户的控制;

2)、通常,在同步区域内你应该做尽可能少的工作;

3)、为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法,尽量限制同步区域内部的工作量;

50条:永远不要在循环的外面调用wait

1)、一定要在同步区域内调用object.wait方法,总是使用wait

51条:不要依赖于线程调度器

1)、任何依赖于线程调度器而达到正确性或性能要求的程序,很有可能是不可移植的;

2)、线程是Java平台上最不可移植的特征了;

3)、对于大多数程序员来说,Thread.yield的唯一用途是在测试期间人为的增加一个程序的并发性;

4)、推论:不要让应用程序的正确性依赖于线程调度器;

52条:线程安全性的文档化

1)、不能通过文档中synchronized来确认一个线程的安全性,在一个方法的声明中出现synchronized修饰符,这是一个实现细节,并不是导出API的一部分;

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

53条:避免使用线程组

1)、线程组基本上已经过时了;

54条:谨慎地实现Serializable

1)、因为实现Serializable而付出的最大代价是,一旦一个类被发布,则“改变这个类的实现”的灵活性将大大降低;

2)、实现Serializable的第二个代价是,他增加了错误(bug)和安全漏洞的可能性;

3)、实现Serializable的第三个代价是,随着一个类的新版本的发行,相关的测试负担增加了;

4)、实现Serializable接口不是一个很轻松就可以做出的决定;

5)、为了继承而设计的类应该很少实现Serializable,接口也应该很少会扩展它;

6)、对于为继承而设计的不可序列化的类,你应该考虑提供一个无参数的构造函数;

55条:考虑使用自定义的序列化形式

1)、若没有认真考虑默认序列化形式是否合适,则不要接受这种形式;

2)、如果一个对象的物理表示等同于它的逻辑内容,则默认的序列化形式可能是合适的;

3)、如果你确定了默认序列化形式是合适的,通常你仍然要提供一个readObject方法以保证约束关系和安全性;

4)、当一个对象的物理表示与他的逻辑数据内容有实质性的区别时,使用默认序列化形式有4个缺点:

a.它使这个类的导出API永久地约束在该类的内部表示上;

b.他要消耗过多的空间;

c.他要消耗过多的时间;

d.它会引起栈溢出;

5)、如果所有的实例都是transient的,那么,从技术角度而言,省去调用defaultWriteObjectdefaultReadObject也是允许的,但是不推荐这样做;

6)、在决定讲一个域做成非transient之前,请一定要确信它的值将是该对象逻辑状态的一部分;

7)、不管你选择了哪种序列化形式,你都要为自己编写的每个可序列化的类声明一个显示的序列版本UID

56条:保护性地缩写readObject方法

1)、当一个对象反序列化的时候,对于客户不应该拥有的对象引用,如果哪个域包含了这样的对象引用,则必须要做保护性拷贝,这是非常重要的;

57条:必要时提供一个readResolve方法

1)、readResolve方法不仅仅对于singleton对象是必要的,而且对于所有其他的实例受控的类也是必须的;

2)、readResolve方法的第二个用法是,就像在第56条中建议的那样,作为保护性的readObject方法的一种保守的替代选择;

3)、尽管保护性readResolve模式并没有被广泛使用,但是它值得认真考虑;

4)、readResolve方法的可访问性(accessibility)是非常重要的;

 

 

 

 

 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!