10

I've got the following 3 tests. The first two work, the last one doesn't. My motivation for asking this question is that I'd like to be able to cast object A so that it has the same class as object B, when A is known to be a subtype of B.

  @Test
  public void testWorks() {
    Object bar = "foobar";
    String blah = (String) bar;
    System.out.println(blah); // Outputs foobar
  }

  @Test
  public void testAlsoWorks() {
    Object bar = "helloworld";
    String blah = String.class.cast(bar);
    System.out.println(blah);  // Outputs helloworld
  }

  @Test
  public void testfails() {
    Object bar = "foobar";
    String thetype = "hello";
    Class stringclass = thetype.getClass();
    String blah = stringclass.cast(bar); // Compiler error: incompatible types: Object cannot be converted to String                                                                                                            
    System.out.println(blah);
  }

Can anyone explain why the last case fails when the first two succeed, and why this is the case? And what would be a better approach to achieve this?

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
user3170530
  • 416
  • 3
  • 13
  • I'd assume because .class is evaluated at compile time, but .getClass() is evaluated at runtime. – odlund Oct 08 '14 at 17:40
  • 6
    The compiler should have warned you about the use of the raw type `Class` instead of the parameterized type `Class<>`, which would be your hint that something is wrong with that declaration. – Alex Oct 08 '14 at 17:53

2 Answers2

16

You need to specify the type parameter of Class:

Class<String> stringclass = (Class<String>) thetype.getClass();

or

Class<? extends String> stringclass = thetype.getClass();

java.lang.Class.cast(Object obj) casts an object to the class or interface represented by this Class object.

Without specifying the type, you are not telling the compiler what class or interface is represented by the Class instance stringclass.

When you call String.class, the Class object is implicitly parametrized with the String type.

M A
  • 71,713
  • 13
  • 134
  • 174
11

The Java Language Specification states

The type of C.class, where C is the name of a class, interface, or array type (§4.3), is Class<C>.

So the type of the expression

String.class

is Class<String>. Class is a generic class where the method cast uses the generic type variable in its return type. So the result of

String.class.cast(bar);

is an expression of type T, where T has been bound to String in this invocation.

The return type of Object#getClass() is Class<? extends T> where T is the erased type of the expression it's invoked on.

In this case, that is

Class<? extends String>

However, you are assigning it to a raw reference.

Class stringclass = thetype.getClass();

Since the variable stringclass is raw, any uses of its methods that depend on the generic type variable are erased.

So Class#cast(Object) now has a return type of Object which is not assignable to a String variable.

Had you done

Class<? extends String> stringclass = thetype.getClass();

you'd be fine.

Community
  • 1
  • 1
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724