Porque é que List não um sub-tipo de List
-
11-09-2019 - |
Pergunta
public void wahey(List<Object> list) {}
wahey(new LinkedList<Number>());
A chamada para o método não irá escrever-check. Eu não posso nem converter o parâmetro da seguinte maneira:
wahey((List<Object>) new LinkedList<Number>());
De minha pesquisa, eu recolhi que a razão para não permitir que este é tipo de segurança. Se fomos autorizados a fazer o descrito acima, então nós poderíamos ter o seguinte:
List<Double> ld;
wahey(ld);
Dentro do Wahey método, poderíamos acrescentar algumas strings à lista de entrada (como o parâmetro mantém uma referência List<Object>
). Agora, após a chamada de método, ld refere-se a uma lista com um tipo List<Double>
, mas a lista atual contém alguns objetos String!
Isto parece diferente da forma normal Java funciona sem genéricos. Por exemplo:
Object o;
Double d;
String s;
o = s;
d = (Double) o;
O que estamos fazendo aqui é essencialmente a mesma coisa, exceto que isso vai passar cheques em tempo de compilação e só falhar em tempo de execução. A versão com listas não irá compilar.
Isto leva-me a crer isso é puramente uma decisão de projeto com relação às restrições de tipo sobre os genéricos. Eu estava esperando para obter alguns comentários sobre esta decisão?
Solução
O que você está fazendo no exemplo "sem genéricos" é um elenco, o que deixa claro que você está fazendo algo tipo inseguro. O equivalente com genéricos seria:
Object o;
List<Double> d;
String s;
o = s;
d.add((Double) o);
que se comporta da mesma maneira (compila, mas falhar em tempo de execução). A razão para não permitir que o comportamento que você está perguntando é porque permitiria implícita ??em> acções de tipo inseguro, que são muito mais difíceis de aviso no código. Por exemplo:
public void Foo(List<Object> list, Object obj) {
list.add(obj);
}
Isso parece perfeitamente bem e tipo seguro até que você chamá-lo assim:
List<Double> list_d;
String s;
Foo(list_d, s);
O que também parece tipo seguro, porque você como o chamador não necessariamente sabem o Foo vai fazer com seus parâmetros.
Então, nesse caso você tem dois bits aparentemente segura de tipo de código, que juntos acabam sendo tipo inseguro. Isso é ruim, porque ele está escondido e, portanto, difícil de evitar e mais difícil de depuração.
Outras dicas
Considere se fosse ...
List<Integer> nums = new ArrayList<Integer>();
List<Object> objs = nums
objs.add("Oh no!");
int x = nums.get(0); //throws ClassCastException
Você seria capaz de adicionar qualquer coisa do tipo pai para a lista, que pode não ser o que foi anteriormente declarado como, que, como o exemplo acima demonstra, faz com que todos os tipos de problemas. Assim, não é permitido.
Não são subtipos do outro devido como genéricos trabalho. O que você quer é declarar a sua função como esta:
public void wahey(List<?> list) {}
Em seguida, ele vai aceitar uma lista de tudo que se estende objeto. Você também pode fazer:
public void wahey(List<? extends Number> list) {}
Isso permitirá que você tomar em Listas de algo que é uma subclasse de Number.
Eu recomendo que você pegar uma cópia de "Java Generics e Coleções", de Maurice Naftalin & Philip Wadler.
Existem essencialmente duas dimensões de abstração aqui, a lista de abstração e a abstração de seus conteúdos. É perfeitamente possível variar ao longo da lista de abstração - para dizer, por exemplo, que é um LinkedList ou um ArrayList - mas não é bom para restringir ainda mais o conteúdo, a dizer: Este (lista que contém objetos) é um (lista encadeada que detém apenas números). Porque qualquer referência que sabe como (lista que contém objetos) compreende, pelo contrato de seu tipo, que pode conter qualquer objeto.
Este é bastante diferente do que você tem feito no exemplo de código não-genéricos, onde você disse: tratar esta String como se fosse um duplo. Você está em vez tentando dizer: tratar este assunto (lista que contém apenas números) como um (lista que detém nada). E isso não acontecer, eo compilador pode detectá-lo, para que ele não deixá-lo fugir com ele.
"O que estamos fazendo aqui é essencialmente a mesma coisa, exceto que isso vai passar verificações de tempo de compilação e só não a tempo de execução. A versão com listas não compilar. "
O que você está observando faz todo o sentido quando se considera que o principal objetivo dos genéricos Java é fazer com que tipo incompatibilidades a falhar em tempo de compilação em vez de tempo de execução.
A partir java.sun.com
Os genéricos fornece uma maneira para você comunicar o tipo de uma coleção para o compilador, de modo que possa ser verificado. Uma vez que o compilador sabe o tipo de elemento de recolha, o compilador pode verificar que você tenha usado a coleta de forma consistente e lata inserir os moldes correctas sobre valores sendo retirado da coleção.
Em Java, List<S>
não é um subtipo de List<T>
quando S
é um subtipo de T
. Essa regra fornece segurança de tipos.
Vamos dizer que permitir que um List<String>
ser um subtipo de List<Object>
. Considere o seguinte exemplo:
public void foo(List<Object> objects) {
objects.add(new Integer(42));
}
List<String> strings = new ArrayList<String>();
strings.add("my string");
foo(strings); // this is not allow in java
// now strings has a string and an integer!
// what would happen if we do the following...??
String myString = strings.get(1);
Assim, forçando este fornece segurança de tipos, mas também tem uma desvantagem, é menos flexível. Considere o seguinte exemplo:
class MyCollection<T> {
public void addAll(Collection<T> otherCollection) {
...
}
}
Aqui você tem uma coleção de T
de, você quer adicionar todos os itens de outra coleção. Você não pode chamar esse método com um Collection<S>
para um subtipo S
de T
. Idealmente, isso é ok, porque você só está adicionando elementos em sua coleção, você não está modificando a coleção de parâmetros.
Para corrigir isso, Java fornece o que eles chamam de "wildcards". Wildcards são uma forma de prestação de covariância / contravariance. Agora, considere o seguinte usando wildcards:
class MyCollection<T> {
// Now we allow all types S that are a subtype of T
public void addAll(Collection<? extends T> otherCollection) {
...
otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T)
}
}
Agora, com wildcards nós permitimos covariância no tipo T e nós bloquear operações que não são do tipo seguro (por exemplo, adicionar um item na coleção). Desta forma, temos flexibilidade e tipo de segurança.