Porque é que List não um sub-tipo de List ?

StackOverflow https://stackoverflow.com/questions/1240316

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?

Foi útil?

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 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top