Java override方法时返回类型协变、实参逆变

Covariant(协变) 与 Contravariant(逆变)

逆变与协变用来描述类型转换(type transformation)后的继承关系:(从继承关系来看,继承树从上到下,从父类到子类是协变,从子类到父类则是逆变)

  • 当本使用父类的地方,却使用了子类,则称为Covariant(协变)。
  • 当本实用子类的地方,却使用了父类,则称为Contravariant(逆变)。
  • 当使用类没有发生变化,则称为invariant(不变)。

具体到Java语言中,则是:

  • 当子类覆盖父类方法时,返回类型可以协变(假设A是B的父类,父类方法返回A,子类覆盖父类方法时却返回B,从返回类型A到返回类型B,发生了协变;当然也可以不变,这是肯定得)。
  • 当实参传递给形参,参数类型可以逆变(实参类型为子类,而形参类型为父类,从子类到父类,发生了逆变;注意这里与继承覆盖这些无关)。

以上两点也符合协变与逆变的本质——里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象。即Java中父类引用可以指向子类对象。

协变例子(override方法时返回类型协变)

class Grain {
  public String toString() { return "Grain"; }
}

class Wheat extends Grain {
  public String toString() { return "Wheat"; }
}

class Mill {
  Grain process() { return new Grain(); }
}

class WheatMill extends Mill {
  @override
  Wheat process() { return new Wheat(); }
  //返回值Wheat是父类方法返回值Grain的子类,发生协变
}

public class CovariantReturn {
  public static void main(String[] args) {
    Mill m = new Mill();
    Grain g = m.process();
    System.out.println(g);
    m = new WheatMill();
    g = m.process();
    System.out.println(g);
  }
} /* Output:
Grain
Wheat
*/

注意WheatMill 覆盖子类方法时,返回值类型发生了协变。

逆变例子(实参逆变)

class Grain {
    public String toString() { return "Grain"; }
}

class Wheat extends Grain {
    public String toString() { return "Wheat"; }
}

class Mill {
    void process(Grain g) { System.out.println("Mill "+g); }
}

public class Ex10 {
    public static void main(String[] args) {
        Wheat w = new Wheat();
        new Mill().process(w);
    }
} /* Output:
Mill Wheat
*/

new Mill().process(w)实参类型为子类,而形参类型为父类,从子类Wheat到父类Grain,发生了逆变。

逆变不能用在覆盖父类方法的参数类型

class Grain {
    public String toString() { return "Grain"; }
}

class Wheat extends Grain {
    public String toString() { return "Wheat"; }
}

class Mill {
    void process(Wheat w) { System.out.println("Mill "+w); }
}

class WheatMill extends Mill {
    //@Override 注意加此句报错,因为父类没有这样的签名的方法
    void process(Grain g) { System.out.println("WheatMill "+g); }//此方法从未被使用,应该解释为函数重载
}

public class Ex10 {
    public static void main(String[] args) {
        Wheat w = new Wheat();
        new Mill().process(w);
        new WheatMill().process(w);
    }
}/* Output:
Mill Wheat
Mill Wheat
*/

override方法时,父类类型参数和子类类型参数是严格区分开来了的。所以,这个例子实际展示了overload重载函数。

posted @ 2019-10-12 23:26  allMayMight  阅读(270)  评论(0编辑  收藏  举报