Este é um exemplo do mundo real de uma API de biblioteca de terceiros, mas simplificado.
Compilado com o Oracle JDK 8u72
Considere estes dois métodos:
<X extends CharSequence> X getCharSequence() {
return (X) "hello";
}
<X extends String> X getString() {
return (X) "hello";
}
Ambos relatam um aviso de "elenco não verificado" - eu entendo o porquê. O que me deixa perplexo é por que posso ligar
Integer x = getCharSequence();
e compila? O compilador deve saber que Integer
não implementa CharSequence
. A chamada para
Integer y = getString();
dá um erro (como esperado)
incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String
Alguém pode explicar por que esse comportamento seria considerado válido? Como isso seria útil?
O cliente não sabe que esta chamada é insegura - o código do cliente é compilado sem aviso. Por que a compilação não alertou sobre isso / emitiu um erro?
Além disso, como é diferente deste exemplo:
<X extends CharSequence> void doCharSequence(List<X> l) {
}
List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles
List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error
Tentar passar List<Integer>
dá um erro, como esperado:
method doCharSequence in class generic.GenericTest cannot be applied to given types; required: java.util.List<X> found: java.util.List<java.lang.Integer> reason: inference variable X has incompatible bounds equality constraints: java.lang.Integer upper bounds: java.lang.CharSequence
Se isso é relatado como um erro, por que Integer x = getCharSequence();
não?
Integer x = getCharSequence();
vai compilar, mas lançando sobre o RHSInteger x = (Integer) getCharSequence();
falha compilaçãoRespostas:
CharSequence
é uminterface
. Portanto, mesmoSomeClass
que não implementeCharSequence
, seria perfeitamente possível criar uma classePortanto você pode escrever
porque o tipo inferido
X
é o tipo de interseçãoSomeClass & CharSequence
.Isso é um pouco estranho,
Integer
porqueInteger
é final, masfinal
não desempenha nenhum papel nessas regras. Por exemplo, você pode escreverPor outro lado,
String
não é uminterface
, portanto, seria impossível estenderSomeClass
para obter um subtipo deString
, porque o java não suporta herança múltipla para classes.Com o
List
exemplo, você precisa lembrar que os genéricos não são covariantes nem contravariantes. Isso significa que seX
é um subtipo deY
,List<X>
não é um subtipo nem um supertipo deList<Y>
. ComoInteger
não implementaCharSequence
, você não pode usarList<Integer>
no seudoCharSequence
método.Você pode, no entanto, fazer isso para compilar
Se você tem um método que retorna um
List<T>
como este:você pode fazer
Novamente, isso ocorre porque o tipo inferido é
Integer & CharSequence
e esse é um subtipo deInteger
.Os tipos de interseção ocorrem implicitamente quando você especifica vários limites (por exemplo
<T extends SomeClass & CharSequence>
).Para obter mais informações, aqui está a parte do JLS, na qual explica como os limites de tipo funcionam. Você pode incluir várias interfaces, por exemplo
mas apenas o primeiro limite pode ser uma não interface.
fonte
&
na definição genérica. 1<T extends String & List & Comparator>
está ok, mas<T extends String & Integer>
não está, porqueInteger
não é uma interface.Collections.emptyList()
bem comoOptional.empty()
. Eles retornam implementações de uma interface genérica, mas não armazenam nada.final
em tempo de compilação estaráfinal
em tempo de execução.getCharSequence()
promete retornar o queX
o chamador precisa, que inclui retornar um tipo de extensãoInteger
e implementação,CharSequence
se o chamador precisar e, de acordo com essa promessa, é correto permitir atribuir o resultadoInteger
. É o métodogetCharSequence()
que está quebrado, pois não cumpre sua promessa, mas isso não é culpa do compilador.O tipo que é deduzido pelo seu compilador antes da atribuição para
X
éInteger & CharSequence
. Esse tipo parece estranho, porqueInteger
é final, mas é um tipo perfeitamente válido em Java. Em seguida, é convertido emInteger
, o que é perfeitamente aceitável.Há exatamente um valor possível para o
Integer & CharSequence
tipo:null
. Com a seguinte implementação:A seguinte atribuição funcionará:
Devido a esse valor possível, não há razão para que a atribuição esteja errada, mesmo que seja obviamente inútil. Um aviso seria útil.
O verdadeiro problema é a API, não o site de chamada
De fato, escrevi recentemente sobre esse anti-padrão de design de API . Você (quase) nunca deve projetar um método genérico para retornar tipos arbitrários, porque você (quase) nunca pode garantir que o tipo inferido será entregue. Uma exceção são métodos como
Collections.emptyList()
, no caso em que o vazio da lista (e apagamento de tipo genérico) é a razão pela qual qualquer inferência para<T>
funcionará:fonte