Почему это использование Generics не генерирует исключение времени выполнения или компиляции?
У меня есть метод в классе, который имеет тип возвращаемого значения, указанный с использованием общего.
public class SomeMain {
public static void main(String[] args) {
Foo<Integer> foo = new Foo<Integer>();
System.out.println(foo.getFoo()); // Works, prints out "Foo"
}
public static class Foo<E> {
public E getFoo() {
return (E) "Foo";
}
}
}
С типичным типом возврата я предположил, что возврат в приведенном выше примере будет оцениваться с помощью:
return (Integer) "Foo"; // Inconvertible types !!!
Вместо этого String
возвращается и печатается правильно.
Я получаю ошибку компиляции, если я изменяю вызов:
String fooString = foo.getFoo(); // Compile error, incompatible types found
System.out.println(fooString);
Что мне не хватает, чтобы помочь мне понять, что происходит здесь, и почему исходная версия не привела к ошибке компиляции.
Ответы
Ответ 1
Это связано с тем, что разрешение перегрузки разрешало ваш вызов println
для println(Object)
, поскольку нет println(Integer)
.
Имейте в виду, что дженерики Java стираются во время выполнения. И удары, подобные (E) "Foo"
, удаляются и перемещаются на сайт вызова. Иногда это не обязательно, поэтому вещи присылаются в нужный тип только тогда, когда это необходимо.
Другими словами, никакие броски не выполняются внутри getFoo
. Спецификация языка поддерживает это:
Раздел 5.5.2 Проверенные броски и непроверенные роли
После стирания getFoo
возвращает Object
. И это передается в println(Object)
, что отлично.
Если я вызову этот метод и foo.getFoo
, я получу ошибку:
static void f(Integer i) {
System.out.println(i);
}
// ...
f(foo.getFoo()); // ClassCastException
потому что на этот раз его нужно отбросить.
Ответ 2
System.out.println
не имеет перегрузки, которая принимает Integer
. Итак, это утверждение:
System.out.println(foo.getFoo());
System.out.println(Object);
,
Чтобы убедиться, что он в противном случае завершился неудачей, попробуйте:
Foo<Integer> foo = new Foo<Integer>();
Integer fooInt = foo.getFoo(); //class cast exception
Следующее не получится таким же образом:
public static void main(String[] args) throws Exception {
Foo<Integer> foo = new Foo<Integer>();
print(foo.getFoo()); //Would also fail with a class cast exception
}
static void print(Integer in) {
System.out.println(in);
}
И это приводит к сбою компиляции по очевидным причинам:
String fooString = foo.getFoo(); //can't work
foo
- Foo<Integer>
, а foo.getFoo()
возвращает Integer
и компилятор может выбрать это.
Ответ 3
Я хотел бы добавить, что во время процесса стирания типа компилятор Java заменяет параметр E
неограниченного типа E
на Object
, поэтому класс Foo
фактически скомпилирован в:
public static class Foo {
public Object getFoo() {
return "Foo";
}
}
Поэтому допустим следующий код (бросок не нужен):
Object obj = foo.getFoo();
System.out.println(obj);
В то же время следующий фрагмент кода создает ошибку времени компиляции, как и ожидалось:
Foo<Integer> foo = new Foo<Integer>();
String fooString = foo.getFoo(); // you're trying to trick the compiler (unsuccessfully)
^
incompatible types: Integer can not be converted to String
И что основная ответственность за дженерики - проверка времени компиляции.
Но есть и другая сторона истории - казни времени. Например, если вы пишете:
Integer value = foo.getFoo();
вы получаете ClassCastException
во время выполнения (компилятор Java вставляет checkcast
которая проверяет, может ли результат foo.getFoo()
быть foo.getFoo()
в Integer
).