I'm losing the type of a variable in a generic method when passing to a second generic method with varargs

I guess it's probably best to start with the behaviour I'm looking at:

public class genericTest {
    public static void main(String[] args) {
        String str = "5318008";

        printClass(str);              // class java.lang.String
        callPrintClass(str);          // class java.lang.String

        printClassVarargs(str);       // class java.lang.String
        callPrintClassVarargs(str);   // class java.lang.Object
    }

    public static <T> void printClass(T str) {
        System.out.println(str.getClass());
    }

    public static <T> void printClassVarargs(T ... str) {
        System.out.println(str.getClass().getComponentType());
    }

    public static <T> void callPrintClass(T str) {
        printClass(str);
    }

    @SuppressWarnings("unchecked")
    public static <T> void callPrintClassVarargs(T str) {
        printClassVarargs(str);
    }
}

Looking at printClass() and callPrintClass(), it seems like everything is working fine. callPrintClass() takes a generic argument and passes it along. printClass() recognises this variable by its correct type, not caring who is sending the parameter, and then does as it's supposed to and prints java.lang.String.

But when we try to use varargs, this stops working. I would expect printClassVarargs() to recognise it's argument is of type String[], much like the method without varargs recognises the type of its argument. Notice also that this doesn't happen if I call printClassVarargs() directly (it's perfectly happy there to output String), but only when it's called by callPrintClassVarargs(), where it forgets the type of its argument and presumes it's getting an Object. I also realise I'm having to suppress a compiler warning here, which usually comes along when I'm trying to cast generics, but I'm not sure what exactly is going on there.

So my question is really two. What is the reason behind this behaviour? Is this some consequence of type erasure, or the way Java handles arrays? And secondly, is there any way around this?

This is only a simple example, of course. I'm not trying to print class names this way, but originally found the problem while writing an overloaded method to concatenate arrays.

Answers


Varargs are syntactic sugar that translates to an array of the given type by the compiler. That means that a method(Type arg...) will become method(Type[] arg).

In Java, you cannot create arrays of Non-reifiable types (types whose type information is lost by erasure). Therefore, a generic varargs such as printClassVarargs(T ... str) will translate to printClassVarargs(Object[] str), effectively resulting in erasure of the type info. This is what you are observing in your test.

--- EDIT ---

To answer your question (cfr comments) on the difference between printClassVarargs(str) and callPrintClassVarargs(str) we could have a look into the bytecode of your test class for the needed clues:

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

public static void main(java.lang.String[]);
  Code:
   0:   ldc #16; //String 5318008
   2:   astore_1
   3:   aload_1
   4:   invokestatic    #18; //Method printClass:(Ljava/lang/Object;)V
   7:   aload_1
   8:   invokestatic    #22; //Method callPrintClass:(Ljava/lang/Object;)V
   11:  iconst_1
   12:  anewarray   #25; //class java/lang/String
   15:  dup
   16:  iconst_0
   17:  aload_1
   18:  aastore
   19:  invokestatic    #27; //Method printClassVarargs:([Ljava/lang/Object;)V
   22:  aload_1
   23:  invokestatic    #31; //Method callPrintClassVarargs:(Ljava/lang/Object;)V
   26:  return

public static void printClass(java.lang.Object);
  Code:
   0:   getstatic   #40; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   aload_0
   4:   invokevirtual   #46; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   7:   invokevirtual   #50; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   10:  return

public static void printClassVarargs(java.lang.Object[]);
  Code:
   0:   getstatic   #40; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   aload_0
   4:   invokevirtual   #46; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   7:   invokevirtual   #59; //Method java/lang/Class.getComponentType:()Ljava/lang/Class;
   10:  invokevirtual   #50; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   13:  return

public static void callPrintClass(java.lang.Object);
  Code:
   0:   aload_0
   1:   invokestatic    #18; //Method printClass:(Ljava/lang/Object;)V
   4:   return

public static void callPrintClassVarargs(java.lang.Object);
  Code:
   0:   iconst_1
   1:   anewarray   #3; //class java/lang/Object
   4:   dup
   5:   iconst_0
   6:   aload_0
   7:   aastore
   8:   invokestatic    #27; //Method printClassVarargs:([Ljava/lang/Object;)V
   11:  return

}

Observe on main#12 that a new String[] of your string obj is created to be used as parameter for printClassVarargs() and callPrintClassVarargs()

On main#19 printClassVarargs is called, with the created String[] as parameter. This results in printClassVarargs knowing the type of that object at runtime. This type was preserved.

On main#23 callPrintClassVarargs is called, also with the created String[] as parameter. Then on callPrintClassVarargs#1 a new array is created. This time, no type information is available from the generic type declaration, so a new Object[] is created. The String[] is stored in this array and on callPrintClassVarargs#8 passed to printClassVarargs, which now has to work on an Object[], whose componentType is object.

As you can observe, the type of your parameter got erased when passed to the generic parameter of callPrintClassVarargs(T str).

QED


I think the problem boils down to the inability to create arrays of generic type. If callPrintClassVarargs is modified to explicitly create a new array instance and pass it to printClassVarargs then the underlying issue becomes explicit.

// doesn't work, gives compiler error (cannot create array of generic type)
public static <T> void callPrintClassVarargs(T str) {
        printClassVarargs(new T[]{str});
}

//This works
public static <T> void callPrintClassVarargs(T str) {
        printClassVarargs(new Object[]{str});
}

What's the reason I can't create generic array types in Java? - This question deals with why it is not possible to create arrays of generic type, maybe the same explains this issue as well.


Need Your Help

How to detect another MKAnnotaion pin when touch

ios objective-c mapkit

I have multiple annotation pin in map(place1,place2)

Makefile cannot find module in Python3

python python-3.x makefile

I have a little application on Python3 but it won't run because of module not found. I added __init__.py for all the folders already but it doesn't work. I'm wondering why.