这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战
前言
继承要考虑是否使用的适当,当我们在很小的一个范围或者一个程序员的控制下使用继承的时候,使用继承很安全,但如果我们使用了其他人的api,并对其中的一个类进行了实现继承(不是对接口进行继承),那此时就将会十分的危险,如果下次对方对此类进行了一些修改,那么将有可能造成意料之外的情况发生。
一个例子
public class TestHashSet<E> extends HashSet<E> {
private int count = 0;
public TestHashSet() {
}
public TestHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@Override
public boolean add(E e) {
count++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
count += c.size();
return super.addAll(c);
}
public int getCount() {
return count;
}
public static void main(String[] args) {
TestHashSet<String> hashSet = new TestHashSet<String>();
hashSet.addAll(Arrays.asList(new String[]{"1","2","3"}));
System.out.println(hashSet.getCount());
}
}
我们期望它能返回3,但是实际上返回的是6
这个类不能正常工作的原因是因为在super的addAll方法中,实际上使用了add方法,而这里的add方法又被我们
的TestHashSet这个类覆盖了,就导致addAll中加了3,每次调用add又加了1,调用了3次,就是6了。
如何解决
用复合的方式来解决继承导致的这种依赖于现有类的实现细节的问题,复合不用扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例。 因为现有类变成了一个新类的一个组件,新类中的每个实例方法都能调用被包含的类的实例方法,并返回相应的结果,这称之为转发。 对应的实现分为两部分,类本身和可重用的转发类。
类本身
public class InstrumentedSet<E> extends ForwardingSet<E> {
private int addCount = 0;
public InstrumentedSet(Set<E> s) {
super(s);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
注意这里我们的构造方法里面传入的是一个Set类型,而不是像之前传入参数后实例化一个父类,这样对于InstrumentedSet这个包装类而言,就可以用来包装任何Set实现了。
可重用的转发类
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s; }
public void clear() { s.clear(); }
public boolean contains(Object o) { return s.contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
public int size() { return s.size(); }
public Iterator<E> iterator() { return s.iterator(); }
public boolean add(E e) { return s.add(e); }
public boolean remove(Object o) { return s.remove(o); }
public boolean containsAll(Collection<?> c) { return s.containsAll(c); }
public boolean addAll(Collection<? extends E> c) { return s.addAll(c); }
public boolean removeAll(Collection<?> c) { return s.removeAll(c); }
public boolean retainAll(Collection<?> c) { return s.retainAll(c); }
public Object[] toArray() { return s.toArray(); }
public <T> T[] toArray(T[] a) { return s.toArray(a); }
@Override
public boolean equals(Object o) { return s.equals(o); }
@Override
public int hashCode() { return s.hashCode(); }
@Override
public String toString() { return s.toString(); }
}
转发类这里我们可以看到它的构造方法中将得到的Set类型赋给了它内部的私有域s,从而使得我们可以使用被包含的现有类实例中对应的方法。
包装类的缺点
包装类不适用于回调框架,回调框架中对象会将自身的引用传递给其他的对象,但因为被包装的对象并不知道它外面的包装对象,此时回调会避开外面的包裹类