Java 泛型 协变式覆盖和泛型重载

@author ixenos

1.协变式覆盖(Override)

在JDK 1.4及以前,子类方法如果要覆盖超类的某个方法,必须具有完全相同的方法签名,包括返回值也必须完全一样。

JDK 5开始,只要子类方法与超类方法具有相同的方法签名,或者子类方法的返回值是超类方法的子类型(增加了对协变返回值的支持),就可以覆盖。这样有什么好处呢?以Object类的clone方法为例:

class Object {
...
public Object clone() { ... }
}

在5.0以前,如果子类需要重载clone方法,必须像下面这样写代码:

class Point {
public int x;
public int y;
public Point(int x, int y) { this.x=x; this.y=y; }
public Object clone() { return new Point(x,y); }
}

虽然在我们的Point类里,clone方法总是返回一个Point类型的对象,但却必须把返回类型写成Object,在外部使用clone方法时也必须使用恼人的强制类型转换。

在Java5.0以后,我们就可以利用新的覆盖规则,像下面这样编写代码:

class Point {
public int x;
public int y;
public Point(int x, int y) { this.x=x; this.y=y; }
public Point clone() { return new Point(x,y); }
}

这样,我们就可以直接使用Point p2 = p1.clone(); 而不用强制类型转换了。

2.泛型重载(overload)

Java的方法重载一般指在同一个类中的两个同名方法,规则是:两个方法必须具有不同的方法签名。因此形式参数必须不相同,使得编译器能够区分开这两个重载的方法。由于编译器不能仅仅通过方法的返回值类型来区分重载方法,所以如果两个方法只有返回类型不同,其它完全一样,编译是不能通过的。(泛型、重载是java语言级别的,但擦除技术是关于实现的,它关系到合法class文件的生成,而合法的class文件才能被jvm接受,jvm本来就支持签名相同,但返回类型不同的方法存在

  在java语言角度的添加这种限制也是自然的。比如两个方法:

  void test(int i);

  int test(int i);

  编译器不能确定到底应该调用哪个方法,所以这种情况在java中不允许存在。

  但是,对于这两个方法test(ArrayList<String> list)和test(ArrayList<Integer> list),在java语言的级别,即编译时,也可以是合法的重载!

  因为编译器可以通过参数类型信息来确定调用哪个版本。再加上返回类型不同,经过编译和类型擦除得到的两个方法是可以在class文件中共存的。这样问题就解决了。

泛型方法的重载时,这个规则变化如下:

class Overloaded {
public static int sum(List<Integer> ints) {
int sum = 0;
for (int i : ints) sum += i;
return sum;
}
public static String sum(List<String> strings) {
StringBuffer sum = new StringBuffer();
for (String s : strings) sum.append(s);
return sum.toString();
}
}

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

int sum(List)
String sum(List)

JVM允许这两个方法进行重载(overload!),虽然它们的方法签名相同(形参),只有返回值类型不同。这在两个普通方法的重载中是不允许的。

当然了,如果两个泛型方法的参数在擦除后相同,而且返回值类型也完全一样,那编译肯定是不能通过的。

类似地,一个类不能同时实现两个具有相同擦除的接口。如Class A implements Comparable<Integer>, Comparable<Long>。

  总结一下,

  如果两个泛型方法在擦除泛型信息后,如果只是具有相同的参数类型,而返回值不一样,就可以进行重载;


 

2016-09-05 17:39:18更新:

  此类泛型重载在JDK 1.7及以上编译时已不允许。

JDK7、8是不可以编译的,需要用JDK6才可以(答案中的均使用oracle jdk提供的编译器)。

首先,按道理这个本来就应该报错,从Java语言层面来说,方法重载依赖于相同的方法名、不同的参数个数、类型、顺序,而List<Integer>和List<String>类型擦除后都为List<E>,从而不符合方法重载的要求。
但是,为什么会说这种依赖返回值可以通过甚至正常运行,原因在于,编译后的俩个方法在class中的signature分别为

(Ljava/util/List<Ljava/lang/Integer;>;)I
(Ljava/util/List<Ljava/lang/String;>;)Ljava/lang/String;

它们可以合法的共存在一个class文件中。

从jdk7开始呢,编译期做了check,保证了behavior一致,所以报错

参考链接:
http://bugs.java.com/view_bug.do?bug_id=6182950

作者:葫芦娃
链接:https://www.zhihu.com/question/37802781/answer/75883080
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

最新文章

  1. How to Install The Alpha Control Packages.
  2. 关于ddpush推动实现抖动视频的使用
  3. FAST特征点检测features2D
  4. 学习笔记——Maven 内置变量
  5. Replace Nested Conditional with Guard Clauses(用卫语句代替嵌套循环)
  6. iOS 进入后台的处理
  7. Peer certificate cannot be authenticated with known CA certificates.
  8. 3713: [PA2014]Iloczyn
  9. 一个简单的makefile文件编写
  10. JavaSe:UncaughtExceptionHandler
  11. web安全基础第一天
  12. 11:57:24 [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1] WARN o.apache.kafka.clients.NetworkClient - [Consumer clientId=consumer-2, groupId=jiatian_api] 3 partitions have leader……
  13. day 15 - 2 内置函数练习
  14. sping入门
  15. GGTalk ——C#开源即时通讯系统
  16. 使用Future停止超时任务
  17. 常用linux,DOS命令——持续更新
  18. USB AUDIO Device CLASS Requests
  19. Python中使用MongoEngine
  20. spring mvc 解决json 不能转换的问题

热门文章

  1. iOS 加载Image的两种方式
  2. C# IIS7.0+ Web.Config 配置Session过期时间
  3. MVC 之下载 我的实践
  4. Android中Activity全局共享方法AppContext
  5. nodejs 中es5 模块的几种写法
  6. Jstorm调度定制化接口(0.9.5 及高版本)
  7. php获取url字符串截取路径的文件名和扩展名
  8. 世界之窗(TheWorld)浏览器 3.6.1.0 简体中文绿色版
  9. oc UIAlertController封装
  10. 前端用Request Payload方式请求后台