Invoking Java Generic Methods
Asked Answered
Q

3

28

I am studying Java generic feature and I am not sure how to explain the third line in the following main method:

public class Example4 {
    public static void main(final String[] args) {
        System.out.println(Util.<String>compare("a", "b"));
        System.out.println(Util.<String>compare(new String(""), new Long(1)));
        System.out.println(Util.compare(new String(""), new Long(1)));
    }
}

class Util {
    public static <T> boolean compare(T t1, T t2) {
        return t1.equals(t2);
    }
}

The first line compiles, runs, and returns (as expected) false.

The second line does not compile as expected, because I am explicitely mixing String and Long.

The third line compiles, runs, and return false but I am not sure to understand how it happens to work: does the compiler/JVM instantiates the parameter type T as Object? (Also, would there be a way to obtain this declared type of T are runtime?)

Thank you.

Quotha answered 8/4, 2013 at 8:48 Comment(0)
Q
11

The answer seems to go beyond @Telthien and @newacct' answers. I was curious to "see" for myself the difference between:

System.out.println(Util.<String>compare("a", "b"));

with explicity typing, and:

System.out.println(Util.compare(new String(""), new Long(1)));

with implicit typing.

I performed several experiments, using variations on these two previous lines. These experiments show that, short of using the anonymous/local class trick, the compiler does check the types during compilation but the generated bytecodes only refer to Object, even in the case of the first line.

The following piece of code shows that typecasts can be performed safely all the way to Object even in the case of the explicity type argument <String>.

public final class Example44 {
    public static void main(final String[] args) {
        System.out.println(new Util44<String>().compare("a", "b"));
        System.out.println(new Util44().compare(new String(""), new Long(1)));
    }
}

final class Util44<T> {
    private T aT;
    public boolean compare(T t1, T t2) {
        System.out.println(this.aT);
        // I was expecting the second and third assignments to fail
        // with the first invocation because T is explicitly a String
        // and then to work with the second invocation because I use
        // a raw type and the compiler must infer a common type for T.
        // Actually, all these assignments succeed with both invocation. 
        this.aT = (T) new String("z");
        this.aT = (T) new Long(0);
        this.aT = (T) new Object();
        return t1.equals(t2);
    }
}

The bytecodes of the main method look like:

  // Method descriptor #15 ([Ljava/lang/String;)V
  // Stack: 7, Locals: 1
  public static void main(java.lang.String[] args);
     0  getstatic java.lang.System.out : java.io.PrintStream [16]
     3  new ca.polymtl.ptidej.generics.java.Util44 [22]
     6  dup
     7  invokespecial ca.polymtl.ptidej.generics.java.Util44() [24]
    10  ldc <String "a"> [25]
    12  ldc <String "b"> [27]
    14  invokevirtual ca.polymtl.ptidej.generics.java.Util44.compare(java.lang.Object, java.lang.Object) : boolean [29]
    17  invokevirtual java.io.PrintStream.println(boolean) : void [33]
    20  getstatic java.lang.System.out : java.io.PrintStream [16]
    23  new ca.polymtl.ptidej.generics.java.Util44 [22]
    26  dup
    27  invokespecial ca.polymtl.ptidej.generics.java.Util44() [24]
    30  new java.lang.String [39]
    33  dup
    34  ldc <String ""> [41]
    36  invokespecial java.lang.String(java.lang.String) [43]
    39  new java.lang.Long [46]
    42  dup
    43  lconst_1
    44  invokespecial java.lang.Long(long) [48]
    47  invokevirtual ca.polymtl.ptidej.generics.java.Util44.compare(java.lang.Object, java.lang.Object) : boolean [29]
    50  invokevirtual java.io.PrintStream.println(boolean) : void [33]
    53  return
      Line numbers:
        [pc: 0, line: 24]
        [pc: 20, line: 25]
        [pc: 53, line: 26]
      Local variable table:
        [pc: 0, pc: 54] local: args index: 0 type: java.lang.String[]

It actually makes sense that all the calls are always to methods with Object as formal parameter types, as explained in another question/answer. To conlude, the compiler always uses Object for the generated bytecodes, not matter if there is an explicity type argument (first line) or an implicit type argument but that the objects could have a common superclass different from Object.

Quotha answered 15/4, 2013 at 7:54 Comment(2)
Just to note this here, even though you link to it; the phenomenon is called "type erasure".Frontogenesis
Yeah. Basically, Java implements generics as behind-the-scenes typecasting. For example, an ArrayList<String> internally considers every element to be an Object, but automatically casts it back to String when you use it.Pegasus
P
21

The shared inherited type of String and Long is Object.

