Por que matrizes em Java não substituem igual a ()?


8

Eu estava trabalhando com um HashSetoutro dia, que tem isso escrito na especificação:

[add ()] adiciona o elemento especificado e a este conjunto se este conjunto não contiver nenhum elemento e2 tal que (e == null? e2 == null: e.equals (e2))

Eu estava usando char[]no HashSetaté que percebi que, com base neste contrato, não era melhor do que um ArrayList! Como ele está usando o não substituído .equals(), minhas matrizes só serão verificadas quanto à igualdade de referência, o que não é particularmente útil. Eu sei que Arrays.equals()existe, mas isso não ajuda quando alguém está usando coleções como HashSet.

Então, minha pergunta é: por que as matrizes Java não substituem iguais?


3
misturando matrizes nativas e coleta não é realmente aconselhável
aberração catraca

1
É o princípio da questão, eu acho. Uma matriz é a segunda estrutura de dados imperativos mais primitiva após variáveis ​​mutáveis. São meramente nslots de memória grandes o suficiente para cada um conter algum valor do tipo Tcolado sequencialmente. Eles são os alicerces para coleções efêmeras mais sofisticadas. O que você queria era List.
Doval 15/05

1
@ratchetfreak Já ouvi muitas pessoas dizerem isso, mas nunca por que. Por que é uma má ideia?
Richard Tingle 15/05

As matrizes @RichardTingle nem sempre combinam bem com os genéricos, as matrizes não são iteráveis, sua toString()representação é praticamente inútil e quase não há vantagem em usar uma sobre uma ArrayList.
Doval 15/05

@Doval Mas essas são todas as razões para não usar matrizes (o que eu concordo com 95% do tempo). Às vezes, preciso usar matrizes, porque elas são boas com gráficos 3D, mas quero mapear uma chave para elas. Então Map<Key, int[]>sempre pareceu natural. Mas eu sempre estou nervoso que há algo horrível esperando por mim
Richard Tingle

Respostas:


8

Houve uma decisão de design a ser tomada desde o início em Java:

As matrizes são primitivas? ou eles são objetos?

A resposta é: nem realmente ... ou ambos, se você olhar de outra maneira. Eles trabalham bastante próximos ao sistema em si e ao back-end da jvm.

Um exemplo disso é o método java.lang.System.arraycopy () que precisa ter uma matriz de qualquer tipo. Assim, a matriz precisa ser capaz de herdar algo e isso é um Objeto. E arraycopy é um método nativo.

