第2章 创建和销毁对象
第1条:考虑用静态工厂方法代替构造方法
静态工厂方法与构造方法的不同
优点:
- 静态工厂方法有名称。当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造方法,并且慎重选择名称以便突出区别。
- 不用每次调用它们的时候都创建一个新对象。静态工厂方法能够为重复的调用返回相同的对象,有助于类总能严格控制在某个时刻那些实例不该存在。
- 可以返回原返回类型的任何子类型的对象。有更大的灵活性;如APi可以返回对象,又同时不会使对象的类变成公有的,适用于基于接口的框架。公有的静态工厂方法返回的对象的类不仅可以是非公有的,而且该类还可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。
- 在创建参数化类型实例的时候,代码更简洁。
缺点:
- 类如果不含有公有的或者受保护的构造器,就不能被子类化。
- 他们与其他的静态方法实际上没有任何区别。
第2条:遇到多个构造器参数时要考虑用构造器
与构造器相比,builder可以有多个可变的参数。builder模式十分灵活,可以利用单个builder构造多个对象。总之,如果类的构造器和静态工厂中具有多个参数,builder模式就是不错的选择,特别是当大多数参数是可选的时候。
第3条:用私有构造器或者枚举类型强化singleton属性
第4条:用私有构造器强化不可实例化的能力
第5条:避免创建不必要的对象
一般来说,最好能重用对象而不是在每次需要的时候就创建一个相同功能的对象。
第6条:消除过期的对象引用
- 清空对象引用应该是一种例外,而不是一种规范行为,消除过期引用最好的方法是让包含该引用的变量结束其生命周期。
- stack类自己管理内存,只要是类自己管理内存,程序猿就应该警惕内存泄漏问题。
- 内存泄漏的另一种常见来源是缓存。只要在缓存之外存在对某个项的键的引用,该项就有意义,那么就可以用weakhashmap代表缓存。
- 内存泄漏的第三个常见来源是监听器和其他回调。确保回调立即被当做垃圾回收的最佳方法是只保存它们的弱引用,例如,只将它们保持成WeakHashMap中的键。
第7条:避免使用终结方法
- 终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的。
- java语言规范不仅不保证终结方法会被及时执行,而且根本不保证它们会被执行。
- 使用终结方法有一个非常严重的(severe)性能损失
- 显示的终结方法通常与try-finally结构结合起来使用,以确保及时终止。(显示终止方法的典型例子是InputStream,OutputStream和java.sql.Connection上的close方法,java.util.Timer上的cancel方法等)
- 总之,除非作为安全网(当对象的所有者忘记调用前面段落中建议的显示终止方法时,终结方法可以充当安全网),或者是为了终止非关键的本地资源,否则请不要使用终结方法。
第3章 对于所有对象都通用的方法
第8条:覆盖equals时请遵守通用约定
- 类的每个实例本质都是唯一的。对于代表活动实体而不是值的类来说确实如此,例如Thread。
- 不关心类是否提供了"逻辑相等"的测试功能。
- 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。
- 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用。
在实现equals方法时,必须遵守的通用约定
- 自反性。对于任何非null的引用值x,x.equals(x)必须返回true。
- 对称性。对于任何非null的引用值x和y,当且仅当y.equals(x)时,x.equals(y)必须返回true。
- 传递性。对于任何非null的引用值x,y和z,如果x.equals(y)返回true,并且y.equals(x)也返回true,那么x.equals(z)也必须返回true。
- 一致性。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致的返回false。
- 非空性。意思是所有的对象都必须不等于null。
实现提高质量equals方法的诀窍
- 使用==操作符检查"参数是否为这个对象的引用"。
- 使用instanceof操作符检查"参数是否为正确的类型"。
- 吧参数转换成正确的类型。
- 对于该类中的每个"关键(significant)"域,检查参数中的域是否与该对象中对应的域相匹配。
- 当编写完成了equals方法之后,应该问自己三个问题:它是是否对称的,传递的,一致的?
- 覆盖equals是总要覆盖hashCode。
- 不要企图让equals方法过于智能。
- 不要将equals声明中的Object对象替换为其他类型。
第9条 覆盖equals是总要覆盖hashCode
- 相等的对象必须具有相等的散列码(hash code)
- 不要试图从散列码计算中出掉一个对象的关键部分来提高性能。
第10条 始终要覆盖toString
- 提供好的toString实现可以使类用起来更加舒适。
- toString方法应该返回对象中包含的所有值得关注的信息。
- 指定toString返回值的格式也有不足之处:如果这个类已经被广泛使用,一旦指定格式,就必须始终如一地坚持这种格式。
- 无论是否决定指定格式,都应该在文档中明确地表明你的意图。如果要指定格式,则应该严格地去做。
- 无论是否指定格式,都为toString返回值中包含的所有信息,提供一种编程式的访问途径。
第11条 谨慎地覆盖clone
第12条 考虑实现Comparable接口
第4章 类和接口
第13条:是类和成员的可访问性最小化
- 区别设计良好的模块和设计不好的模块,最重要的因素在于,这个模块对于外部的其他模块而言,是否隐藏其内部数据和其他实现细节。(信息隐藏/封装)
- 尽可能地使每个类或者成员不被外界访问
- 私有的(private)
- 包级私有的(package-private/default)
- 受保护的(protected)
- 公有的(public)
- 如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类的访问级别。接口中的所有方法都隐含着公有访问级别。
- 实例域决不能是公有的,包含公有可变域的类并不是线程安全的。
- 长度非零的数组总是可变的,所以类具有公有的静态final数组域,或者返回这种域的访问方法,这几乎总是错误的。
- 总之,应该始终尽可能地降低可访问性。除了公有静态final域的特殊情况之外,公有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都是不可变的。
第14条:在公有类中使用访问方法而非公有域
- 如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改变该类的内部表示法的灵活性。
- 如果类是包级私有的,或者是私有的嵌套类,直接暴露它的数据域并没有本质的错误。
- 公有类永远都不应该暴露可变域,虽然还是有问题,但是让公有类暴露不可变的域其危害比较小。但是,有时候会需要用包级私有的或者私有的嵌套类来暴露域,无论这个类是可变还是不可变的。
第15条:使可变性最小化
不可变类知识其实力不能被修改的类。每个实例中包含的所有信息都必须在创建实例的时候提供,并在对象的整个周期内固定不变。
1. 不要提供任何会修改对象状态的方法(mutator)
2. 保证类不会被扩展
3. 使所有的域都是final的
4. 使所有的域都成为私有的
5. 确保对于任何可变组件的互斥访问(如果类具有指向可变的域,则必须确保该类的客户端无法获得指向这些对象的引用)
– 创建并返回新的实例,而不是修改这个实例。大多数重要的不可变类都使用了这个模式,被称为函数的做法。
– 不可变对象本质上是线程安全的,它们不要求同步,可以被自由地共享。不仅可以共享不可变对象,甚至也可以共享它们的内部信息,不需要保护性拷贝。缺点:对于每个不同的值都需要一个单独的对象。
– 总之,坚决不用为每个get方法编写对应的set方法,除非有很好的理由要让类成为可变类,否则应该是不可变的。如果类不能被做成不可变的,仍然应改尽可能地限制它的可变性。
第16条:复合优先于继承
- 于方法调用不同,继承打破了封装性。
- 不用扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例。(复合/转发)这样得到的类将会非常稳固,它不依赖于现有类的实现细节。
- 只有当子类真正是超类的子类型时(存在"is-a"关系),才适合用继承。
第17条:要么为继承设计,并提供文档说明,要么就禁止继承
第18条:接口优于抽象类
- 接口和抽象类的区别:抽象类允许包含某些方法的实现,但接口不允许。为了实现有抽象类定义的类型,类必须成为抽象类的一个子类,Java只允许单继承,所以,抽象类作为类型定义受到了极大的限制。
- 现有的类可以很容易被更新,以实现新的接口。
- 接口的定义mixin(混合类型)的理想选择。(mixin允许任选的功能可被混合到类型的主要功能中)
- 接口允许我们构造非层次结构的类型框架。
- 通过对你导出的每个重要的接口都提供一个抽象的骨架实现(skeletal implementation)类,把接口和抽象类的优点都集合起来。接口的作用仍然是定义类型,但是骨架实现类接管了所有与接口实现相关的工作。
- 按照惯例,骨架实现被称为AbstractInterface,这里的interface是指所实现的接口的名字。例如AbstractCollection,AbstractSet,AbstractList和AbstractMap等。
- 如果设计得当,骨架实现可以使程序员很容易提供他们自己的接口实现。
- 骨架实现为抽象类提供了实现上的帮助,但又不强加"抽象类被用作类型定义时"所特有的严格限制。
- 骨架实现类仍然能够有助于接口的实现。(模拟多重继承:实现这个接口的类可以把对于接口方法的调用,转发到一个内部私有类的实例上,这个内部私有类扩展了骨架的实现)
- 编写骨架实现类相对比较简单,但是有点单调乏味。
- 因为骨架实现类是为继承目的实现的,所以应该遵循第17条中介绍的所有关于设计和文档的指导原则。
- 骨架实现上有个小小的不同,就是简单实现(simple implementation),AbstractMap的SimpleEntry就是个例子。
- 使用抽象类来定义允许多个实现的类型,与使用接口相比有一个明显的优势:抽象类的演变比接口的演变要容易得多。
- 一般来说,要想在公有接口中增加方法,而不破坏实现这个接口的所有现有的类,这是不可能的。因此,设计公有的接口要非常谨慎,接口一旦被公开发行,并且被广泛实现,在想改变这个接口几乎是不可能的。
- 总之,接口通常是定义允许多个实现的类型的最佳途径。这条规则有个例外,即但演变的容易性和灵活性和功能更为重要的时候。
第19条:接口只用于定义类型
当类实现接口时,接口就充当可以引用这个类的实例的类型。常亮接口模式是对接口的不良使用。简而言之,接口应该只被用来定义类型,它们不应该被用来导出常亮。
第20条:类层次优于标签类
- 类标签过于冗长,容易出错,并且效率低下。为了将标签类转变为类层次,首先要为标签类中的每个方法都要定义一个包含抽象方法的抽象类,这每个方法的行为都依赖于标签值。
- 简而言之,标签类很少有适用的时候,当你想要编写一个包含显示标签域的类时,应该考虑一下,这个标签是否可以被取消,这个类是否可以用类层次来代替,当你遇到一个包含标签域的现有类时,就要考虑将它重构到一个层次结构中去。
第21条:用函数对象消失策略
函数指针的主要用途就是实现策略模式。为了在Java中实现这种模式,要声明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类。当一个具体策略只被使用过一次时,通常是使用匿名类来声明和实例化这个具体策略类。当一个具体策略是设计用来重复使用的时候,他的类通常就要被实现为私有的静态成员类,并通过公有的静态final域被导出,其类型为该策略接口。
第22条:优先考虑静态成员类
- 嵌套类是指被定义在另一个类的内部的类。嵌套类分为四种:静态成员类,非静态成员类,匿名类和局部类。
- 如果一个嵌套类需要在单个方法之外仍然是可见的,或者它太长了,不适合于放在方法内部,就应该使用成员类。如果成员类的每个实例都需要一个指向其外围实例的引用,就要把成员类做成非静态的;否则,就做成静态的。架设这个嵌套类属于一个方法的内部,如果你只需要一个地方创建实例,并且已经有一个预置的类型可以说明这个类的特征,就要把它做成匿名类;否则,就做成局部类。
Read full article from 《Effective Java》学习笔记上 – 技术圈
No comments:
Post a Comment