Por que o Java Collections não pode armazenar diretamente tipos de Primitivos?

123

As coleções Java armazenam apenas objetos, não tipos primitivos; no entanto, podemos armazenar as classes de wrapper.

Por que essa restrição?

JavaUser
fonte
2
Essa restrição é péssima quando você lida com primitivas e deseja usar as Filas para enviar e suas taxas de envio são muito rápidas. Estou lidando com esse problema agora: a autobox leva muito tempo.
JPM
Tecnicamente, tipos primitivos são objetos (instâncias singleton para serem exatas), eles simplesmente não são definidos por a class, e sim pela JVM. A instrução int i = 1define um ponteiro para a instância singleton do objeto que define intna JVM, configurada com o valor 1definido em algum lugar na JVM. Sim, ponteiros em Java - isso é apenas abstraído de você pela implementação da linguagem. As primitivas não podem ser usadas como genéricos porque o idioma predicado de todos os tipos genéricos deve ser do supertipo Object- portanto, por que A<?>compila A<Object>em tempo de execução.
Robert E Fry
1
Os tipos primitivos do @RobertEFry não são objetos em Java; portanto, tudo o que você escreveu sobre instâncias singleton é fundamentalmente errado e confuso. Sugiro a leitura do capítulo "Tipos, valores e variáveis" da Especificação da linguagem Java, que define o que é um objeto: "Um objeto (§4.3.1) é uma instância criada dinamicamente de um tipo de classe ou de uma matriz criada dinamicamente. "
TypeRacer

Respostas:

99

Foi uma decisão de design Java e que alguns consideram um erro. Contêineres querem Objetos e primitivas não derivam de Objeto.

Este é um lugar que os designers do .NET aprenderam com a JVM e implementaram tipos de valor e genéricos, de modo que o boxe é eliminado em muitos casos. No CLR, contêineres genéricos podem armazenar tipos de valor como parte da estrutura subjacente do contêiner.

Java optou por adicionar suporte genérico 100% no compilador sem suporte da JVM. Sendo a JVM o que é, não suporta um objeto "não-objeto". Os genéricos Java permitem fingir que não há invólucro, mas você ainda paga o preço de desempenho do boxe. Isso é importante para certas classes de programas.

O boxe é um compromisso técnico, e acho que são detalhes de implementação vazando para o idioma. A autoboxing é um bom açúcar sintático, mas ainda é uma penalidade de desempenho. Se alguma coisa, eu gostaria que o compilador me avisasse quando ele caixa automática. (Pelo que sei, agora, escrevi esta resposta em 2010).

Uma boa explicação sobre SO sobre boxe: por que alguns idiomas precisam de boxe e unboxing?

E críticas aos genéricos Java: Por que alguns afirmam que a implementação de genéricos em Java é ruim?

Na defesa de Java, é fácil olhar para trás e criticar. A JVM resistiu ao teste do tempo e é um bom design em muitos aspectos.

codenheim
fonte
6
Não é um erro, uma troca cuidadosamente escolhida que, acredito, serviu Java muito bem.
precisa saber é o seguinte
13
Foi um erro suficiente que o .NET aprendeu com ela e implementou a caixa automática desde o início e os genéricos no nível da VM sem sobrecarga de boxe. A própria tentativa de correção de Java foi apenas uma solução no nível de sintaxe que ainda sofre com o impacto no desempenho da caixa automática versus a ausência de boxe. A implementação do Java mostrou um desempenho ruim com grandes estruturas de dados.
Codenheim
2
@mrjoltcola: IMHO, a caixa automática padrão é um erro, mas deveria ter havido uma maneira de marcar variáveis ​​e parâmetros que deveriam automaticamente colocar os valores dados a eles. Mesmo agora, acho que deve haver um meio adicionado para especificar que determinadas variáveis ​​ou parâmetros não aceitem valores com caixa automática nova [por exemplo, deve ser legal passar Object.ReferenceEqualsreferências do tipo Objectque identificam números inteiros em caixa, mas não deve ser legal passar um valor inteiro]. O auto-unboxing de Java é IMHO apenas desagradável.
supercat
18

Facilita a implementação. Como as primitivas Java não são consideradas objetos, você precisará criar uma classe de coleção separada para cada uma dessas primitivas (sem código de modelo para compartilhar).

Você pode fazer isso, é claro, apenas veja GNU Trove , Apache Commons Primitives ou HPPC .

A menos que você tenha coleções realmente grandes, a sobrecarga para os wrappers não importa o suficiente para que as pessoas se importem (e quando você tem coleções primitivas realmente grandes, pode querer se esforçar para usar / construir uma estrutura de dados especializada para elas )

Thilo
fonte
11

É uma combinação de dois fatos:

  • Tipos primitivos Java não são tipos de referência (por exemplo, um intnão é um Object)
  • Java faz genéricos usando apagamento de tipos de referência (por exemplo, a List<?>é realmente um List<Object>em tempo de execução)

