equals && hashCode
- 重写equals方法,只适合值类(枚举类除外)
- 重新equals方法(普遍的等价关系),需遵循的约定:
- 自反性:x.equals(x)==true
- 对称性: x.equals(y)==true 必然 y.equals(x)==true
- 传递性: x.equals(y)==true, y.equals(z)==true 必然 x.equals(z)==true
- 一致性:多次执行结果一致。
重写equals,必须重写hashCode, equals相等hashCode必须相等。hashMap,hashSet需要。
无法再扩展可实例化类(写子类)的同时,既增加值组件,又保留equals约定
- 详见”代码-1”
- 解决方式:1,用组合代替继承。2,值组件加到抽象类中。
- 代码-1:
Piont p = Piont(x,y);
Point3D p3d = Point3D(x,y,z);
p.equals(p3d) == true;
p3d.equals(p) == false;
封装、继承、组合(组合优于继承+自己的心得)
- 封装:隐藏实现细节,改变细节而不影响接口/服务/使用
- 函数/方法是封装,类也是封装。
- 目的:解放精力在更高的层次上考虑和解决问题
- 复用的两种方式:继承、组合
- 继承的优缺点:
- 优点: 很方便的复用,比组合复用方便的多
- 缺点: 可能会破坏封装
- 继承可能会破坏封装的分析:
- 子类依赖于父类的实现细节,子类在继承时需要关注父类的实现细节;父类在修改内部实现时,需要考虑对子类的影响。详见”代码-1”,”代码-2”
- 子类重写父类方法,可能会违背父类方法的声明
- 方法调用不会依赖实现细节,
- 如何正确使用继承:
- 要么为继承而设计,并提供文档说明,要么就禁止继承–原书第17章
- 文档必须说明每个可覆盖的方法的自用性
- 文档必须精确地描述覆盖每个方法所带来的影响
- 上述两条违背了”文档应该描述做什么,而不是怎么做”,这是继承破坏封装带来的恶果。
- 构造器决不能调用可被覆盖的方法,无论是直接调用还是间接调用
- 不可覆盖的方法: 使用final或者private修饰
- 不可被继承的类: 使用final修饰类,private修饰所有构造(然后提供工厂方法)
- 要么为继承而设计,并提供文档说明,要么就禁止继承–原书第17章
- 代码-2:
/**
* 1,子类MyHashSet的addAll(),势必要考虑父类addAll()的实现细节。
* 2,父类未来修改addAll()的内部实现,很可能会影响到子类的正确性。
*/
public class MyHashSet<E> extends HashSet<E>{
private int addCount = 0;
public boolean add(E e) {
addCount++;
return super.add(e);
}
/**
* 父类,子类各自的实现细节及依赖:
* <pre>
* 1, 父类HashSet.addAll()-->父类HashSet.add()
* 2, 子类MyHashSet.addAll()-->父类HashSet.addAll()-->子类MyHashSet.add() //因为多态
* </pre>
*/
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
public static void main(String[] args) {
MyHashSet<String> s = new MyHashSet<String>();
s.addAll(Arrays.asList("a", "b", "c"));
//输出是6,不是3。
System.out.println(s.getAddCount());
}
}
其他准则
- 始终覆盖toString
- 使类和成员的访问下最小。以后再改大。
- 接口优于抽象类
- 接口只用于定义类型
- 用类层次替代标签类