T не означает Integer, оно должно быть допустимым для Integer или любого класса, который происходит от него.Допустим, что StrangeInteger расширяется от Integer и заменяет T на StrangeInteger:
void passVal (StrangeInteger t) {
Integer number = 5;
t = (StrangeInteger) number;
}
Он пытается присвоить переменную Integer переменной StrangeInteger, вы не можете сделать это, если число не было StrangeInteger или производным классом впервое место.Фактически, ваш код должен (концептуально) генерировать исключение во время выполнения, если t не является целым числом, но на самом деле он не будет этого делать в этом случае из-за стирания типа (см. Редактирование 2).
Ситуация аналогичнаto:
Object obj = "Hello"
String t = (String)obj; // this will not fail, but requires a cast
Object obj2 = getDBConnection();
String t2 = (String)obj2; // this will fail at runtime
Edit: Integer действительно финальный, поэтому T может быть только Integer, но возможно, что компилятор не проверяет, является ли верхняя граница конечной, в конце концов, для верхнего нет никакого смысладолжен быть окончательным, поэтому допущение того, что этот особый случай добавляет сложности для очень небольшого реального выигрыша.
Редактировать 2: английский не мой родной язык, поэтому я могу быть не совсем понятен.
Я думаю, что выборются с разницей между использованием универсального типа с верхней границей и использованием только верхней границы в качестве типа.Идея обобщений (из C ++ и других языков) состоит в том, что код должен быть действительным, если вы замените T любым типом T, разрешенным границами, поэтому вы не можете вызвать метод, который не определен в верхней границе.
Будучи верхней границей T, A также означает, что вы всегда можете присвоить объект T переменной A.Вы не можете безопасно назначить объект A переменной T (если только A == T), вы можете сделать это, только если A была нижней границей T, а не верхней.Также см. Понимание верхней и нижней границ?в Java Generics.
Java использует стирание типа для реализации обобщений, есть несколько преимуществ , но это вызывает некоторые ограничения, которые не всегда очевидны.Из-за стирания типа в этом случае само приведение не будет неудачным, после проверки типа T заменяется верхней границей на шаге стирания типа, то есть число (T) заменяется числом (целое число).Исключение по-прежнему будет происходить, если вы сделаете все, что вызывает приведение к подклассу, например, если вы вернете измененный t и присвойте результат переменной подкласса, потому что компилятор добавляет неявное приведение.
Это также не удастся, если вы вызовете метод, который зависит от подкласса T, который является распространенным шаблоном, например:
List<Person> persons = ...
Comparator<Person> nameComparator = (p1,p2) -> p1.getName().compareTo(p2.getName())
java.util.Collections.sort(persons,nameComparator);
В следующем примере кода показано поведение в несколькихслучаев.Я использовал System.err для всего, чтобы избежать проблем с порядком в выводе.
import java.util.function.Consumer;
import java.util.function.Function;
class A {
@Override public String toString(){ return "A";}
public String foo(){ return "foo";}
}
class B extends A {
@Override public String toString(){ return "B";}
public String bar(){ return "bar";}
}
class C extends B { }
public class Main {
public static void main(String[] args) {
Function<A,String> funA = a -> a.foo();
Function<B,String> funB = b -> b.bar();
Function<C,String> funC = c -> c.bar();
Consumer<B> ignoreArgument = b -> {
System.err.println(" Consumer called");
};
B b = new B();
System.err.println("* voidTest *");
voidTest(b);
System.err.println("------------");
System.err.println("* returnTest *");
returnTest(b);
System.err.println("returnTest without using result did not throw");
System.err.println("------------");
try {
System.err.println("Returned " + returnTest(b).toString());
System.err.println("returnTest: invoking method on result did not throw");
}
catch(Exception ex) {
System.err.println("returnTest: invoking method on result threw");
ex.printStackTrace();
}
System.err.println("------------");
B b2 = null;
try {
b2 = returnTest(b);
System.err.println("returnTest: assigning result to a B variable did not throw");
}
catch(Exception ex) {
System.err.println("returnTest: assigning result to a B variable threw");
ex.printStackTrace();
}
System.err.println("------------");
System.err.println("* functionTest funA *");
functionTest(b, funA);
System.err.println("------------");
System.err.println("* functionTest funB * ");
functionTest(b, funB);
System.err.println("------------");
System.err.println("* consumerTest *");
consumerTest(b, ignoreArgument);
// The following won't work because C is not B or a superclass of B
// Compiler error functionTest(T, Function<? super T,String>) is not applicable for the arguments (B, Function<C,String>)
// functionTest(b, funC);
}
private static <T extends A> void voidTest(T t){
System.err.println(" Before: " + t.toString());
t = (T)new A(); // warning Type safety: Unchecked cast from A to T
System.err.println(" After: " + t.toString());
}
private static <T extends A> T returnTest(T t){
System.err.println(" Before: " + t.toString());
t = (T)new A();
System.err.println(" After: " + t.toString());
return t;
}
private static <T extends A> void functionTest(T t, Function<? super T,String> fun) {
System.err.println(" fun Before: " + fun.apply(t));
t = (T)new A();
try {
System.err.println(" fun After: " + fun.apply(t));
}
catch(Exception ex) {
System.err.println(" fun After: threw");
ex.printStackTrace();
}
}
private static <T extends A> void consumerTest(T t, Consumer<? super T> c) {
System.err.print(" Before: ");
c.accept(t);
t = (T)new A();
try {
System.err.println(" After: ");
c.accept(t);
System.err.println(" c.accept(t) After: worked");
}
catch(Exception ex) {
System.err.println(" c.accept(t) After: threw");
ex.printStackTrace();
}
}
}
Вывод в OpenJDK 11:
* voidTest *
Before: B
After: A
------------
* returnTest *
Before: B
After: A
returnTest without using result did not throw
------------
Before: B
After: A
returnTest: invoking method on result threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.main(Main.java:35)
------------
Before: B
After: A
returnTest: assigning result to a B variable threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.main(Main.java:45)
------------
* functionTest funA *
fun Before: foo
fun After: foo
------------
* functionTest funB *
fun Before: bar
fun After: threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.functionTest(Main.java:83)
at Main.main(Main.java:57)
------------
* consumerTest *
Before: Consumer called
After:
c.accept(t) After: threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.consumerTest(Main.java:97)
at Main.main(Main.java:60)
Я не совсем уверен в результате.Вызвано исключение, если результат полностью игнорируется, возможно, в этом случае язык не требуется для приведения или компилятор удалил его.Вызов метода, определенного в верхней границе результата, все еще вызывает исключение.Наконец, наблюдение от customerTest заключается в том, что для вызова ClassCastException не нужно было вызывать bar (), ему просто нужно было передать t потребителю, который ожидает аргумент B.