Effective java Item 16 : Favour composition over inheritance

There is a problem in inheritance: a sub class’s behaviour depends on the implement detail of its super class. And the super class’s implementation may change from release to release, and the sub class may break, even though its code has never been touched.

Consider this broken class, which is trying to keep track of how many items has been added to the CountingHashSet:

/**
* Broken
* A Sample class that demonstrate the inappropriate use of inheritance
*/
public class CountingHashSet<E> extends HashSet<E> {
private int counter = 0;

public CountingHashSet() {
super();
}

@Override
public boolean add(E e) {
counter++;
return super.add(e);
}

@Override
public boolean addAll(Collection<? extends E> c) {
counter+= c.size();
return super.addAll(e);
}

public int getCounter(){
return counter;
}

}

And After executing:

CountingHashSet<String> s = new CountingHashSet<>();
s.addAll(Arrays.asList("A","B","C"));

At this point, we should expect s.getCounter() to return 3. However, it is returning 6. This is because the addAll() function in the sub class first increase the counter by 3, and call its super class’s addAll() function. And we do not know that the super class’s addAll() implementation is calling its add() iteratively to add the items. And the add() function is overrided by its sub class, which increase the counter by 1 each time. So the counter becomes 3(from the c.size()) + 1 + 1 + 1 = 6.

Solution: Composition comes to the rescue! Composition is the ‘sub class’ holds a reference to the ‘super class’ instead of expanding it. Here is the composition example of IncreasingHashSet:

public class ForwardingSet<E> implements Set<E> {

private final Set<E> s;
public ForwardingSet(Set<E> s) {this.s = s;}
public int size() {return s.size();}
public boolean isEmpty() {return s.isEmpty();}
public boolean contains(Object o) {return s.contains();}
public Iterator<E> iterator() {return s.iterator();}
public Object[] toArray() {return s.toArray();}
public <T> T[] toArray(T[] a) {return s.toArray(a);}
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 retainAll(Collection<?> c) {return s.retainAll(c);}
public boolean removeAll(Collection<?> c) {return s.removeAll(c);}
public void clear() { s.clear();}

}

public class IncreasingSet<E> extends ForwardingSet<E>{
private int counter = 0;

public IncreasingSet(Set<E> s) {
super(s);
}

@Override
public boolean add(E e) {
counter++;
return super.add(e);
}

@Override
public boolean addAll(Collection<? extends E> e) {
counter++;
return super.addAll(e);
}

public int getCounter() {
return counter;
}
}

Conclusion: A should extend B only if A truly ‘is-a’ a B, if not, use composition instead, which means A should hold a reference of B and expose a simpler API.