泛型遇上重载

泛型遇上重载

引出这个问题

我知道的是方法的返回值是不参与到方法的特征签名中的,方法重载要求的是不同的特征签名,因此方法的返回值不参与重载,而Java中的泛型是一个假的泛型,在运行之后都被擦除了,全变成List原生的E,就是说下面的代码是编译不会通过的

1
2
3
4
5
6
public static void test(List<String> list){
System.out.println("String");
}
public static void test(List<Integer> list){
System.out.println("Integer");
}

编译直接报错

1
2
3
4
5
6
7
GenericAndReload.java:25: name clash: test(java.util.List<java.lang.String>) and test(java.util.List<java.lang.Integer>) have the same erasure
public static void test(List<String> list){
^
GenericAndReload.java:29: name clash: test(java.util.List<java.lang.Integer>) and test(java.util.List<java.lang.String>) have the same erasure
public static void test(List<Integer> list){
^
2 errors

在深入理解Java虚拟机书中的编译器优化部分看到这个

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
test(new ArrayList<String>());
test(new ArrayList<Integer>());
}
public static int test(List<String> list){
System.out.println("String");
return 1;
}
public static String test(List<Integer> list){
System.out.println("Integer");
return "";
}
  • 上面的代码在jdk6中使用原生的命令行模式下运行,如果使用编译器运行,编译器会拒绝运行这个代码
  • 编译器一般来说会提示both method hava same erasure
  • 我个人使用的是我的一台centos6的服务器上的jdk6运行的,运行截图如下

分析一下为什么上面第一次运行的代码不能编译而第二个又可以编译

  • 这个问题主要细分为几个问题

    1. 泛型擦除
    2. 重载
    3. 方法区、特征签名
  • 泛型
    泛型的本质是参数化类型的应用,在Java中的泛型实际上是一颗语法糖.

    1
    2
    3
    4
    5
    6
    7
    public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    list.add("hehe");
    for (String str : list){
    System.out.println(str);
    }
    }

使用jap反编译后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

public static void main(String args[])
{
List list = new ArrayList();
// 0 0:new #2 <Class ArrayList>
// 1 3:dup
// 2 4:invokespecial #3 <Method void ArrayList()>
// 3 7:astore_1
list.add("hehe");
// 4 8:aload_1
// 5 9:ldc1 #4 <String "hehe">
// 6 11:invokeinterface #5 <Method boolean List.add(Object)>
// 7 16:pop
String str;
for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(str))
//* 8 17:aload_1
//* 9 18:invokeinterface #6 <Method Iterator List.iterator()>
//* 10 23:astore_2
//* 11 24:aload_2
//* 12 25:invokeinterface #7 <Method boolean Iterator.hasNext()>
//* 13 30:ifeq 53
str = (String)iterator.next();
// 14 33:aload_2
// 15 34:invokeinterface #8 <Method Object Iterator.next()>
// 16 39:checkcast #9 <Class String>
// 17 42:astore_3

// 18 43:getstatic #10 <Field PrintStream System.out>
// 19 46:aload_3
// 20 47:invokevirtual #11 <Method void PrintStream.println(String)>
//* 21 50:goto 24
// 22 53:return
}

同样Map等等这些都是这样做的.

  • 重载
    重载的规则:
    1. 方法名一样,方法的参数的顺序,类型、个数不同
    2. 可以抛出不同的异常,可以有不同的修饰符
    3. 重载与方法的返回值无关.
  • 方法与特征签名
    1. 特征签名: 就是一个方法中各个参数在常量池中的字段符号引用的集合.
    2. 在Class文件格式中,特征签名的范围更大一些,只要描述符不是一致的两个方法也可以共存.
    3. 描述符是java虚拟机层面的概念,是针对class文件字节码定义的.
    4. 方法描述符中包括方法的返回值类型.
  • 问题
    1. 既然Java虚拟机这本书中写道,在Class文件中只要描述符不一致就可以共存,而方法描述符中包括方法的返回值类型.那按照这个意思在Class文件中只要返回值 类型不一样,也是可以实现重载的.
      但是问题就是如果这样看的话,重载就与方法的返回值有关了.
    2. 先列出我运行的情况
      1. 参数个数类型这个就不说了
      2. 返回值是int型的test(List<String>)和void的list<Integer>可以
      3. 只有返回值不同的两个方法,参数类型个数一致,都是List<String>,这种是不可以的.
      4. 一开始写的返回值类型相同,但是参数类型不同的,一个list里面存的是String,一个存的是integer,这种也是不行的.
  • 结论:
    要重载一个方法,除了要与原来的方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名是一个方法中各个参数在常量池中的字段符号的集合,也就是因为返回值不包含在特征签名中,因此Java语言里面是无法仅仅根据返回值的不同来对一个已有的方法进行重载的,但是在Class文件格式中,特征签名的范围更大,只要描述符不完全一致就可以共存。也就是说如果两个方法有相同的名称和特征签名,但是返回值不同,那么也是可以合法的共存于同一个Class文件中的。
0%