Effective Java 读书笔记

1.考虑用静态工厂方法代替构造器

静态工厂方法与构造器不同的第三大优势在于,他们可以返回原返回类型的任何子类型

2.遇到多个构造器参数时要考虑用构建器 3.用私有构造器或者枚举类型强化Singleton属性

单元素的枚举类型已经成为实现Singleton的最佳方法

4.通过私有构造器强化不可实例化的能力 5.避免创建不必要的对象 6.消除过期的对象引用(这里可以给个例子) 7.避免使用终结方法finalizer 8.覆盖equals时请遵守通用约定

i. 自反性,对于任何非null的引用值x, x.equals(x)必须返回true

ii. 对称性,对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时x.equals(y)必须返回true

iii. 传递性,对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)也必须返回true

iv. 一致性,对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一直地返回true,或者一致地返回false.

v. 对于任何非null的引用值x,x.equals(null)必须返回false(我觉得可以叫绝对性)

9.覆盖equals时总要覆盖hashCode

i. 在每个覆盖equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作。

10.始终要覆盖toString 11.谨慎的覆盖clone

i. 拷贝对象往往会导致创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构。这个过程没有调用构造器。

ii. 永远不要让客户去做任何类库能够替客户完成的事情

iii. clone架构与引用可变对象的final域的正常用法是不兼容的

12.考虑实现Comparable接口

i. 依赖于比较关系的类包括有序集合类TreeSet和TreeMap,以及工具类Collections和Arrays,它们内部包含搜索和排序算法。

13.使类和成员的可访问性最小化 14.在共有类中使用访问方法而非共有域 15.使可变性最小化 16.复合优先于继承

i. 对于两个类A和B,只有当两者之间确实存在“is-a”关系的时候,类B才应该扩展类A。

17.要么为继承而设计,并提供文档说明,要么就禁止继承

i. 构造器决不能调用可被覆盖的方法

ii. 无论是clone还是readObject,都不可以调用可覆盖的方法

18.接口优于抽象类

i. 现有的类可以很容易被更新,以实现新的接口

ii. 接口是定义mixin(混合型)的理想选择

iii. 接口允许我们构造非层次结构的类型框架

iv. 抽象类的演变比接口的演变要容易的多

v. 如果你导出了一个重要的接口,就应该坚决考虑同时提供骨架实现类

19.接口只用于定义类型

i. 接口应该只被用来定义类型,它们不应该被用来导出常量

20.类层次优于标签类 21.用函数对象表示策略

i. 函数指针的主要用途就是实现策略(Strategy)模式。为了在Java中实现这种模式,要声明一个接口来表示该策略,并且为每个具体策略声明一个 实现了该接口的类。当一个具体策略只能使用一次时,通常使用匿名类来声明和实例化这个具体策略类。当一个具体策略是设计用来重复使用的时候,它的类通常就要被实现为私有的静态成员类,并通过公有的静态final域被导出,其类型为该策略接口。

22.有限考虑静态成员类

i. 如果声明成员类不要求访问外围实例,就要始终把static修饰符放在它的声明中

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

23.请不要在新代码中使用原生态类型

// 泛型使用instanceof操作符的首选方法

if(o instanceof Set) {
    Set<?> s = (Set<?>) o;
}

// set<Object>是个参数化类型,表示可以包含任何对象类型的一个集合
//
// set<?>是一个通配符类型,表示只能包含某种未知对象类型的一个集合
// 
// set是个原生态类型,脱离了泛型系统
//
// 前两种是安全的,最后一种是不安全的
24.消除非受检警告

i. 非受检警告很重要,不要忽略它们。每一条警告都表示可能在运行时抛出ClassCastException异常。要尽最大的努力消除这些警告。如果无法消除警告,同时可以证明引起警告的代码是类型安全的,就可以在尽可能小的范围中,用@SuppressWarnings(“unchecked”)注释禁止该警告。要用注释把禁止该警告的原因纪录下来

25.列表优先于数组

i. 数组和泛型有着非常不同的类型规则。数组是协变且可以具体化的;泛型是不可变的且可以被擦除的。因此数组提供了运行时的类型安全,但是没有编译时的类型安全,反之,对于泛型也一样。一般来说,数组和泛型不能很好的混合使用。如果你发现自己将它们混合起来使用,并且得到了编译时错误或者警告,你的第一反应就应该是用列表代替数组

ii. 不可创建不可具体化的(non-reifiable)类型的数组

26.优先考虑泛型 27.优先考虑泛型方法

i. 静态工具方法尤其适合于泛型化

