How is having a wrapper class equals composition as described Joshua Bloch?

我们两清 提交于 2019-12-14 03:57:10

问题


I am reading the book effective java by Joshua Bloch. on the item 16 of "favor composition over inheritance", he gives an example of using HashSet and querying how many elements have been added since it was created(not to be confused with current size, which goes down when an element is removed). he provided the following code and here the getAddCount return 6, which I can understand. This should return 3 actually. (this is because HashSet's addAll method is implemented on top of its add method)

import java.util.HashSet;

public class InstrumentedHashSet<E> extends HashSet<E> {
    // The number of attempted element insertions
    private int addCount = 0;

    public InstrumentedHashSet() {
    }

    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }

    @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;
    }

    public static void main(String[] args) {
        InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
        s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
        System.out.println(s.getAddCount());
    }
} 

Now he explains a way to fix this, using wrapper classes (composition and forwarding). here is where I am having hard time to understand. he provides the following two classes

    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();
    }
}

AND

import java.util.*;
    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;
        }

        public static void main(String[] args) {
            InstrumentedSet<String> s = new InstrumentedSet<String>(
                    new HashSet<String>());
            s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
            System.out.println(s.getAddCount());
        }
    }

how this works? In the main method, I create an instance of HashSet and using addAll method, I add all the elements of list. but the HashSet invokes its addAll method (which in turn uses its add method), which should be the same as in the first in correct example and I should get value of 6, however this gives me 3.


回答1:


In

public class InstrumentedHashSet<E> extends HashSet<E> {

you're adding directly to the HashSet because the addAll() is delegating to the super implementation

InstrumentedHashSet<String> s = new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
System.out.println(s.getAddCount());

The addAll() internally calls add() which defers to your @Override implementation of add() because of polymorphism

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

that increments the count and prints 6 (3 + 1 + 1 + 1).

In

public class InstrumentedSet<E> extends ForwardingSet<E> {

you are adding to

private final Set<E> s;

because the addAll() is delegating to it, so

public static void main(String[] args) {
    InstrumentedSet<String> s = new InstrumentedSet<String>(
                new HashSet<String>());
    s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
    System.out.println(s.getAddCount());
}

and prints 3. Here the add() is being called on the Set<E> s, not on your instance.

The conclusion is that if you are inheriting, you need to understand the side-effects. Do the super method calls invoke any other method calls internally? If so, you need to act appropriately.

Inheritance (start from bottom)

s.add() // s is your InstrumentedHashSet instance, because of polymorphism (inheritance), this adds to the count
this.add() // this is the internal call inside the HashSet#addAll()
super.addAll(...) // this calls the HashSet implementation of addAll which calls add() internally
s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); // s is your InstrumentedHashSet instance

Composition

this.add() // this is the internal call to add() inside the Set implementation
s.addAll() // s is the Set<E> instance
super.addAll(...) // this calls the ForwardingSet implementation of addAll()
s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); // s is your InstrumentedSet instance



回答2:


InstrumentedSet#getAddCount() returns 6 because the size of the array (3) is added twice!

//InstrumentedSet
public boolean addAll(Collection<? extends E> c) {
    addCount += c.size(); //here
    return super.addAll(c); //and here!
}

super.addAll(c); calls the add() Method.

More detailed:

InstrumentedSet#addAll -> ForwardingSet#addAll (because of super.addAll) -> HashSet#addAll() (because this is what you give it in the main) -> InstrumentedSet#add (because of polymorphism)

If you want a fix: remove addCount += c.size();

InstrumentedSet#addAll returns 3 because it calls this:

InstrumentedSet#addAll() (adds 3) -> ForwardingSet#addAll (because of super) -> HashSet#addAll (because forwardingset has a field of type HashSet) -> HashSet#add



来源:https://stackoverflow.com/questions/18519192/how-is-having-a-wrapper-class-equals-composition-as-described-joshua-bloch

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!