Como ambas são verdadeiras, as coleções Java genéricas não podem armazenar tipos primitivos diretamente. Por conveniência, a caixa automática é introduzida para permitir que tipos primitivos sejam automaticamente encaixotados como tipos de referência. No entanto, não se engane, as coleções ainda estão armazenando referências a objetos.

Isso poderia ter sido evitado? Possivelmente.

  • Se an inté an Object, então não há necessidade de tipos de caixas.
  • Se os genéricos não forem feitos usando apagamento de tipo, as primitivas poderiam ter sido usadas para os parâmetros de tipo.
poligenelubricants
fonte
8

Existe o conceito de boxe automático e unboxing automático. Se você tentar armazenar um intem um List<Integer>compilador Java, ele será automaticamente convertido em um Integer.

Jeremy
fonte
1
A autoboxing foi introduzida no Java 1.5 junto com os Generics.
Jeremy
1
Mas é uma coisa de tempo de compilação; açúcar de sintaxe sem benefício de desempenho. O compilador Java autoboxa, daí a penalidade de desempenho em comparação com implementações de VM como .NET, que são genéricos, não envolvem boxe.
Codenheim
1
@mrjoltcola: Qual é o seu ponto? Eu estava apenas compartilhando fatos, não discutindo.
Jeremy
3
Meu ponto é importante ressaltar a diferença entre sintaxe e desempenho. Também considero meus comentários compartilhamento de fatos, não argumento. Obrigado.
Codenheim
2

Não é realmente uma restrição, é?

Considere se você deseja criar uma coleção que armazene valores primitivos. Como você escreveria uma coleção que pode armazenar int, float ou char? Provavelmente, você terminará com várias coleções, portanto precisará de uma lista intlist e de uma charlist etc.

Aproveitando a natureza orientada a objetos do Java, quando você escreve uma classe de coleção, ele pode armazenar qualquer objeto, portanto, você precisa de apenas uma classe de coleção. Essa idéia, polimorfismo, é muito poderosa e simplifica muito o design das bibliotecas.

Vincent Ramdhanie
fonte
7
"Como você escreveria uma coleção que pode armazenar int, float ou char?" - Com modelos / genéricos adequadamente implementados, como outros idiomas, que não pagam a pena de fingir que tudo é um objeto.
Codenheim
Quase nunca em seis anos de Java queria armazenar uma coleção de primitivos. Mesmo nos poucos casos em que eu poderia querer que os custos extras de tempo e espaço do uso dos objetos de referência fossem insignificantes. Em particular, acho que muitas pessoas pensam que querem Map <int, T>, esquecendo que uma matriz fará esse truque muito bem.
quer
2
@DJClayworth Isso só funciona bem se os valores-chave não forem escassos. Obviamente, você pode usar uma matriz auxiliar para acompanhar as chaves, mas isso tem seus próprios problemas: o acesso relativamente eficiente exigiria a manutenção de ambas as matrizes classificadas com base na ordem das chaves para permitir a pesquisa binária, o que, por sua vez, faria inserção e exclusão ineficiente, a menos que a inserção / exclusão seja padronizada de forma que os itens inseridos provavelmente acabem onde um item excluído anteriormente estava e / ou alguns buffers estão intercalados nas matrizes, etc. Existem recursos disponíveis, mas seria bom ter Próprio Java.
JAB
@JAB Na verdade, funciona bem se as teclas são escassas, só precisa de mais memória do que se não forem escassas. E se as chaves forem esparsas, isso implica que não há muitas e usar Integer como chave funciona bem. Use qualquer abordagem que precise de menos memória. Ou o que você quiser, se não se importa.
DJClayworth
0

Acho que podemos ver progresso nesse espaço no JDK, possivelmente no Java 10, com base nesse JEP - http://openjdk.java.net/jeps/218 .

Se você deseja evitar primitivas de boxe em coleções hoje, existem várias alternativas de terceiros. Além das opções de terceiros mencionadas anteriormente, também há Eclipse Collections , FastUtil e Koloboke .

Uma comparação dos mapas primitivos também foi publicada há pouco tempo com o título: Visão geral do HashMap grande: JDK, FastUtil, Goldman Sachs, HPPC, Koloboke, Trove . A biblioteca GS Collections (Goldman Sachs) foi migrada para a Eclipse Foundation e agora é Eclipse Collections.

Donald Raab
fonte
0

A principal razão é a estratégia de design java. ++ 1) coleções requer objetos para manipulação e primitivas não são derivadas do objeto, portanto, esse pode ser o outro motivo. 2) Os tipos de dados primitivos Java não são de referência para ex. int não é um objeto.

Superar:-

nós temos o conceito de boxe automático e unboxing automático. portanto, se você estiver tentando armazenar tipos de dados primitivos, o compilador converterá isso automaticamente em objeto dessa classe de dados primitivos.

user5693566
fonte