Почему кастинг основан на Class.class, но не getClass?

У меня есть следующие 3 теста. Первые две работы, а последняя - нет. Моей мотивацией для этого вопроса является то, что я хотел бы использовать объект A, чтобы он имел тот же класс, что и объект B, когда A известен подтипом 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);
  }

Может ли кто-нибудь объяснить, почему последний случай терпит неудачу, когда первые два успеха, и почему это так? И что было бы лучшим подходом к достижению этого?

Ответ 1

Вам нужно указать параметр типа Class:

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

или

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

java.lang.Class.cast(Object obj) задает объект классу или интерфейсу, представленному этим объектом класса.

Без указания типа вы не сообщаете компилятору, какой класс или интерфейс представлен экземпляром Class stringclass.

Когда вы вызываете String.class, объект Class неявно параметризуется с типом String.

Ответ 2

Спецификация языка Java содержит

Тип C.class, где C - это имя класса, интерфейса или тип массива (§4.3), Class<C>.

Итак, тип выражения

String.class

- Class<String>. Class - это общий класс, в котором метод cast использует переменную типа generic в возвращаемом типе. Таким образом, результат

String.class.cast(bar);

- выражение типа T, где T привязано к String в этом вызове.

Возвращаемый тип Object#getClass() является Class<? extends T>, где T - это стертый тип выражения, на которое он вызывается.

В этом случае это

Class<? extends String>

Однако вы назначаете его исходной ссылке.

Class stringclass = thetype.getClass();

Так как переменная stringclass raw, любые применения ее методов, зависящие от общей переменной типа, стираются.

Итак, Class#cast(Object) теперь имеет тип возврата Object, который не может быть назначен переменной String.

Если бы вы сделали

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

вам будет хорошо.