Как класс обертки равен составу, как описано Джошуа Блохом?

Я читаю книгу Джошуа Блоха "Эффективная Java". В пункте 16 "Сочетание предпочтения по сравнению с наследованием" он приводит пример использования HashSet и запроса количества элементов, добавленных с момента его создания (не путать с текущим размером, который уменьшается при удалении элемента). он предоставил следующий код и здесь getAddCount возвращает 6, что я могу понять. Это должно вернуть 3 на самом деле. (это потому, что метод addAll HashSet реализован поверх его метода add)

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

Теперь он объясняет, как это исправить, используя классы-обертки (составление и пересылка). вот где мне трудно понять. он предоставляет следующие два класса

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

А ТАКЖЕ

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

как это работает? В основном методе я создаю экземпляр HashSet и, используя метод addAll, добавляю все элементы списка. но HashSet вызывает свой метод addAll (который, в свою очередь, использует свой метод add), который должен быть таким же, как в первом в правильном примере, и я должен получить значение 6, однако это дает мне 3.

2 ответа

Решение

В

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

вы добавляете прямо к HashSet поскольку addAll() делегирует супер реализации

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

addAll() внутренние звонки add() который зависит от вашего @Override реализация add() из-за полиморфизма

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

который увеличивает счет и печатает 6 (3 + 1 + 1 + 1).

В

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

вы добавляете в

private final Set<E> s;

поскольку addAll() делегировать ему, так

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

и печатает 3, Здесь add() вызывается на Set<E> sне в вашем случае.

Вывод таков: если вы наследуете, вам нужно понимать побочные эффекты. Сделать super вызовы методов вызывают любые другие вызовы методов внутри? Если это так, вам нужно действовать соответствующим образом.

Наследование (начало снизу)

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

Состав

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

InstrumentedSet#getAddCount() возвращает 6, потому что размер массива (3) добавляется дважды!

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

super.addAll(c); вызывает add() Метод.

Более подробный:

InstrumentedSet # addAll -> ForwardingSet # addAll (из-за super.addAll) -> HashSet # addAll () (потому что это то, что вы даете в основном) -> InstrumentedSet#add (из-за полиморфизма)

Если вы хотите исправить: удалить addCount += c.size();

InstrumentedSet#addAllвозвращает 3, потому что это вызывает это:

InstrumentedSet # addAll () (добавляет 3) -> ForwardingSet#addAll (из-за супер) -> HashSet#addAll (потому что forwardingset имеет поле типа HashSet) -> HashSet#add

Другие вопросы по тегам