2

我正在阅读 Joshua Bloch 的书 Effective java。在“优先组合优于继承”的第 16 项中,他给出了一个使用 HashSet 并查询自创建以来添加了多少元素的示例(不要与当前大小混淆,当元素被删除时,当前大小会减小)。他提供了以下代码,这里 getAddCount 返回 6,我可以理解。这实际上应该返回 3。(这是因为 HashSet 的 addAll 方法是在其 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());
        }
    }

这是如何工作的?在 main 方法中,我创建了一个 HashSet 实例,并使用 addAll 方法添加列表的所有元素。但是 HashSet 调用它的 addAll 方法(它又使用它的 add 方法),这应该与正确示例中的第一个相同,我应该得到 6 的值,但是这给了我 3。

4

2 回答 2

4

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
于 2013-08-29T19:16:22.967 回答
1

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(因为 super)-> HashSet#addAll(因为 forwardingset 有一个 HashSet 类型的字段)-> HashSet#add

于 2013-08-29T19:20:15.380 回答