Java泛型方法如何在幕后工作?

时间:2018-01-25 08:01:45

标签: java generics

首先我要承认,我并不是一名Java程序员。我来自C ++的观点,看着别人的代码,并想知道......究竟是如何运作的?

C ++确实有模板化的成员函数,类似于Java的泛型方法,但是 C ++模板化成员函数不能是虚拟的 - 也就是说,它们不能被覆盖。这个限制对我来说很有意义,因为虚拟(可覆盖)函数最终是函数指针,并且如果虚函数可能被模板化,那么类定义无法知道为自己生成多少函数指针。

但是Java手上的通用方法似乎完全可以被派生类覆盖。 Java如何管理它?

2 个答案:

答案 0 :(得分:2)

据我所知,C ++编译器为给定的模板参数实例化(隐式或显式)模板函数,并且在运行时,我们为每个模板参数提供不同的函数。

在Java中,没有像通用即时或专业化这样的概念。例如,

public class MyClass{

    public void <T> method(T t){ }

}

我们不能像C ++中那样实例化和专门化它

public void <Integer> MyClass::method(Integer t){ //not valid in Java
   //...
}

取而代之的是type erasure,所以在运行时我们有一个可以覆盖的泛化方法的单一版本。

看看这个简单的课程:

public class Main{

    public static void main(String[] args){

        Main m = new Main();
        m.method(10);
        m.methodNumber(10);
        m.methodNumberAnd(10);
        m.methodNumberAnd2(10);
        m.methodInteger(10);
    }
    public <T> void method(T t){ }
    public <T extends Number> void methodNumber(T t){ }
    public <T extends Number & java.io.Serializable> void methodNumberAnd(T t){ }
    public <T extends java.io.Serializable & java.lang.Comparable<T>> void methodNumberAnd2(T t){ }
    public void methodInteger(Integer t){ }
}

并且运行

javac Main.java
javap -c Main.class

它给出了这个:

public class Main {
  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Main
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: bipush        10
      11: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      14: invokevirtual #5                  // Method method:(Ljava/lang/Object;)V
      17: aload_1
      18: bipush        10
      20: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      23: invokevirtual #6                  // Method methodNumber:(Ljava/lang/Number;)V
      26: aload_1
      27: bipush        10
      29: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      32: invokevirtual #7                  // Method methodNumberAnd:(Ljava/lang/Number;)V
      35: aload_1
      36: bipush        10
      38: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      41: invokevirtual #8                  // Method methodNumberAnd2:(Ljava/io/Serializable;)V
      44: aload_1
      45: bipush        10
      47: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      50: invokevirtual #9                  // Method methodInteger:(Ljava/lang/Integer;)V
      53: return

  public <T> void method(T);
    Code:
       0: return

  public <T extends java.lang.Number> void methodNumber(T);
    Code:
       0: return

  public <T extends java.lang.Number & java.io.Serializable> void methodNumberAnd(T);
    Code:
       0: return

  public <T extends java.io.Serializable & java.lang.Comparable<T>> void methodNumberAnd2(T);
    Code:
       0: return

  public void methodInteger(java.lang.Integer);
    Code:
       0: return

}

注意已编译方法的签名:

Method method:(Ljava/lang/Object;)V
Method methodNumber:(Ljava/lang/Number;)V
Method methodNumberAnd:(Ljava/lang/Number;)V
Method methodNumberAnd2:(Ljava/io/Serializable;)V
Method methodInteger:(Ljava/lang/Integer;)V

正如@Eugene所说,在&的情况下,泛型类型将擦除为第一个类型绑定。

答案 1 :(得分:2)

C ++模板需要根据实际参数类型生成实际类。

Java旨在生成一个基于Object的参数类。在运行时只保留Object - 这称为类型擦除

public class A<T> {
    public T f(T x) { return x; }
}

public class B extends A<String> {
    @Override
    public String f(String s) { return s.toLowerCase(); }
}

生成的内容如下:

public class A {
    public Object f(Object x) { return x; }
}

public class B extends A {
    @Override
    public Object f(Object x) { return B.this.f((String)x); }

    public String f(String s) { return s.toLowerCase(); }
}

C ++有一个虚拟方法表,用于将方法指针替换为类的方法指针。

Java有一个.class文件,它更像是一个C ++ .o或.obj,包含方法的“链接”信息:它们的签名:名称和完整参数类型名称。 “链接”发生在类加载上。然后jvm字节码方法调用操作可以处理继承。