Ambiguous behaviour in casting

T doesn't exist at runtime. It resolves to the lower bound of the constraint. In this case, there are none, so it resolves to Object. Everything can be cast to Object, so no class cast exception.

If you were to do change the constraint to this

private static <V,T extends Map<?,?>> T castToType(V value, T type){
    return (T) value;
}

then the cast to T becomes a cast to the lower bound Map, which obviously Integer is not, and you get the class cast exception you're expecting.


Also, when I break the same statement into two, something like

Object o = castToType(10,new HashMap<Integer,Integer>());
System.out.println(o.getClass());

It is not throwing any error

castToType(10,new HashMap<Integer,Integer>()).getClass() 

This throws a class cast exception because it statically links to the method HashMap::getClass (not Object::getClass) since the signature says to expect HashMap as a return value. This necessitates an implicit cast to HashMap which fails because castToType returns an Integer at runtime.

When you use this first

Object o = castToType(10,new HashMap<Integer,Integer>());

you are now statically linking against Object::getClass which is fine regardless of what's actually returned.

The "unsplit" version is equivalent to this

final HashMap<Integer, Integer> map = castToType(10, new HashMap<>());
System.out.println(map.getClass());

which hopefully demonstrates the difference


You could see the differences using javap tool.

The compiling process by default makes code optimizations that changes the Generic types into the primitive ones

First code:

public class ObjectUtility {

  public static void main(String[] args) {
    System.out.println(castToType(10,new java.util.HashMap<Integer,Integer>()));
  }

  private static <V,T> T castToType(V value, T type){
    return (T) value;
  }

}

Real PseudoCode:

Compiled from "ObjectUtility.java"
public class ObjectUtility {
  public ObjectUtility();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: bipush        10
       5: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       8: new           #4                  // class java/util/HashMap
      11: dup
      12: invokespecial #5                  // Method java/util/HashMap."<init>":()V
      15: invokestatic  #6                  // Method castToType:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      18: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      21: return
    LineNumberTable:
      line 4: 0
      line 5: 21

  private static <V, T> T castToType(V, T);
    descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    Code:
       0: aload_0
       1: areturn
    LineNumberTable:
      line 8: 0
}

The calls of the Generic types are changed to Object and an Integer.valueOf is added on the System out print.

Second code:

public class ObjectUtility {

  public static void main(String[] args) {
    System.out.println(castToType(10,new java.util.HashMap<Integer,Integer>()).getClass());
  }

  private static <V,T> T castToType(V value, T type){
    return (T) value;
  }

}

Real Pseudo Code:

Compiled from "ObjectUtility.java"
public class ObjectUtility {
  public ObjectUtility();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: bipush        10
       5: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       8: new           #4                  // class java/util/HashMap
      11: dup
      12: invokespecial #5                  // Method java/util/HashMap."<init>":()V
      15: invokestatic  #6                  // Method castToType:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      18: checkcast     #4                  // class java/util/HashMap
      21: invokevirtual #7                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      24: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      27: return
    LineNumberTable:
      line 4: 0
      line 5: 27

  private static <V, T> T castToType(V, T);
    descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    Code:
       0: aload_0
       1: areturn
    LineNumberTable:
      line 8: 0
}

The checkcast is invoqued over HashMap but the signature is changed to Object and the returnt is the value as int without the cast inside castToType. The "int" primitive type causes an invalid cast

Third Code:

public class ObjectUtility {

  public static void main(String[] args) {
    Object o = castToType(10,new java.util.HashMap<Integer,Integer>());
    System.out.println(o.getClass());
  }

  private static <V,T> T castToType(V value, T type){
    return (T) value;
  }

}

Real Pseudo Code:

Compiled from "ObjectUtility.java"
public class ObjectUtility {
  public ObjectUtility();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    Code:
       0: bipush        10
       2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       5: new           #3                  // class java/util/HashMap
       8: dup
       9: invokespecial #4                  // Method java/util/HashMap."<init>":()V
      12: invokestatic  #5                  // Method castToType:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
      15: astore_1
      16: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: aload_1
      20: invokevirtual #7                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      23: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      26: return
    LineNumberTable:
      line 4: 0
      line 5: 16
      line 6: 26

  private static <V, T> T castToType(V, T);
    descriptor: (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    Code:
       0: aload_0
       1: areturn
    LineNumberTable:
      line 9: 0
}

At this case the method is similar to the first one. castToType returns the first parameter without change.

As you can see the java compiler mades some "performance" changes that could affect in some cases. The Generics are an "invention" of the source code that are finally converted to the real type required in any case.