4

Let me admit first that I'm not much of a Java programmer. I'm coming from a C++ viewpoint, looking at someone else's code, and wondering... how on earth does that work?

C++ does have templated member functions akin to Java's generic methods, but C++ templated member functions cannot be virtual -- that is, they cannot be overridable. This limitation makes sense to me, because virtual (overridable) functions are ultimately function pointers, and if it was possible for a virtual function to be templated, then a class definition couldn't know how many function pointers to generate for itself.

But Java generic methods, on the hand, seem perfectly fine being overridden by a derived class. How does Java manage that?

Jeff M
  • 2,492
  • 3
  • 22
  • 38
  • This might be a good start: https://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java/ – Artur Biesiadowski Jan 25 '18 at 08:05
  • 1
    This is a very broad question. Can you show a code example of the specific thing you're asking about, so that an answer can focus on that? – Andy Turner Jan 25 '18 at 08:12

2 Answers2

3

As far as I can tell, C++ compiler instantiate (implicitly or explicitly) template function for a given template argument and at runtime we have different function for each that template argument.

In Java there is no concept such like generic instantination or specialization. For example,

public class MyClass{

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

}

We cannot instantiate and specialize it something like in C++

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

Instead type erasure came, so at runtime we have a single ungenerified version of the generic-method which is possible to override.

Take a look at this simple class:

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){ }
}

And the run

javac Main.java
javap -c Main.class

It gives this:

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

}

Notice the signatures of the compiled methods:

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

As @Eugene noted generic types will erase to the first type bound in case of &.

St.Antario
  • 26,175
  • 41
  • 130
  • 318
  • Sounds like `public void method(T t){ }` is more or less equivalent to `public void method(Object t){ }`? – Jeff M Jan 25 '18 at 08:33
  • @JeffM Judging by the bytecode generated, yes. It can be called with any `Object`. I added some more examples with type-parameter bound – St.Antario Jan 25 '18 at 08:35
  • @JeffM If we specify type-parameter bound (`extends` or `super`). The type erasure will erase it to the type bound. – St.Antario Jan 25 '18 at 08:40
  • @St.Antario one more important diff is that C++ `Integer` and `Double` can not share any code, while java can – Eugene Jan 25 '18 at 08:49
  • @St.Antario actually I think it would be correct to say it will erase to the first type bound, as there could be `& Something & SomethingElse` – Eugene Jan 25 '18 at 08:51
  • @Eugene Very interesting note. Have not thought about it actually so far. Just checked it with `Serializable` and `Comparable`. – St.Antario Jan 25 '18 at 08:55
  • @St.Antario unfortunately I don't remember where this would matter (I'll try to dig it up), that would really make this comment a good one – Eugene Jan 25 '18 at 09:58
2

C++ templates need for every usage to generate an actual class based on the actual parameter types.

Java intended to generate one single Object parameters based class. At runtime just remains Object - this is called type erasure.

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(); }
}

What is generated is something like:

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++ has a virtual method table for a this, to replace method pointers with that of the class of this.

Java has a .class file which is more like a C++ .o or .obj containing "link" info on methods: their signature: name and full parameter type names. The "linking" happens on class loading. The jvm byte code method invoke operation can then deal with inheritance.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • 1
    A little clarification. "Java intended to generate one single Object parameters based class." If type bound that's different from `Object` is specified, the type parameter will be in the generated bytecode. – St.Antario Jan 25 '18 at 08:46
  • @St.Antario thanks; I will not improve upon my answer further as I upvoted your answer, especially after adding the disassembled code nicely presented. – Joop Eggen Jan 25 '18 at 08:55