Do Effective Java de Joshua Bloch,
- Matrizes diferem do tipo genérico de duas maneiras importantes. As primeiras matrizes são covariantes. Os genéricos são invariantes.
Covariante significa simplesmente se X é subtipo de Y, então X [] também será subtipo de Y []. Matrizes são covariantes Como string é o subtipo de Object So
String[] is subtype of Object[]
Invariante significa simplesmente, independentemente de X ser subtipo de Y ou não,
List<X> will not be subType of List<Y>.
Minha pergunta é por que a decisão de fazer matrizes covariantes em Java? Existem outras postagens de SO, como Por que as matrizes são invariáveis, mas as listas são covariantes? , mas eles parecem estar focados em Scala e eu não consigo acompanhar.
java
arrays
generics
language-design
covariance
ansioso para aprender
fonte
fonte
LinkedList
.Respostas:
Via wikipedia :
Isso responde à pergunta "Por que as matrizes são covariantes?" Ou, mais precisamente, "Por que as matrizes foram feitas covariantes na época ?"
Quando os genéricos foram introduzidos, eles não foram propositalmente tornados covariantes pelas razões apontadas nesta resposta por Jon Skeet :
A motivação original para tornar as matrizes covariantes descritas no artigo da wikipedia não se aplicava aos genéricos, porque os curingas tornaram possível a expressão de covariância (e contravariância), por exemplo:
fonte
Object[] num = new Number[4]; num[1]= 5; num[2] = 5.0f; num[3]=43.4; System.out.println(Arrays.toString(num)); num[0]="hello";
ArrayStoreException
s conforme necessário. Claramente, isso foi considerado um compromisso digno na época. Compare isso com hoje: muitos consideram a covariância de matriz um erro, em retrospecto.O motivo é que toda matriz conhece seu tipo de elemento durante o tempo de execução, enquanto a coleta genérica não sabe por causa do apagamento do tipo.
Por exemplo:
Se isso foi permitido com coleções genéricas:
Mas isso causaria problemas mais tarde, quando alguém tentaria acessar a lista:
fonte
Pode ser esta ajuda: -
Os genéricos não são covariantes
Matrizes na linguagem Java são covariantes - o que significa que, se Inteiro estender Number (o que ele faz), além de um Inteiro também ser um Número, um Inteiro [] também será um
Number[]
, e você poderá passar ou atribuir um número inteiro.Integer[]
onde aNumber[]
é chamado. (Mais formalmente, se Number é um supertipo de Inteiro, entãoNumber[]
é um supertipo deInteger[]
.) Você também pode pensar o mesmo para tipos genéricos - queList<Number>
é um supertipoList<Integer>
e que você pode passar paraList<Integer>
ondeList<Number>
é esperado. Infelizmente, não funciona dessa maneira.Acontece que há uma boa razão para que não funcione dessa maneira: quebraria o tipo que os genéricos de segurança deveriam fornecer. Imagine que você poderia atribuir a
List<Integer>
a aList<Number>
. Em seguida, o código a seguir permitiria colocar algo que não era um número inteiro em umList<Integer>
:Como ln é a
List<Number>
, adicionar um float a ele parece perfeitamente legal. Mas se ln estivesse com um aliasli
, isso quebraria a promessa de segurança de tipo implícita na definição de li - que é uma lista de números inteiros, e é por isso que tipos genéricos não podem ser covariantes.fonte
ArrayStoreException
em tempo de execução.WHY
se as matrizes são covariantes. como Sotirios mencionou, com Arrays seria possível obter ArrayStoreException em tempo de execução, se Array se tornasse invariável, poderíamos detectar esse erro no momento da compilação correto?Animal
, que não precisa aceitar nenhum item recebido de outro lugar" de "Matriz que não deve conter nada além deAnimal
, e deve estar disposto a aceitar referências fornecidas externamente aAnimal
. Código que precisa do primeiro deve aceitar uma matriz deCat
, mas código que precisa do último não. Se o compilador puder distinguir os dois tipos, poderá fornecer verificação em tempo de compilação. Infelizmente, a única coisa que os distingue ... #As matrizes são covariantes por pelo menos dois motivos:
É útil para coleções que contêm informações que nunca serão alteradas para serem covariantes. Para que uma coleção de T seja covariante, sua loja de apoio também deve ser covariante. Embora se possa projetar uma
T
coleção imutável que não a useT[]
como repositório (por exemplo, usando uma árvore ou uma lista vinculada), é improvável que essa coleção funcione tão bem quanto uma que é apoiada por uma matriz. Alguém poderia argumentar que uma maneira melhor de fornecer coleções imutáveis covariantes seria definir um tipo de "matriz imutável covariante" que eles pudessem usar em uma loja de apoio, mas simplesmente permitir a covariância da matriz era provavelmente mais fácil.As matrizes serão frequentemente alteradas por código que não sabe que tipo de coisa haverá nelas, mas não colocará na matriz nada que não tenha sido lido nessa mesma matriz. Um excelente exemplo disso é o código de classificação. Conceitualmente, pode ter sido possível para os tipos de matriz incluir métodos para trocar ou permutar elementos (esses métodos podem ser igualmente aplicáveis a qualquer tipo de matriz) ou definir um objeto "manipulador de matriz" que faça referência a uma matriz e uma ou mais coisas que foram lidas a partir dele e podem incluir métodos para armazenar itens lidos anteriormente 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 ter incluído alguns métodos especializados.
O fato de que as matrizes são covariantes pode ser visto como um truque feio, mas na maioria dos casos, facilita a criação de código funcional.
fonte
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.
- ponto bomUma 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 do seu valor de parâmetro, como
Arrays.sort()
.Com genéricos, isso é feito com tipos curinga:
Para ser realmente útil, os tipos de caracteres curinga requerem captura de caracteres 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 makes do tipo covariante de referência permitiram uma maneira muito mais simples de permitir algoritmos polimórficos:
No entanto, essa simplicidade abriu uma brecha no sistema de tipo estático:
exigindo uma verificação de tempo de execução de todo acesso de gravação a uma matriz do tipo de referência.
Em poucas palavras, 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 segura do tipo estaticamente. Os projetistas da linguagem optaram pela abordagem mais simples, tendo coisas mais importantes a fazer do que fechar uma pequena brecha no sistema de tipos que raramente causa problemas. Mais tarde, quando o Java foi estabelecido e as necessidades prementes foram atendidas, eles tinham os recursos necessários para fazer o correto para genéricos (mas alterá-lo para matrizes teria quebrado os programas Java existentes).
fonte
Os genéricos são invariantes : da JSL 4.10 :
e algumas linhas adiante, a JLS também explica que as
matrizes são covariantes (primeiro marcador):
4.10.3 Subtipagem entre tipos de matriz
fonte
Eu acho que eles tomaram uma decisão errada em primeiro lugar que tornaram a matriz covariável. Ele quebra o tipo de segurança descrito aqui e eles ficaram presos por causa da compatibilidade com versões anteriores e depois tentaram não cometer o mesmo erro de genérico. E essa é uma das razões pelas quais Joshua Bloch prefere as listas ao item 25 do livro "Java eficaz (segunda edição)"
fonte
Minha opinião: quando o código está esperando uma matriz A [] e você fornece B [] onde B é uma subclasse de A, há apenas duas coisas com que se preocupar: o que acontece quando você lê um elemento da matriz e o que acontece se você escrever isto. Portanto, não é difícil escrever regras de linguagem para garantir que a segurança do tipo seja preservada em todos os casos (a regra principal é que um
ArrayStoreException
poderia ser lançado se você tentar inserir um A em um B []). Para um genérico, porém, quando você declara uma classeSomeClass<T>
, pode haver várias maneiras deT
usá-lo no corpo da classe, e acho que é muito complicado elaborar todas as combinações possíveis para escrever regras sobre quando coisas são permitidas e quando não são.fonte