Java exige a instanciação de um tipo de parâmetro limitada à sua classe limite superior de ter um elenco, por exemplo:
<T extends Integer> void passVal (T t) {
Integer number = 5;
t = (T) number; // Without cast a compile error is issued
}
T
já está restrito a Integer
ou uma de suas subclasses (eu sei, não há qualquer) mediante declaração, e parece-me que qualquer parâmetro de tipo limitada só pode ser instanciado para sua classe limite superior ou uma de suas subclasses, por isso, porque é que o elenco precisava?
Além disso, eu sei que este não é o caso se eu instanciar o parâmetro através de fora invocação deste método, portanto, não oferecer isso como uma resposta; a questão é específico para parâmetros de tipo limitadas a ser instanciada dentro das suas declarando classe / métodos.
T não significa inteiro, ele deve ser válido por inteiro ou qualquer classe que se estende a partir dele. Vamos dizer que StrangeInteger se estende de Integer e substituí-T com StrangeInteger:
void passVal (StrangeInteger t) {
Integer number = 5;
t = (StrangeInteger) number;
}
Ele tenta atribuir uma variável Integer a uma variável StrangeInteger, você não pode fazer isso a menos que o número foi um StrangeInteger ou uma classe derivada, em primeiro lugar. Na verdade seu código deve (conceitualmente) lançar uma exceção em tempo de execução, a menos que t é um inteiro, mas não vai realmente fazer isso neste caso devido ao tipo de apagamento (ver Editar 2).
A situação é semelhante a:
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 é realmente final, então T só pode ser Integer, mas é possível que o compilador não está verificando se o limite superior é final, depois de tudo o que faz pouco sentido para um limite superior para ser final, permitindo assim que o caso especial adiciona complexidade para muito pouco ganho real.
Editar 2: TL; DR: Você está confundindo limites superiores e inferiores, mas há ressalvas com tipo de rasura. Isso vai quebrar logo que você faça qualquer coisa que vale a pena fazer usando os genéricos em vez de apenas usando o tipo de base.
Inglês não é a minha primeira língua para que eu possa não ser totalmente claro.
Eu acho que você está lutando com a diferença entre usar um tipo genérico com um limite superior e apenas usando o limite superior como tipo. A idéia de genéricos (de C ++ e outras linguagens) é que o código deve ser válido se você substituir T por qualquer tipo T permitido pelos limites, então você não pode chamar qualquer método que não está definido no limite superior.
A ser um limite superior em T também significa que você pode sempre atribuir um objeto T a uma variável A. Não é possível atribuir com segurança um Um objeto a uma variável T (a menos que A == T), você só pode fazer isso se Um era um ligado na T inferior, não um limite superior. Ver também Compreender limites superiores e inferiores em? em Java Generics.
Java usa tipo de apagamento para implementar os genéricos, existem algumas vantagens , mas que faz com que algumas limitações que nem sempre são aparentes. Por causa do tipo de rasura neste caso, o próprio molde não falhará, após verificação de tipo T é substituído pelo limite superior na etapa de apagamento tipo, ou seja (T) é substituído pelo número de número (inteiro). Uma exceção ainda ocorrerá se você fizer qualquer coisa que provoca um elenco para a subclasse, por exemplo, se você retornar a alteração t e atribuir o resultado a uma variável da subclasse, porque o compilador acrescenta uma conversão implícita.
Isso também irá falhar se você chamar um método que depende de uma subclasse de T, que é um padrão comum, por exemplo:
List<Person> persons = ...
Comparator<Person> nameComparator = (p1,p2) -> p1.getName().compareTo(p2.getName())
java.util.Collections.sort(persons,nameComparator);
O seguinte exemplo de código exibe o comportamento em vários casos. Eu costumava System.err em tudo para evitar problemas de ordem na saída.
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();
}
}
}
A saída sob 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)
Eu não sou inteiramente certo resultTest porque não causou uma exceção se o resultado é completamente ignorado, talvez um elenco não é exigido pela linguagem, nesse caso, ou o compilador removido. Chamar um método definidos no limite superior do resultado ainda causou uma excepção. Finalmente, uma observação de consumerTest é que ele não tinha necessidade de barramento de chamadas () para causar uma ClassCastException, ele só precisava passar t para o consumidor que espera um argumento B.