协变式重写和泛型重载

1、协变式重写

1.1、不同版本之间变化
在Java1.4及以前,子类方法如果要覆盖超类的某个方法,必须具有完全相同的方法签名,包括返回值也必须完全一样;Java5.0放宽了这一限制,只要子类方法与超类方法具有相同的方法签名,或者子类方法的返回值是超类方法的子类型,就可以覆盖;可以不需要强制转换类型
例如:重写 Object 类的 clone()方法:

  • Object 中该方法的声明如下:
    protected native Object clone() throws CloneNotSupportedException;
  • 在类中可以重写实现如下:
@Override
public Employee clone() throws CloneNotSupportedException {
 Employee e = (Employee) super.clone();
 e.address = address.clone();
 return e;
}

2、泛型重载

Java的方法重载一般指在同一个类中的两个同名方法,规则很简单:两个方法必须具有不同的方法签名;换句话说:就是这两个方法的参数必须不相同,使得编译器能够区分开这两个重载的方法;由于编译器不能仅仅通过方法的返回值类型来区分重载方法,所以如果两个方法只有返回类型不同,其它完全一样,编译是不能通过的。在泛型方法的重载时,这个规则稍微有一点变化,看如下代码:

class Overloaded {
 public static int sum(List<Integer> ints) {   
  return 0;
 }
 public static String sum(List<String> strings) {
  return null;
 }
}

上面是两个泛型方法的重载例子,由于Java的泛型采用擦除法实现,List和List在运行时是完全一样的,都是List类型。也就是,擦除后的方法签名如下:
int sum(List) String sum(List)

  • Java允许这两个方法进行重载,虽然它们的方法签名相同,只有返回值类型不同,这在两个普通方法的重载中是不允许的;当然了,如果两个泛型方法的参数在擦除后相同,而且返回值类型也完全一样,那编译肯定是不能通过的;类似地,一个类不能同时继承两个具有相同擦除类型的父类,也不能同时实现两个具有相同擦除的接口。如
    Class A implements Comparable,Comparable。
  • 总结一下:两个泛型方法在擦除泛型信息后,如果具有相同的参数类型,而返回值不一样,是可以进行重载的;Java有足够的信息来区分这两个重载的方法

3、重写与重载

3.1、两者的比较

  • 重载是一个编译期概念、重写是一个运行期间概念;
  • 重载遵循所谓"编译期绑定",即在编译时根据参数变量的类型判断应该调用哪个方法。
  • 重写遵循所谓"运行期绑定",即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法
  • 因为在编译期已经确定调用哪个方法,所以重载并不是多态。而重写是多态。重载只是一种语言特性,是一种语法规则,与多态无关,与面向对象也无关。(注:严格来说,重载是编译时多态,即静态多态。但是,Java中提到的多态,在不特别说明的情况下都指动态多态)
    3.2、重写的条件
  • 参数列表必须完全与被重写方法的相同;
  • 返回类型必须完全与被重写方法的返回类型相同;
  • 访问级别的限制性一定不能比被重写方法的强;
  • 访问级别的限制性可以比被重写方法的弱;
  • 重写方法一定不能抛出新的检查异常或比被重写的方法声明的检查异常更广泛的检查异常
  • 重写的方法能够抛出更少或更有限的异常(也就是说,被重写的方法声明了异常,但重写的方法可以什么也不声明)
  • 不能重写被标示为final的方法;如果不能继承一个方法,则不能重写这个方法
  • 参数列表必须完全与被重写方法的相同;
    3.3、重载的条件
  • 被重载的方法必须改变参数列表;
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载;

4、重载

能够用一个统一的接口名称来调用一系列方法

  • 重载本身并不是多态,同时运行时绑定重载方法也不是多态的表现;
  • 如下例子:重载方法"3"注释与不注释,结果有和不一样
public class NullArguementOverloading {
 public static void main(String[] args) {
  NullArguementOverloading obj = new NullArguementOverloading();
  obj.overLoad(null); // Double array argument method.
 }
 private void overLoad(Object o){ // 1
  System.out.println("Object o arguement method.");
 }
 private void overLoad(double[] dArray){ //2
  System.out.println("Double array argument method.");
 }
 private void overLoad(String str) { //3
  System.out.println("String argument method.");
 }
}

①、注释掉"3",运行结果:Double array argument method
②、不注释掉:obj.overLoad(null);编译错误
Java对重载的处理有最精确匹配原则:

  • ①、Java 的重载解析过程是以两阶段运行的:
    第一阶段 选取所有可获得并且可应用的方法或构造器;
    第二阶段在第一阶段选取的方法或构造器中选取最精确的一个;
  • ②、上面代码:String 也是继承自 Object, 数组也是可认为继承自 Object, 两个为平行等级,null 不确定到底是哪个;
  • ③、另外,重载是在编译期就已经确定了的,并不需要等到运行时才能确定,因此重载不是多态的一个原因。
  • ④、重载对于传入的参数类型只认了引用的类型,并没有去解析实际对象的类型。如果重载是一种多态的话,它这里应该去解析实际对象的类型并调用ArrayList的方法
public class OverridePuzzle {    
 private void overloadList(List list){
  System.out.println("List arguement method.");
 }    
 private void overloadList(ArrayList arrayList){
  System.out.println("ArrayList arguement method");
 }
 public static void main(String[] args) {
  OverridePuzzle op = new OverridePuzzle();
  List list = new ArrayList<String>();
  op.overloadList(list); // List arguement method
 }    
}

5、重写

涉及到继承这个概念中的问题,子类继承了父类的方法,但是它可能需要有不同的操作行为,就需要在子类中重写这个父类方法.父类如果将方法声明为 final 的就可保证所有子类的调用此方法时调用的都是父类的方法;

6、两者的比较

  • 重载是一个编译期概念、重写是一个运行期间概念;
  • 重载遵循所谓"编译期绑定",即在编译时根据参数变量的类型判断应该调用哪个方法。
  • 重写遵循所谓"运行期绑定",即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法
  • 因为在编译期已经确定调用哪个方法,所以重载并不是多态。而重写是多态。重载只是一种语言特性,是一种语法规则,与多态无关,与面向对象也无关。(注:严格来说,重载是编译时多态,即静态多态。但是,Java中提到的多态,在不特别说明的情况下都指动态多态)
posted @ 2020-01-28 11:09  阳神  阅读(126)  评论(0编辑  收藏  举报