第八条:覆盖equals时请遵守通用约定
1.确定应不应该覆盖equals方法的情况
(1)类的每个实例本质上是唯一的
(2)不关心类是否提供了“逻辑相等“的测试功能。
(3)超类已经覆盖了equals,并且从超类继承过来的子类也是合适的
例如:从AbstractSet继承equals
(4)类是私有的或者包级私有的:此时应该覆盖并抛出异常。
2.Equals的通用约定
(1)自反性:reflexive, x.equals(x)必须返回true
(2)对称性:symmetric对于任意非null的x和y,x.equals(y)和y.equals(x)必须返回相同的结果
(3)传递性transitivity,对于任何非null的对象x,y和z,如果x.equals(y)和y.equals(z)都返回true,那么x.equals(z)也应该返回true
(4)一致性consistency:如果两个对象相等,那么他们必须始终保持相等,直到其中有一个被修改过。
(5)非空性 null-nullity:需要类型检查,不需要额外代码
3.高质量equals方法的诀窍
(1)使用 == 操作符检查“参数是否为自身的引用”:性能优化
(2)使用instanceof操作符检查“参数是否为正确的类型”:类或者接口
(3)把参数转化为正确的类型:上一步确保成功
(4)对于每个关键域,检查是否匹配
4.告诫
(1)必须覆盖hashCode方法
(2)不要企图让equals过于智能
(3)不要把equals里声明的Object替换成其他类型,否则不是override而是重载
第九条:覆盖equals时总要覆盖hashCode
1.约定
(1)对于任意一个对象,只要对象中涉及equals的关键域没有改变,所有的hashCode必须返回同样的整数结果
(2)如果两个对象的equals返回相等,那么他们的hashCode也必须相等,违反此条会导致HashMap类无法使用
(3)如果两个对象的equals返回不等,最好返回截然不同的整数,使散列表有较高的效率
第十条:始终要覆盖toString()
1.约定;所有的子类都应该覆盖toString()
2.toString应该返回对象所包含的所有值得关注的信息
3.对象太大或者包含的状态无法用字符串描述时,应该返回摘要
4.无论是否在文档里指定返回值的格式,都应该在文档中明确表现你的意图。
第十一条:谨慎地覆盖clone()
1.Object的clone方法是受保护的,所以实现cloneable接口并不能调用clone,除非使用反射机制。
2.Cloneable接口的作用:决定了Object中受保护的clone方法的实现行为
3.如果覆盖了非final类的clone方法,就应该返回一个通过调用super.clone()而得到的对象
4.clone方法是另一个构造器:
(1)不能伤害到原始对象
(2)正确创建被克隆对象中的约束条件。
5.代替对象拷贝的途径:
(1)拷贝构造器:public Yum(Yum yum)
(2)拷贝工厂:public static Yum newInstance(Yum yum)
第十二条:考虑实现Comparable接口
1.如果你正在编写一个值类,并且具有非常明显的顺序关系,那么就应该坚决实现这个接口
2.compareTo方法约定
(1)将当前对象与参数比较,当对象小于,等于或者大于参数时,返回一个负整数,0和正整数。无法比较或者类不同时则抛出ClassCastException。
(2)实现者必须满足自反性,传递性等性质
(3)compareTo是comparable接口中的唯一方法。
第十三条:使类和成员的可访问性最小化
1.封装的规则:
(1)尽可能使每个类或者成员不被外界访问
(2)实例类绝不能公开
第十四条:在公有类中使用访问方法而不是公有域
1.公有类不应该直接暴露数据域
第十五条:使可变性最小化
1.不可变类遵循的原则
(1)不要提供任何会修改对象状态的方法
(2)保证类不会被扩展:一般做法是让这个类成为final
(3)使所有的域都是final的
(4)使所有的域都成为私有的
(5)确保对于任何可变组件的互斥访问
2.不可变对象本质上是线程安全的,他们不要求同步。
(1)它们可以被自由的共享
(2)不可变的类可以提供静态工厂,把频繁请求的实例缓存起来,当现有实例符合请求的时候就不必创建新的实例
(3)永远不用进行保护性拷贝
(4)可以共享这些类的内部信息
3.不可变类的缺点:对于每一个不同的类都需要一个单独的对象
4.告诫
(1)如果选择让不可变类实现Serializable接口,并且它包含一个或者多个指向可变对象的域,就必须提供显式的readResolve或者readObject方法
(2)坚决不要为get方法添加setter:除非有很好的理由让类成为可变的,否则就应该是不可变
(3)如果类不能做成不可变的,依然应该尽可能限制它的可变性。除非有令人信服的原因把域做成非final的,否则每个域都必须是final的
(4)构造器应该创建完全初始化的对象,建立其所有的约束关系,不要提供额外的公有初始化方法,也不要提供重新初始化方法
第十六条:复合优先于实现继承
1.继承打破了封装性
例:HashMap类拓展导致重复计数
2.复合与转发方法
例:
public class InstrumentedSet implements Set{
private final Set s;
private int addCount = 0;
public InstrumentedSet(Set s){
this.s = s;
}
public boolean add(Object o){
addCount++;
return s.add(o);
}
public boolean addAll(Collection c){
addCount +=c.size();
return s.addAll(c);
}
public int getAddCount(){
return addCount;
}
//Forwarding methods
}
第十七条:要么为继承而设计并提供文档,要么禁止继承
1.该类的文档必须精确的描述覆盖每个方法所带来的影响(自用性说明)。
(1)类通过钩子hook以便能够进入方法的实现内部。这样的方法可以是protected方法
2.其他约束
(1)构造器决不能调用可覆盖的方法,否则会导致意外
(2)谨慎的使用Cloneable接口和Serializable接口:clone和readObject方法 同样不能调用可覆盖的方法
(3)如果在一个为继承而设计的类实现Serializable接口,就必须让readResolve方法成为受保护的方法:为了继承而暴露实现细节
第十八条:接口优于抽象类
1.接口的优势
(1)现有的类可以很容易被更新,已实现新的接口。
(2)接口是定义mixin的理想选择。
(3)接口允许我们构造非层次的类框架。
(4)对每一个接口都最好提供一个抽象的骨架实现类skeleton implementation,把接口和抽象类的优势结合起来,骨架实现被称之为AbstractInterface.
例如 Collection Framework中的AbstractSet, AbstractList和AbstractMap. 他们是为了继承而设计的.
2.骨架实现类
(1)它们为抽象类提供了实现上帮助,但又不强加“抽象类被用作类型定义”时所特有的严格限制
(2)如果预置的类无法拓展骨架实现类,这个类始终可以实现对应的接口。实现了这个接口的类可以把接口方法的调用转发到一个内部私有类的实例上,这个实例扩展了骨架实现类。这种方法叫做模拟多重继承。
(3)编写骨架类
3.抽象类的优势
(1)抽象类的演变要比接口的演变更加容易,如果想在后续的发行版本里加入新的方法,抽象类始终可以实现具体方法,并且所有实现都能使用新方法。接口一旦被公开发行并实现,就不能更改
(2)发行接口之前必须尽可能测试接口
第十九条:接口只用于定义类型
1.常量接口是对接口的不良使用
第二十条:类层次优于标签类
1.标签类的缺点
(1)标签类破坏了可读性。
(2)标签类占用了额外内存。
(3)域不能做成final的
(4)无法给标签类添加新的风格而不修改源代码
(5)实例没有提供任何关于其风格的线索
(6)标签类几乎没有使用的时候,使用标签类就要考虑是否重构成类层次
(7)标签类实际是类层次的简单效仿
第二十一条:用函数对象表示策略
1.类似C语言的函数指针(策略模式)
在Java里有函数对象,这种实例的方法是某种操作的具体策略。
2.策略接口:
策略类应该extend的接口,需要使用泛型实现
3.策略类的具体实现
(1)大部分时候使用匿名类
(2)如果策略类需要经常调用,就应该将这个类保存在一个私有静态final域中。
第二十二条:优先考虑静态成员类
1.嵌套类
被定义在另一个类的内部的类,存在目的是为它的外围类提供服务,共四种。
(1)静态成员类
(2)非静态成员类
(3)匿名类
(4)局部类
2.如果声明成员类不要求访问外围实例, 就要始终把static修饰符放在它的声明中.
本人才疏学浅,若有错误,请指出
谢谢!