1

Every once in a while, I feel I don't know Java at all.. I recently found this strange behavior of Java type casting:

public static void main(String[] args) {
    String res = get();
    System.out.println(res);
}

public static <T> T get() {
    Object longObj = Long.valueOf("0");
    T casted = (T) longObj;
    System.out.println("longObj=" + longObj.getClass());
    System.out.println("casted=" + casted.getClass()); // <-- Why the type of "casted" is Long instead of String???
    return casted;
}

The output is:

longObj=class java.lang.Long
casted=class java.lang.Long
Exception in thread "main" java.lang.ClassCastException: java.lang.Long 
cannot be cast to java.lang.String
    at com.apple.geo.coreloc.Test.main(Test.java:5)

My confusion is: Why variable casted has type of Long? Shouldn't it be casted to type T, that is String?

Following is why I thought it should be String:

  • At runtime, String res = get() hints get() method to return a String
  • T casted = (T) longObj; should try to type cast Long to a String (I expect an exception here, but there isn't..)
SexyNerd
  • 934
  • 2
  • 11
  • 18
  • 3
    An object won't just magically change it's type when it's cast to another type. When you are casting an object, you are basically saying to the type system that "I know better than you". After the cast, the object will still be of type `Long`, but the type system will think that it's of type `T`. – marstran Oct 23 '17 at 17:27

4 Answers4

3

Generics are compile time checks. They are not safeguards for writing a bad cast.

You tried to do (String) longIbj; where longObj was a long. That would have given you a runtime ClassCastException.

But, because it isn't a compile time error within the context of T get() the T get() is compiled correctly. Due to "type erasure" the T is promoted into the highest constrained type. You didn't constrain the upper bounds on the type, so your compiler processed code that roughly looks like:

public static Object get() {
    Object longObj = Long.valueOf("0");
    Object casted = (Object) longObj;
    System.out.println("longObj=" + longObj.getClass());
    System.out.println("casted=" + casted.getClass()); // <-- Why the type of "casted" is Long instead of String???
    return casted;
}

Since <T> is an Object the two lines of code populating longObj and casted are roughly identical. In any case, the polymorphic call to the object reference by either name will still resolve to the object's .getClass() method, which will still return java.lang.Long.

Casting in Java does not create new objects. If you wanted a different object, you need to new a different object and populate it with the relevant information. Casting in java will only have you access the class through the method names of the cast type. So you might lose getLong() in a Long cast to an Object but it is still there (if you call it through a variable of type Long.

Edwin Buck
  • 64,804
  • 7
  • 90
  • 127
2

Casting is not the same as conversion.

Casting is a declaration that your object IS of the target type. It doesn't change the class of the current object.

Conversion is the process whereby an object of one type is recreated as an object of another type.

This is why casting should only be used when you already know the type of the object that you are casting. In all other cases, you should convert your object into an instance of the target type.

Silas Reinagel
  • 3,987
  • 1
  • 19
  • 28
  • Okay, I feel it's same to say that "Type casting into a generic type is usually a bad practice", right? – SexyNerd Oct 23 '17 at 19:11
  • That wouldn't be quite the same. When you are doing serialization, working with reflection, or reading external data files, it's relatively common to cast the value to a generic type. In those scenarios, it seems like a good practice. – Silas Reinagel Oct 23 '17 at 20:08
1

In Java, generic types work only at compile time, but it has (almost) no information about specific target generic type because of so called "type erasure" (see here and here).

If you decompile your code, you'll see this:

public static void main(String[] args) {
    String res = (String)get();
    System.out.println(res);
}

public static <T> T get() {
    Object longObj = Long.valueOf("123");
    System.out.println("longObj=" + longObj.getClass());
    System.out.println("casted=" + longObj.getClass());
    return longObj;
}

Here all is clear:

  • in main, it is just cast operation
  • in get, variable with "casted" value was removed entirely
cybersoft
  • 1,383
  • 13
  • 25
1

Java generics are different that C++ templates. For Java generic methods, there is one and only one version of the method. In C++, the code for templated methods are generated as needed for the specific type so that each different parameterized type gets it's own version of the method.

To get the error you expect, you have to help Java out by telling it the generic type.

class A {
  public static void main(String[] args) {
    String res = get(String.class);
    System.out.println(res);
  }

  public static <T> T get( Class<T> klass) {

    Object longObj = Long.valueOf("0");
    T casted = klass.cast(longObj);
    System.out.println("longObj=" + longObj.getClass());
    System.out.println("casted=" + casted.getClass());
    return casted;
  }
}

Running this does give a cast exception:

Exception in thread "main" java.lang.ClassCastException: Cannot cast java.lang.Long to java.lang.String
    at java.lang.Class.cast(Class.java:3369)
    at A.get(A.java:10)
    at A.main(A.java:4)
pcarter
  • 1,362
  • 14
  • 20