Por que as matrizes são covariantes, mas os genéricos são invariantes?

De Java eficaz por Joshua Bloch,

  1. As matrizes diferem do tipo genérico de duas maneiras importantes. Os primeiros arrays são covariantes. Os genéricos são invariantes.
  2. Covariant significa simplesmente que se X é um subtipo de Y, então X [] também será um sub-tipo de Y []. Matrizes são covariantes Como string é o subtipo de Object So

    String[] is subtype of Object[]

    Invariante significa simplesmente que, independentemente de X ser um subtipo de Y ou não,

      List will not be subType of List. 

Minha pergunta é por que a decisão de fazer matrizes covariant em Java? Existem outras mensagens SO, como Por que as matrizes são invariantes, mas as listas são covariantes? , mas eles parecem estar focados em Scala e eu não sou capaz de seguir.

    Via wikipedia :

    Versões anteriores de Java e C # não incluíam genéricos (também conhecido como polymorphism paramétrico).

    Em tal cenário, fazer matrizes invariantes exclui programas polimórficos úteis. Por exemplo, considere escrever uma function para embaralhar uma matriz ou uma function que testa duas matrizes para igualdade usando o método Object.equals nos elementos. A implementação não depende do tipo exato de elemento armazenado na matriz, portanto, deve ser possível escrever uma única function que funcione em todos os tipos de matrizes. É fácil implementar funções do tipo

     boolean equalArrays (Object[] a1, Object[] a2); void shuffleArray(Object[] a); 

    No entanto, se os tipos de matriz fossem tratados como invariantes, só seria possível chamar essas funções em uma matriz exatamente do tipo Object[] . Não se pode, por exemplo, embaralhar um array de strings.

    Portanto, Java e C # tratam os tipos de matriz de forma covariável. Por exemplo, em C # string[] é um subtipo de object[] , e em Java String[] é um subtipo de Object[] .

    Isso responde à pergunta “Por que as matrizes são covariantes?” Ou, mais precisamente, “Por que as matrizes foram tornadas covariantes na época ?”

    Quando os genéricos foram introduzidos, eles não foram propositadamente tornados covariantes por razões apontadas nesta resposta por Jon Skeet :

    Não, uma List não é uma List . Considere o que você pode fazer com uma List – você pode adicionar qualquer animal a ele … incluindo um gato. Agora, você pode adicionar logicamente um gato a uma ninhada de filhotes? Absolutamente não.

     // Illegal code - because otherwise life would be Bad List dogs = new List(); List animals = dogs; // Awooga awooga animals.add(new Cat()); Dog dog = dogs.get(0); // This should be safe, right? 

    De repente você tem um gato muito confuso.

    A motivação original para criar matrizes covariantes descritas no artigo da Wikipédia não se aplicava aos genéricos porque os curingas tornavam possível a expressão de covariância (e contravariância), por exemplo:

     boolean equalLists(List< ?> l1, List< ?> l2); void shuffleList(List< ?> l); 

    O motivo é que cada array sabe seu tipo de elemento durante o tempo de execução, enquanto a coleção genérica não faz isso por causa do apagamento de tipo. Por exemplo:

     String[] strings = new String[2]; Object[] objects = strings; // valid, String[] is Object[] objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime 

    Se isso foi permitido com collections genéricas:

     List strings = new ArrayList(); List objects = strings; // let's say it is valid objects.add(12); // invalid, Integer should not be put into List but there is no information during runtime to catch this 

    Mas isso causaria problemas mais tarde quando alguém tentasse acessar a lista:

     String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String 

    Pode ser esta ajuda: –

    Genéricos não são covariantes

    Matrizes na linguagem Java são covariantes – o que significa que se Integer estender Number (o que ele faz), então não é apenas um Integer também um Number, mas um Integer [] também é um Number[] , e você está livre para passar ou atribuir um Integer[] onde um Number[] é chamado. (Mais formalmente, se Number for um supertipo de Integer, então Number[] é um supertipo de Integer[] .) Você pode pensar que o mesmo é verdadeiro para os tipos genéricos – essa List é um supertipo de List e você pode passar um List onde uma List é esperada. Infelizmente, não funciona assim.

    Acontece que há uma boa razão para que isso não funcione dessa maneira: isso quebraria o tipo que os genéricos de segurança deveriam fornecer. Imagine que você poderia atribuir uma List a uma List . Em seguida, o código a seguir permitiria que você colocasse algo que não fosse um Integer em uma List :

     List li = new ArrayList(); List ln = li; // illegal ln.add(new Float(3.1415)); 

    Como ln é uma List , adicionar um Float parece perfeitamente legal. Mas se tivéssemos um alias de li , então quebraria a promise de segurança de tipos implícita na definição de li – que é uma lista de inteiros, e é por isso que os tipos genéricos não podem ser covariantes.

    As matrizes são covariantes por pelo menos dois motivos:

    • É útil para collections que contêm informações que nunca serão alteradas como covariantes. Para uma coleção de T ser covariante, seu armazenamento de backup também deve ser covariante. Embora seja possível projetar uma coleção T imutável que não use um T[] como seu armazenamento de apoio (por exemplo, usando uma tree ou uma linked list), é improvável que tal coleção tenha o mesmo desempenho de uma matriz. Pode-se argumentar que uma maneira melhor de fornecer collections imutáveis ​​covariantes teria sido definir um tipo de “matriz imovel covariante” que eles poderiam usar um repository de apoio, mas simplesmente permitir a covariância da matriz era provavelmente mais fácil.

    • Matrizes serão freqüentemente mutadas por código que não sabe que tipo de coisa vai ser nelas, mas não colocará no array nada que não tenha sido lido daquele mesmo array. Um bom exemplo disso é o código de sorting. Conceitualmente, pode ser possível que os tipos de matriz incluam methods para trocar ou permutar elementos (tais methods podem ser igualmente aplicáveis ​​a qualquer tipo de matriz) ou definir um object “manipulador de matriz” que contém uma referência a uma matriz e uma ou mais coisas. que tinha sido lido a partir dele, e poderia include methods para armazenar itens previamente lidos na matriz da qual eles vieram. Se as matrizes não fossem covariantes, o código do usuário não seria capaz de definir esse tipo, mas o tempo de execução poderia include alguns methods especializados.

    O fato de que as matrizes são covariantes pode ser visto como um hack feio, mas na maioria dos casos facilita a criação de código de trabalho.

    Uma característica importante dos tipos paramétricos é a capacidade de escrever algoritmos polimórficos, ou seja, algoritmos que operam em uma estrutura de dados, independentemente de seu valor de parâmetro, como Arrays.sort() .

    Com genéricos, isso é feito com tipos curinga:

     > void sort(E[]); 

    Para ser realmente útil, os tipos curinga exigem captura de curinga e isso requer a noção de um parâmetro de tipo. Nada disso estava disponível no momento em que as matrizes foram adicionadas ao Java, e as matrizes de referência de tipo covariante permitiram uma maneira muito mais simples de permitir algoritmos polimórficos:

     void sort(Comparable[]); 

    No entanto, essa simplicidade abriu uma lacuna no sistema de tipos estáticos:

     String[] strings = {"hello"}; Object[] objects = strings; objects[0] = 1; // throws ArrayStoreException 

    requerendo uma verificação de tempo de execução de cada access de gravação a uma matriz de tipo de referência.

    Em suma, a abordagem mais recente incorporada pelos genéricos torna o sistema de tipos mais complexo, mas também mais estaticamente seguro, enquanto a abordagem mais antiga era mais simples e menos estaticamente segura. Os designers da linguagem optaram pela abordagem mais simples, tendo coisas mais importantes a fazer do que fechar uma pequena lacuna no sistema de tipos que raramente causa problemas. Mais tarde, quando o Java foi estabelecido e as necessidades prementes foram resolvidas, eles tinham os resources para fazer o certo para os genéricos (mas alterá-lo para matrizes teria quebrado os programas Java existentes).

    Os genéricos são invariantes : da JSL 4.10 :

    … Subtitulação não se estende por meio de tipos genéricos: T <: U não implica que C <: C

    e algumas linhas adiante, o JLS também explica que
    Matrizes são covariantes (primeira bala):

    4.10.3 Subtipagem entre os tipos de matriz

    insira a descrição da imagem aqui

    Minha opinião: quando o código está esperando um array [] e você dá a ele B [] onde B é uma subclass de A, há apenas duas coisas para se preocupar: o que acontece quando você lê um elemento de array e o que acontece se você escreve isto. Portanto, não é difícil escrever regras de idioma para garantir que a segurança de tipo seja preservada em todos os casos (a regra principal é que uma ArrayStoreException poderia ser lançada se você tentar colocar um A em um B []). Para um genérico, porém, quando você declara uma class SomeClass , pode haver várias maneiras em que T é usado no corpo da class, e eu estou supondo que é complicado demais para resolver todas as combinações possíveis para escrever regras sobre quando as coisas são permitidas e quando elas não são.

    Eu acho que eles tomaram uma decisão errada no primeiro lugar que fez a matriz covariante. Ele quebra o tipo de segurança como descrito aqui e eles ficaram presos com isso por causa da compatibilidade com versões anteriores e depois que eles tentaram não cometer o mesmo erro para genéricos. E essa é uma das razões pelas quais Joshua Bloch prefere listas a listas no Item 25 do livro “Effective Java (second edition)”