28.利用有限制通配符来提升API的灵活性

i. 对于任意两个截然不同的类型Type1和Tpye2而言,List既不是List的子类型,也不是它的超类型。

ii. 为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数的输入参数上使用通配符类型 PECS表示producer-extends, consumer-super

iii. 不要用通配符类型作为返回类型。除了为用户提供额外的灵活性之外,它还会强制用户在客户端代码中使用通配符类型。

iv. Comparable始终是消费者,因此使用时始终是Comparable<? super T > 优先于Comparable<T>。对于Comparator也一样,因此使用时始终是Comparator<? super T>优先于Comparator<T>。

v. 如果类型参数只在方法声明中出现一次,就可以用通配符取代它。

vi . 不能将null之外的任何值放到List<?>中

29.优先考虑类型安全的异构容器 30.用enum代替int常量

i. 与int常量相比,枚举有个小小的性能缺点,即装载和初始化枚举时会有空间和时间的成本

ii. 枚举中的switch语句适合于给外部的枚举类型增加特定于常量的行为

iii. 如果多个枚举常量同时共享相同的行为,则考虑策略枚举

31.用实例域代替序数

i. 永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中

32.用EnumSet代替位域

i. 正式因为枚举类型要用在集合中(Set)中,所以没有理由用位域表示它

33.用EnumMap代替序数索引

i. 最好不要用序数来索引数组,而要使用EnumMap

34.用接口模拟可伸缩性

i. 虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟

35.注解优先于命名模式

i. 既然有了注解,就完全没有理由再使用命名模式了

ii. 所有程序员都应该使用Java平台所提供的预定义的注解类型

36.坚持使用Override注解

i. 在你想要覆盖超类声明的每个方法声明中使用Override注解

37.用标记接口定义类型

i. 如果你发现自己在编写的是目标为ElementType.TYPE的标记注解类型,就要花点时间考虑清楚,它是否真的应该为注解类型,想想标记接口是否会更加合适呢。

38.检查参数的有效性 39.必要时进行保护性拷贝

i. 假设类的客户端会尽其所能来破坏这个类的约束条件,因此你必须保护性地设计程序

ii. 对于构造器的每个可变参数进行保护性拷贝是必要的

iii. 保护性拷贝是在检查参数有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象。

iv. 对于参数类型可以被不可信任方子类化的参数,请不要使用clone方法进行保护性拷贝。

40.谨慎设计方法签名

i. 谨慎地选择方法的名称

ii. 不要过于追求提供便利的方法

iii. 避免过长的参数列表

iv. 对于参数化类型,要优先使用接口而不是类

v. 对于boolean参数,要优先使用两个元素的枚举类型

41.慎用重载

i. 要调用哪个重载(overloading)方法是在编译时决定的

ii. 对于重载方法(overloaded method)的选择是静态的,而对于被覆盖的方法(overridden method)的选择是动态的。

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

42.慎用可变参数

i. 不必改造具有final数组参数的每个方法;只当确实是在数量不定的值上执行调用时才使用可变参数

ii. 在重视性能的情况下,使用可变参数机制要特别小心。可变参数方法的每次调用都会导致进行一次数组分配和初始化

43.返回零长度的数组或者集合,而不是null

i. 返回类型为数组或集合的方法没理由返回null,而是返回一个零长度的数组或者集合

44.为所有导出的API元素编写文档注释

i. 为了正确的编写API文档,必须在每个被导出类,接口,构造器,方法和域声明之前增加一个文档注释

ii. 方法的文档注释应该简洁地描述出它和客户端之间的约定

iii. 再也没有必要在文档注释中使用HTML或者标签了:Javadoc {@code}标签更好,因为它避免了转义HTML元字符

iv. 同一个类或者接口中的两个成员或者构造器,不应该具有同样的概要描述

v. 为泛型或者方法编写文档时,确保要在文档中说明所有的类型参数

vi. 为枚举类型编写文档时,要确保在文档中说明常量,以及类型,还有任何共有的方法

vii. 为注解类型编写文档时,要确保在文档中说明所有成员,以及类型本身

45.将局部变量的作用域最小化

i. 要使局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方声明

ii. 几乎每个局部变量的声明都应该包含一个初始化表达式

46.for-each循环优先于传统的for循环 47.了解和使用类库

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

ii. 在每个重要的发行版本中,都会有许多新的特性被加入到类库中,所以与这些新特性保持同步是值得的。

48.如果需要精确的答案,请避免使用float和double
i. float和double类型尤其不适合用于货币计算

