effective中的复合优先于继承

public class InstrumentedHashSet<E> extends HashSet {

  private long count;

  @Override
  public boolean add(Object o) {
    count++;
    return super.add(o);
  }

  @Override
  public boolean addAll(Collection collection) {
    count += collection.size();
    return super.addAll(collection);
  }

  public long getCount() {
    return count;
  }

  public static void main(String[] args) {
    HashSet hashSet = new HashSet(3);
    hashSet.add(1);
    hashSet.add(2);
    hashSet.add(3);

    InstrumentedHashSet<Integer> instrumentedHashSet = new InstrumentedHashSet<>();
    instrumentedHashSet.addAll(hashSet);
    //结果输出为6,而非3
    System.out.println(instrumentedHashSet.getCount());
  }
}

---------------------

这里首先调用了instrumentedHashSet的对象的addAll,然后count变成3,之后调用super.addAll,也就是调用了hashset的addAll,hashset里的addAll应该调用了hashset的add吧?那为什么count会是6,不应该就是3吗?

我调试了下程序,发现是hashset里的addAll调用的是InstrumentedHashSet的add,这是为什么呢?因为add方法被覆盖的缘故吗?父类自己调自己的方法,结果发现被子类覆盖了,就调子类的方法了?

子类调用方法A,如果没被覆盖则调用父类的方法A,如果被覆盖,则调用自身的方法A。
父类调用自己方法A,还要去看是否被子类覆盖的吗?有点懵。

问题抛出:这究竟是怎样的调用链呢?(把自己的思路都写上来了,可能有些凌乱,新手勿喷...)

阅读 2.5k
3 个回答

我java不熟,但是我从js的角度也得出了同样的结果,你的问题可以变成如下的最小模型

class Parent{
    add(){
        console.log("parent")
    }
    addAll(){
        debugger;
        this.add()
    }
}
class Child extends Parent{
    constructor(){
        super()
    }
    add(){
        console.log("child")
    }
    addAll(){
        return super.addAll()
    }
}
var child = new Child();
child.addAll();

clipboard.png

因为child的自身身上已经有了所以无需往parent身上找了
java也是类似

clipboard.png

clipboard.png

你debug的情况下可以看一下进入父类时的this仍然是子类实例,所以调用add仍会调用子类的add方法。

原因在于创建子类对象时会调用父类构造方法但是并不会构建一个父类实例,所以并没有父类实例调用自己的方法。super类似一个标记,告诉jvm此处去调用父类的方法而已。

实际调用的addAll是AbstractCollection里面的addAll,hashSet没有addAll,AbstractCollectoin里面的add是需要被重写的,你的调用类是InstrumentedHashSet,他会优先从这个类开始查找add的重写,所以调用了他的add。
如果你的InstrumentedHashSet没有重写add,他才会调用hashSet的add实现

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