When you run this function as Util.<String>compare( the compiler expects to find two string inputs, and gives an error when it doesn't. However, running it without <String> results in the use of the closest shared inherited type - in this case, Object.

Thus, when compare accepts t1 and t2, they have been cast as Object, and the code runs fine.

To get the actual type at runtime you use the same technique that you would use with any other object: The getClass() which is inherited from the Object class.

Primm answered 8/4, 2013 at 8:52 Comment(4)
Also for the second part(way to obtain declared type of T at runtime) search for "Reified Generics" on Google. Refer gafter.blogspot.com/2006/11/reified-generics-for-java.htmlIlluviation
The getClass() method applied to, say, t1, will return String because it is the runtime type, won't it?Berylberyle
While doing more research, I stumbled upon this interesting Blog entry: super type tokens.Berylberyle
You can also use instanceof, but that's slower. However, from within compare the strings have been anonymized - the only way to read them is to read the class string and interpret it (at least as far as I know).Primm
Q
11

The answer seems to go beyond @Telthien and @newacct' answers. I was curious to "see" for myself the difference between:

System.out.println(Util.<String>compare("a", "b"));

with explicity typing, and:

System.out.println(Util.compare(new String(""), new Long(1)));

with implicit typing.

I performed several experiments, using variations on these two previous lines. These experiments show that, short of using the anonymous/local class trick, the compiler does check the types during compilation but the generated bytecodes only refer to Object, even in the case of the first line.

The following piece of code shows that typecasts can be performed safely all the way to Object even in the case of the explicity type argument <String>.

public final class Example44 {
    public static void main(final String[] args) {
        System.out.println(new Util44<String>().compare("a", "b"));
        System.out.println(new Util44().compare(new String(""), new Long(1)));
    }
}

final class Util44<T> {
    private T aT;
    public boolean compare(T t1, T t2) {
        System.out.println(this.aT);
        // I was expecting the second and third assignments to fail
        // with the first invocation because T is explicitly a String
        // and then to work with the second invocation because I use
        // a raw type and the compiler must infer a common type for T.
        // Actually, all these assignments succeed with both invocation. 
        this.aT = (T) new String("z");
        this.aT = (T) new Long(0);
        this.aT = (T) new Object();
        return t1.equals(t2);
    }
}

The bytecodes of the main method look like:

  // Method descriptor #15 ([Ljava/lang/String;)V
  // Stack: 7, Locals: 1
  public static void main(java.lang.String[] args);
     0  getstatic java.lang.System.out : java.io.PrintStream [16]
     3  new ca.polymtl.ptidej.generics.java.Util44 [22]
     6  dup
     7  invokespecial ca.polymtl.ptidej.generics.java.Util44() [24]
    10  ldc <String "a"> [25]
    12  ldc <String "b"> [27]
    14  invokevirtual ca.polymtl.ptidej.generics.java.Util44.compare(java.lang.Object, java.lang.Object) : boolean [29]
    17  invokevirtual java.io.PrintStream.println(boolean) : void [33]
    20  getstatic java.lang.System.out : java.io.PrintStream [16]
    23  new ca.polymtl.ptidej.generics.java.Util44 [22]
    26  dup
    27  invokespecial ca.polymtl.ptidej.generics.java.Util44() [24]
    30  new java.lang.String [39]
    33  dup
    34  ldc <String ""> [41]
    36  invokespecial java.lang.String(java.lang.String) [43]
    39  new java.lang.Long [46]
    42  dup
    43  lconst_1
    44  invokespecial java.lang.Long(long) [48]
    47  invokevirtual ca.polymtl.ptidej.generics.java.Util44.compare(java.lang.Object, java.lang.Object) : boolean [29]
    50  invokevirtual java.io.PrintStream.println(boolean) : void [33]
    53  return
      Line numbers:
        [pc: 0, line: 24]
        [pc: 20, line: 25]
        [pc: 53, line: 26]
      Local variable table:
        [pc: 0, pc: 54] local: args index: 0 type: java.lang.String[]

It actually makes sense that all the calls are always to methods with Object as formal parameter types, as explained in another question/answer. To conlude, the compiler always uses Object for the generated bytecodes, not matter if there is an explicity type argument (first line) or an implicit type argument but that the objects could have a common superclass different from Object.

Quotha answered 15/4, 2013 at 7:54 Comment(2)
Just to note this here, even though you link to it; the phenomenon is called "type erasure".Frontogenesis
Yeah. Basically, Java implements generics as behind-the-scenes typecasting. For example, an ArrayList<String> internally considers every element to be an Object, but automatically casts it back to String when you use it.Pegasus
P
2

Yes, Object is a choice for T that will allow it to compile. Conceptually, the compiler infers a type for T. What it particularly infers doesn't matter -- as long as it can infer that some type will work for T, then it compiles. It doesn't matter what that inferred type is, since it has no effect on the compiled code.

Panicle answered 8/4, 2013 at 20:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.