ii. 使用BigDecimal, int或者long进行货币计算,BigDecimal的缺点是慢,如果性能非常关键,并且你又不介意自己记录十进制小数点,而且所涉及的数值又不太大,就可以使用int或者long。如果数值范围没有超过9位十进制数字,就可以使用int,如果不超过18位数字,就可以使用long.如果数值可能超过18位数字,就必须使用BigDecimal。

49.基本类型优先于装箱基本类型

i. 对表达式first < second 执行计算会导致被first和second引用的Integer实例被自动拆箱(auto-unboxed);也就是说,它提取了它们的基本类型值。

ii. 对装箱基本类型运用==操作符几乎总是错误的。

iii. 当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱,这种情况无一例外。如果null对象引用被自动拆箱,就会得到一个NullPointerException异常。

iv. 当可以选择的时候,基本类型要优先于装箱基本类型。基本类型更加简单,也更加快速。当程序装箱了基本类型值时,会导致高开销和不必要的对象创建。

v. 自动装箱减少了使用装箱基本类型的繁琐性,但是并没有减少它的风险

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

i. 字符串不适合代替其他的值类型

ii. 字符串不适合代替枚举类型

iii. 字符串不适合代替聚集类型

iv. 字符串不适合代替能力表

v. 如果可以使用更加合适的数据类型,或者可以编写更加适当的数据类型,就应该避免用字符串来表示对象。若使用不当,字符串会比其他的类型更加笨拙,更不灵活,速度更慢,也更容易出错。经常被错误地用字符串来代替的类型包括基本数据类型,枚举类型和聚集类型。

51.当心字符串连接的性能

i. 为连接n个字符串而重复地使用字符串连接操作符,需要n的平方级的时间

ii. 为了获得可以接受的性能,请使用StringBuilder代替String

52.通过接口引用对象

i. 如果有合适的接口类型存在,那么对于参数,返回值,变量和域来说,就都应该使用接口类型进行声明

ii. 如果你养成了用接口作为类型的习惯,你的程序将会更加灵活

iii. 如果没有合适的接口存在,完全可以用类而不是接口来引用对象

53.接口优先于反射机制

i. 缺点

a. 丧失了编译时类型检查的好处
b. 执行反射访问所需要的代码非常笨拙和冗长
c. 性能损失

ii. 通常,普通应用程序在运行时不应该以反射方式访问对象

iii. 如果只是以非常有限的形式使用反射机制,虽然也要付出少许 代价,但是可以获得许多好处

54.谨慎地使用本地方法

i. 使用本地方法来提高性能的做法不值得提倡

55.谨慎地进行优化

i. 要努力编写好的程序而不是快的程序

ii. 努力避免那些限制性能的设计决策

iii. 要考虑API设计决策的性能后果

iv. 为获得好的性能而对API进行包装,这是一种非常不好的想法

v. 在每次试图做优化之前和之后,要对性能进行测量

56.遵守普通接受的命名惯例 57.只针对异常的情况才使用异常

i. 顾名思义,异常应该只用于异常的情况下;它们永远不应该用于正常的控制流

58.对可恢复的情况使用受检异常,对编程错误使用运行时异常

i. 如果期望调用者能够适当地恢复,对于这种情况就应该使用受检的异常

ii. 用运行时异常来表明编程错误

iii. 你实现的所有未受检的抛出结构都应该是RuntimeException的子类

59.避免不必要地使用受检的异常 60.优先使用标准的异常 61.抛出与抽象相对应的异常

i. 更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常

ii. 尽管异常转译与不加选择地从低层传递异常的做法相比有所改进,但是它也不能被滥用

62.每个方法抛出的异常都要有文档

i. 始终要单独的声明受检的异常,并且利用Javadoc的@throws标记,准确地记录下抛出每个异常的条件

ii. 使用Javadoc的@throws标签记录下一个方法可能抛出的每个未受检异常,但是不要使用throws关键字将未受检的异常包含在方法的声明中

iii. 如果一个类中的许多方法出于同样的原因而抛出同一个异常,在该类的文档注释中对这个异常建立文档,这是可以接受的

63.在细节消息中包含能捕获失败的信息

i. 为了捕获失败,异常的细节信息应该包含所有“对该异常有贡献”的参数和域的值

64.努力使失败保持原子性

i. 一般而言,失败的方法调用应该使对象保持在被调用之前的状态

65.不要忽略异常

i. 空的catch块会使异常达不到应有的目的

ii. 空的catch块也应该包含一条说明,解释为什么可以忽略这个异常

66.同步访问共享的可变数据

i. 为了在线程之间进行可靠的通信,也为了互斥访问,同步是必要的