Matrizes são também divertido em que eles podem segurar primitivas ( int, char, double, etc ... enquanto as outras coleções só pode conter objetos. Olhe, por exemplo, na java.util.Arrays eo feio da iguala métodos. Este foi posto em como uma reflexão posterior deepEquals (Object [], Object []) não foi adicionado até 1,5, enquanto o restante da classe Arrays foi adicionado no 1.2.

Porque esses objetos são matrizes , eles permitem que você faça algumas coisas que estão na memória ou quase no nível da memória - algo que o Java geralmente oculta do codificador. Isso permite que certas coisas sejam feitas mais rapidamente às custas de quebrar principalmente o modelo de objetos.

No início do sistema, houve uma troca entre flexibilidade e algum desempenho. O desempenho venceu e a falta de flexibilidade foi envolvida nas várias coleções. Matrizes em Java são um Objeto pouco implementado sobre um tipo primitivo (originalmente) destinado a trabalhar com o sistema quando você precisar.

Na maioria das vezes, matrizes brutas eram coisas que parece que os designers originais tentaram ignorar e esconder apenas no sistema. E eles queriam que fosse rápido (o Java inicial tinha alguns problemas com a velocidade). Era uma verruga no design de matrizes que não são boas matrizes, mas era necessária quando você queria expor algo o mais próximo possível do sistema. Aliás, as linguagens contemporâneas do Java inicial também têm essa verruga - não se pode fazer um .equals()no array do C ++.

Java e C ++ seguiram o mesmo caminho para matrizes - uma biblioteca externa que executa as operações conforme necessário em matrizes em vez de matrizes ... e sugerindo que os codificadores usem tipos nativos melhores, a menos que realmente saibam o que estão fazendo e por que são. fazendo assim.

Portanto, a abordagem para implantar .equals em uma matriz está errada, mas é a mesma que os codificadores provenientes de C ++ conheciam. Portanto, escolha a coisa menos errada em termos de desempenho - deixe como a implementação de Objeto: dois Objetos são iguais se e somente se estiverem se referindo ao mesmo objeto.

Você precisa que a matriz seja uma estrutura primitiva para poder se comunicar com ligações nativas - algo o mais próximo possível da matriz C clássica. Mas, diferentemente das outras primitivas, você precisa que a matriz possa ser transmitida como referência e, portanto, como um Objeto. Portanto, é mais primitivo, com alguns hackers de objetos ao lado e algumas verificações de limites.


+1 e obrigado pela resposta. Era isso que eu realmente estava procurando, a perspectiva do design por trás da decisão, não apenas que é uma má idéia.
Azar

1
@ Azar, há alguma discussão em C2: Java Arrays deve ser um objeto de primeira classe, que possui algum código que mostra alguns dos truques que acontecem nos bastidores com um array ... junto com outros queixando-se de que os arrays não são bons objetos.

2
Felizmente para o C ++, criar uma classe de matriz mais inteligente não requer boxe. Java não é tão afortunado.
Thomas Eding

3

Em Java, matrizes são pseudo-objetos. As referências a objetos podem conter matrizes e eles têm os métodos Object padrão, mas são muito leves em comparação com uma coleção verdadeira. Arrays fazer apenas o suficiente para satisfazer o contrato de um objeto e usar as implementações padrão de equals, hashCodee toStringdeliberadamente.

Considere um Object[]. Um elemento dessa matriz pode ser qualquer coisa que se encaixe em um objeto, que inclui outra matriz. Poderia ser um primitivo em caixa, um soquete, qualquer coisa. O que igualdade significa nesse caso? Bem, isso depende do que realmente está no array. Isso não é algo conhecido no caso geral quando o idioma estava sendo projetado. Igualdade é definida tanto pela própria matriz quanto pelo seu conteúdo .

Essa é a razão pela qual existe uma Arraysclasse auxiliar que possui métodos para calcular igualdade (incluindo valores profundos iguais), códigos de hash, etc. No entanto, esses métodos são bem definidos quanto ao que fazem. Se você precisar de uma funcionalidade diferente, escreva seu próprio método para comparar duas matrizes de igualdade com base nas necessidades do seu programa.


Embora não seja uma resposta estrita à sua pergunta, acho relevante dizer que você realmente deve usar coleções em vez de matrizes. Apenas converta em uma matriz ao fazer interface com uma API que requer matrizes. Caso contrário, as coleções oferecem melhor segurança de tipo, contratos mais bem definidos e geralmente são mais fáceis de usar que as matrizes.


Matrizes são objetos reais. O fato de serem o único tipo agregado de vários elementos significa que qualquer outra coleção de tamanho variável de formulários deve ser apoiada por matrizes ou por objetos O (N) fora de si, portanto, por comparação, as matrizes serão "leves". Penso que a questão mais fundamental não é que as matrizes sejam leves, mas sim que existem muitas maneiras de usá-las. Veja minha resposta abaixo.
precisa

1
"O que igualdade significa nesse caso?" - hum, que tal as matrizes terem o mesmo comprimento, mais todos os objetos em todos os índices na origem e no destino devem ser iguais de acordo com o contrato de igual ()? parece o que você esperaria e é o contrato exato implementado por Arrays.equals ().
Jeffrey Blattman

@ JeffreyBlattman - o que acontece, então, se um array de objetos se contém?
Jules

@JeffreyBlattman, em seguida, aborda os autores da linguagem que implementou a igualdade de referência para matrizes, mas forneceu Arrays.equals()profunda igualdade.

@Jules "o que acontece, então, se uma matriz de objetos se contiver?" a mesma coisa que acontece se um objeto se contém. se você implementar um ingênuo igual a, terá um estouro de pilha.
Jeffrey Blattman

1

A dificuldade fundamental com a substituição de matrizes equalsé que uma variável de um tipo como int[]pode ser usada de pelo menos três maneiras fundamentalmente diferentes, e o significado de equalsdeve variar dependendo do uso. Em particular, um campo do tipo int[]...

  1. ... pode encapsular uma sequência de valores em uma matriz que nunca será modificada, mas pode ser compartilhada livremente com o código que não a modifica.

  2. ... pode encapsular a propriedade exclusiva de um contêiner de retenção de número inteiro que pode ser alterado à vontade por seu proprietário.

  3. ... pode identificar um contêiner de retenção de número inteiro que alguma outra entidade está usando para encapsular seu estado e, assim, servir como uma conexão com o estado dessa outra entidade.

Se uma classe tiver um int[]campo foousado para qualquer um dos dois primeiros propósitos, instale xe ydeverá considerar x.fooe y.foocomo encapsular o mesmo estado se eles mantiverem a mesma sequência de números; se o campo for usado para a terceira finalidade, no entanto, x.fooe y.foosomente encapsulará o mesmo estado se eles identificarem a mesma matriz [isto é, são referência igual]. Se o Java incluísse tipos diferentes para os três usos acima e se equalsadotasse um parâmetro para identificar como a referência estava sendo usada, seria apropriado int[]usar a igualdade de sequência para os dois primeiros usos e a igualdade de referência para o terceiro. Contudo, não existe esse mecanismo.

Observe também que o int[]caso era o tipo mais simples de matriz. Para matrizes que contêm referências a classes diferentes Objectou tipos de matriz, haveria possibilidades adicionais.

  1. Uma referência a uma matriz imutável compartilhável que encapsula coisas que nunca mudarão.

  2. Uma referência a uma matriz imutável compartilhável que identifica itens pertencentes a outras entidades.

  3. Uma referência a uma matriz de propriedade exclusiva que encapsula referências a coisas que nunca mudarão.

  4. Uma referência a uma matriz de propriedade exclusiva que encapsula referências a itens de propriedade exclusiva.

  5. Uma referência a uma matriz de propriedade exclusiva que identifica itens pertencentes a outras entidades.

  6. Uma referência que identifica uma matriz pertencente a alguma outra entidade.

Nos casos 1, 3 e 4, duas referências de matriz devem ser consideradas iguais se os itens correspondentes tiverem "valor igual". Nos casos 2 e 5, duas referências de matriz devem ser consideradas iguais se identificarem a mesma sequência de objetos. No caso 6, duas referências de matriz devem ser consideradas iguais apenas se identificarem a mesma matriz.

Para equalsse comportar de maneira sensata com tipos agregados, eles precisam ter alguma maneira de saber como as referências serão usadas. Infelizmente, o sistema de tipos de Java não tem como indicar isso.


-2

Substituir array equals()e hashCode()depender do conteúdo os tornaria semelhantes a coleções - tipos mutáveis ​​com não constantes hashCode(). Os tipos com alteração hashCode()se comportam mal quando armazenados em tabelas de hash e outros aplicativos que dependem de hashCode()valor fixo.

Set<List<Integer>> data = new HashSet<List<Integer>>();
List<Integer> datum = new ArrayList<Integer>();
datum.add(1);
data.add(datum);
assert data.contains(datum); // true
datum.add(2);
assert data.contains(datum); // false, WAT???

Matrizes, por outro lado, têm hashCode trivial (), podem ser usadas como chaves de tabela de hash e ainda são mutáveis.

Set<int[]> data = new HashSet<int[]>(67);
int[] datum = new int[]{1, 2};
data.add(datum);
System.out.println(data.contains(datum)); //true
datum[0] = 78;
System.out.println(data.contains(datum)); //true
//PROFIT!!!

3
Não há matriz lá. A int[]matriz de tipos.

@ MichaelT, esse é o ponto.
Basilevs 15/05

Por que os votos negativos? O atendedor fez muito trabalho. Algo está errado?
Tom Au

3
@ TomAu, a pergunta é sobre o design da matriz em java, sua natureza de objeto psuedo e as decisões de design por trás dessa escolha (para fazer com que a matriz use apenas igualdade referencial em vez de profunda igualdade). Essa resposta, por outro lado, tenta apresentar uma solução de código de como o código pode ser reescrito - o que não é o que a pergunta faz.

@ MichaelT, isso ilustra a proficiência em lógica de igualdade baseada em identidade sobre a dependente de estado.
Basilevs 15/05
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.