ii. 如果读和写操作没有都被同步,同步就不会起作用

iii. volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近刚刚被写入的值

iv. 将可变数据限制在单个线程中

v. 当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步

67.避免过度同步

i. 为了避免活性失败和安全性失败,在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制

ii. 通常,你应该在同步区域内做尽可能少的工作

68.executor和task优先于线程 69.并发工具优先于wait和notify

iii. 既然正确地使用wait和notify比较困难,就应该用更高级的并发工具来代替

iv. 并发集合中不可能排除并发活动,将它锁定没有是作用

v. 优先使用ConcurrentHashMap,而不是使用Collections.synchronizedMap或者Hashtable

vi. 对于间歇式的定时,始终应该优先使用System.nanoTime,而不是System.currentTimeMills。System.nanoTime更加准确也更加精确,它不受系统的实时时钟的调整所影响

vii. 始终应该使用wait循环模式来调用wait方法;永远不要再循环之外调用wait方法

viii. 没有理由在新代码中使用wait和notify,即使有,也是极少的

70.线程安全性的文档化

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

71.慎用延迟初始化

i. 在大多数情况下,正常的初始化要优先于延迟初始化

ii. 如果利用延迟优化来破坏初始化的循环,就要使用同步访问方法

iii. 如果出于性能的考虑而需要对静态域使用延迟初始化,就使用lazy initialization holder class模式

iv. 如果出于性能的考虑而需要对实例域使用延迟初始化,就使用双重检查模式

72.不要依赖于线程调度器

i. 任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的

ii. 如果线程没有在做有意义的工作,就不应该运行

iii. 线程优先级是Java平台上最不可移植的特征了

iv. Thread.yield的唯一用途是在测试期间人为的增加程序的并发性

v. 不要让应用程序的正确性依赖于线程调度器。否则,结果得到的应用程序既不健壮,也不具有可移植性。作为推论,不要依赖Thread.yield或者线程优先级。这些设施仅仅对调度器做些暗示。线程优先级可以用来提高一个已经能够正常工作的程序的服务质量,但永远不应该用来“修正”一个原本不能工作的程序

73.避免使用线程组 74.谨慎的实现Serializable接口

i. 实现Serializable接口而付出的最大代价是,一旦一个类被发布,就打打降低了“改变这个类的实现”的灵活性

ii. 实现Serializable的第二个代价是,它增加了出现Bug和安全漏洞的可能性

iii. 实现Serializable的第三个代价是,随着类发行新的版本,相关的测试负担也增加了

iv. 为了继承而设计的类应该尽可能少的去实现Serializable接口,用户的接口也应该尽可能少的去实现Serializable接口

v. 对于为继承而设计的不可序列化的类,你应该考虑提供一个无参构造器

vi. 内部类不应该实现Serializable

vii. 内部类的默认序列化形式是定义不清楚的

75.考虑使用自定义的序列化形式

i. 如果没有先认真考虑默认的序列化形式是否合适,则不要贸然接受

ii. 如果一个对象的物理表示法等同于它的逻辑内容,可能就适合于使用默认的序列化形式

iii. 即使你确定了默认的序列化形式是合适的,通常还必须提供一个readObject方法以保证约束关系和安全性

iv. transient修饰符表明这个实例域将从一个类的默认序列化形式中省略掉

v. 如果所有实例域都是瞬时的,从技术角度而言,不调用
defaultWriteObject和defaultReadObject也是允许的,但是不推荐这样做

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

vii. 如果使用默认的序列化形式,并且把一个或者多个域标记为transient,当一个实例被反序列化的时候,这些域将被初始化为它们的默认值

viii. 如果在读取整个对象状态的任何其他方法上强制任何同步,则也必须在对象序列化上强制这种同步

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

76.保护性的编写readObject方法

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

ii. 不用使用writeUnshared和readUnshared方法

iii. 对于对象引用域必须保持为私有的类,要保护性地拷贝这些域中的每个对象。不可变类的可变组件就属于这一类别

iv. 对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常。这些检查动作应该跟在所有的保护性拷贝之后

v. 如果整个对象图在被反序列化之后必须进行验证,就应该使用ObjectInputValidation接口

vi. 无论是直接方式还是间接方式,都不要调用类中任何可被覆盖的方法

77.对于实例控制,枚举类型优先于readResolve

i. 如果依赖readResolve进行实例控制,带有对象引用类型的所有实例域则都必须声明为transient的

ii. readResolve的可访问性很重要

78.考虑用序列化代理代替序列化实例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值