Por que as pessoas ainda usam tipos primitivos em Java?


163

Desde o Java 5, tivemos o boxe / unboxing de tipos primitivos, de modo que intseja moldado para ser java.lang.Integer, e assim por diante.

Eu vejo um monte de novos projetos Java recentemente (que definitivamente necessário um JRE de pelo menos a versão 5, se não 6) que está usando int, em vez de java.lang.Integer, embora seja muito mais conveniente para usar o último, já que tem alguns métodos auxiliares para a conversão para longvalores et al.

Por que alguns ainda usam tipos primitivos em Java? Existe algum benefício tangível?


49
já pensou em consumo e desempenho de memória?
Tedil 4/03/11

76
Eu adicionei a etiqueta do autoboxing ... e descobri que três pessoas realmente a seguem. Realmente? As pessoas seguem a tag AUTOBOXING?
precisa saber é o seguinte

4
@glowcoder Eles não são pessoas reais, são apenas conceitos abstratos que assumem uma forma humana para responder no SO. :)
biziclop

9
@TK Kocheran Principalmente porque new IntegeR(5) == new Integer(5), segundo as regras, avalie como falso.
precisa saber é o seguinte

10
Consulte Coleções GNU Trove ou Mahout ou HPPC ou ... para obter soluções para coleções de tipos primitivos. Aqueles de nós que se preocupam com a velocidade gastam nosso tempo usando tipos mais primitivos, e não menos.
bmargulies

Respostas:


395

No Java efetivo de Joshua Bloch , item 5: "Evite criar objetos desnecessários", ele publica o seguinte exemplo de código:

public static void main(String[] args) {
    Long sum = 0L; // uses Long, not long
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

e leva 43 segundos para ser executado. Levar o Long até o primitivo reduz para 6,8 segundos ... Se isso é alguma indicação de por que usamos primitivos.

A falta de igualdade de valor nativo também é uma preocupação ( .equals()é bastante detalhada em comparação com ==)

para biziclop:

class Biziclop {

    public static void main(String[] args) {
        System.out.println(new Integer(5) == new Integer(5));
        System.out.println(new Integer(500) == new Integer(500));

        System.out.println(Integer.valueOf(5) == Integer.valueOf(5));
        System.out.println(Integer.valueOf(500) == Integer.valueOf(500));
    }
}

Resulta em:

false
false
true
false

EDITAR Por que (3) retorna truee (4) retorna false?

Porque eles são dois objetos diferentes. Os 256 números inteiros mais próximos de zero [-128; 127] são armazenados em cache pela JVM, portanto, eles retornam o mesmo objeto para eles. Além desse intervalo, porém, eles não são armazenados em cache, portanto, um novo objeto é criado. Para tornar as coisas mais complicadas, o JLS exige que pelo menos 256 flyweights sejam armazenados em cache. Os implementadores da JVM podem adicionar mais, se desejarem, o que significa que isso pode ser executado em um sistema em que os 1024 mais próximos são armazenados em cache e todos eles retornam verdadeiros ... #awkward


54
Agora imagine se também iforam declarados Long!
colind

14
@TREE - na verdade, as especificações exigem que as VMs criem flyweights dentro de um determinado intervalo. Mas, infelizmente, isso lhes permite estender esse intervalo, o que significa que os programas podem se comportar de maneira diferente em diferentes VMs. Tanto para plataforma cruzada ...
Daniel Earwicker

12
O Java caiu no ralo, com mais e mais más opções de design. A autoboxing é uma falha completa, não é robusta, previsível ou portátil. Eu realmente me pergunto o que eles estavam pensando ... em vez de consertar a temida dualidade de objetos primitivos que eles conseguiram tornar pior do que em primeiro lugar.
Pop Catalin

34
@ Catalin Eu discordo de você que o autoboxing é uma falha completa. Possui algumas falhas, que não são diferentes de qualquer outro design que poderia ter sido usado (incluindo nada.) Elas deixam bem claro o que você pode ou não pode esperar e, como qualquer outro design, espera que os desenvolvedores conheçam e obedeçam aos contratos desses projetos.
corsiKa

9
@NaftuliTzviKay Isso não é um "fracasso". Eles tornam MUITO CLARO que o ==operador execute comparações de identidade de referência em Integerexpressões e comparações de igualdade de valor em intexpressões. Integer.equals()existe por esse mesmo motivo. Você nunca deve usar ==para comparar valores em qualquer tipo não primitivo. Esta é Java 101.
NullUserException

86

O autounboxing pode levar a NPEs difíceis de detectar

Integer in = null;
...
...
int i = in; // NPE at runtime

Na maioria das situações, a atribuição nula a iné muito menos óbvia do que acima.


43

Os tipos in a box apresentam desempenho inferior e requerem mais memória.


40

Tipos primitivos:

int x = 1000;
int y = 1000;

Agora avalie:

x == y

É true. Dificilmente surpreendente. Agora tente os tipos de caixa:

Integer x = 1000;
Integer y = 1000;

Agora avalie:

x == y

É false. Provavelmente. Depende do tempo de execução. Essa razão é suficiente?


36

Além de problemas de desempenho e memória, eu gostaria de apresentar outro problema: a Listinterface seria quebrada sem int.
O problema é o remove()método sobrecarregado ( remove(int)vs. remove(Object)). remove(Integer)sempre resolveria chamar o último, para que você não pudesse remover um elemento pelo índice.

Por outro lado, há uma armadilha ao tentar adicionar e remover um int:

final int i = 42;
final List<Integer> list = new ArrayList<Integer>();
list.add(i); // add(Object)
list.remove(i); // remove(int) - Ouch!

7
Seria quebrado, sim. No entanto, remove (int) é uma IMO de falha de design. Os nomes dos métodos nunca devem ser sobrecarregados se houver a menor chance de uma confusão.
precisa saber é o seguinte

4
@MrBackend Fair suficiente. Curiosamente, Vectorteve removeElementAt(int)desde o início. remove(int)foi introduzido com a estrutura de coleções em Java 1.2.
xehpuk

6
@MrBackend: quando a ListAPI foi projetada, nem os Genéricos nem o Autoboxing existiam, então não havia chance de se misturar remove(int)e remove(Object)...
Holger

@Franklin Yu: claro, mas ao projetar um novo idioma / versão sem restrições de compatibilidade, você não parava de mudar essa sobrecarga infeliz. Você simplesmente se livraria da distinção de valores primitivos e de caixa completamente, de modo que a pergunta a ser usada nunca aparecesse.
Holger

27

Você pode realmente imaginar um

  for (int i=0; i<10000; i++) {
      do something
  }

loop com java.lang.Integer vez? Um java.lang.Integer é imutável, portanto, cada incremento ao redor do loop criaria um novo objeto java no heap, em vez de apenas incrementar o int na pilha com uma única instrução JVM. O desempenho seria diabólico.

Eu realmente discordo que é muito mais conveniente usar o java.lang.Integer do que int. Pelo contrário. Autoboxing significa que você pode usar int onde, caso contrário, seria forçado a usar Inteiro, e o compilador java se encarrega de inserir o código para criar o novo objeto Inteiro para você. Autoboxing é sobre permitir que você use um int onde um número inteiro é esperado, com o compilador inserindo a construção do objeto relevante. De maneira alguma remove ou reduz a necessidade do int em primeiro lugar. Com o autobox, você obtém o melhor dos dois mundos. Você cria um número inteiro automaticamente para você quando precisa de um objeto java baseado em heap e obtém a velocidade e a eficiência de um int quando está apenas fazendo cálculos aritméticos e locais.


19

Tipos primitivos são muito mais rápidos:

int i;
i++;

Inteiro (todos os números e também uma String) é um tipo imutável : uma vez criado, não pode ser alterado. Se ifosse Inteiro, i++isso criaria um novo objeto Inteiro - muito mais caro em termos de memória e processador.


Você não deseja que uma variável seja alterada se fizer isso i++em outra variável; portanto, o Inteiro precisa ser imutável para poder fazer isso (ou pelo menos isso i++teria que criar um novo objeto Inteiro de qualquer maneira). (E os valores primitivos também são imutáveis ​​- você simplesmente não comenta isso, pois eles não são objetos.)
Paŭlo Ebermann

4
@ Paŭlo: Dizer que os valores primitivos são imutáveis ​​é meio sem sentido. Quando você reatribui uma variável primitiva a um novo valor, não está criando nada de novo. Nenhuma alocação de memória está envolvida. O argumento de Peter é o seguinte: i ++ para um primitivo não faz alocação de memória, mas para um objeto necessariamente.
Eddie

@ Eddie: (Ele não necessariamente precisa de alocação de memória, também pode retornar um valor em cache. Para alguns valores pequenos, eu acho.) Meu ponto era que a imutabilidade de números inteiros aqui não é o ponto de decisão, você iria querer ter outro objeto, independentemente da imutabilidade.
Paŭlo Ebermann

@ Paŭlo: meu único ponto foi que o Inteiro é uma ordem de magnitude mais lenta que as primitivas. E isso se deve ao fato de os tipos de caixa serem imutáveis ​​e toda vez que você altera um valor, um novo objeto é criado. Não afirmei que há algo errado com eles ou o fato de serem imutáveis. Só que eles são mais lentos e que um codificador deve saber disso. Dê uma olhada em como tarifas Groovy sem tipos primitivos jroller.com/rants/entry/why_is_groovy_so_slow
Peter Knego

1
Imutabilidade e ++é um arenque vermelho aqui. Imagine o Java foi aprimorado para operador de suporte a sobrecarga de uma forma muito simples, de tal forma que se uma classe (como Integertem um método plus, então você poderia escrever i + 1em vez de i.plus(1). E assumem também que o compilador é inteligente o suficiente para expandir i++em i = i + 1. Agora você poderia dizer i++e eficaz "incrementar a variável i" sem Integerser mutável.
Daniel Earwicker

16

Em primeiro lugar, hábito. Se você codifica em Java há oito anos, acumula uma quantidade considerável de inércia. Por que mudar se não há motivo convincente para fazê-lo? Não é como se o uso de primitivas in a box tivesse vantagens extras.

O outro motivo é afirmar que nullnão é uma opção válida. Seria inútil e enganoso declarar a soma de dois números ou uma variável de loop como Integer.

Também há o aspecto de desempenho, embora a diferença de desempenho não seja crítica em muitos casos (embora seja muito ruim), ninguém gosta de escrever código que possa ser escrito com a mesma facilidade e rapidez que já estamos. costumava.


15
Discordo. O aspecto do desempenho pode ser crítico. Muito pouco disso é provavelmente inercial ou força do hábito.
Eddie

7
@ Eddie Pode ser, mas muito raramente é. Confie em mim, pois a maioria das pessoas argumentos de desempenho são apenas uma desculpa.
biziclop

3
Eu também gostaria de proteger o argumento de desempenho. No Android com Dalvik, cada objeto que você cria aumenta o "risco" de o GC ser chamado e quanto mais objetos você tiver, as pausas serão mais longas. Portanto, criar Inteiros em vez de int em um loop provavelmente custará alguns quadros descartados.
Igor Čordaš

1
@PSIXO É um argumento justo, escrevi com o Java puramente do servidor em mente. Os dispositivos móveis são um animal totalmente diferente. Mas o que quero dizer é que mesmo os desenvolvedores que, de outra forma, escrevem códigos terríveis sem nenhuma consideração ao desempenho, citam isso como uma razão, a partir deles isso soa muito como uma desculpa.
biziclop

12

A propósito, o Smalltalk possui apenas objetos (sem primitivos) e, no entanto, eles haviam otimizado seus inteiros pequenos (usando nem todos os 32 bits, apenas 27 ou mais) para não alocar espaço de heap, mas simplesmente usando um padrão de bits especial. Também outros objetos comuns (verdadeiro, falso, nulo) tinham padrões de bits especiais aqui.

Portanto, pelo menos nas JVMs de 64 bits (com um espaço de nome do ponteiro de 64 bits), deve ser possível não ter nenhum objeto inteiro, caractere, byte, curto, booleano, flutuante (e pequeno longo) (exceto os criados por explícito new ...()), apenas padrões de bits especiais, que poderiam ser manipulados pelos operadores normais com bastante eficiência.


Eu deveria ter dito "algumas implementações", pois isso não é regido pelas especificações de linguagem, eu acho. (E, infelizmente eu não posso citar quaisquer fontes aqui, é apenas pelo que eu ouvi em algum lugar.)
Paulo Ebermann

Bem, o JIT já mantém meta no ponteiro; incl, o ponteiro pode manter as informações do GC ou o Klass (otimizar Classe é uma idéia muito melhor do que otimizar Inteiros, pelo qual posso me importar menos). Mudar o ponteiro exigiria código shift / cmp / jnz (ou algo parecido) antes de cada ponteiro carregar. A ramificação provavelmente não será muito bem prevista pelo hardware (já que pode ser do tipo Valor e do objeto normal) e levaria a um impacto no desempenho.
bestsss 11/03/11

3
Eu fiz Smalltalk por alguns anos. A otimização ainda era muito cara, pois para cada operação em um int, eles precisavam desmascarar e reaplicá-las. Atualmente, o java está no mesmo nível de C ao manipular números primitivos. Com desmascarar + máscara, provavelmente será> 30% mais lento.
R.Moeller

9

Não acredito que ninguém tenha mencionado o que considero o motivo mais importante: "int" é muito mais fácil de digitar do que "Inteiro". Eu acho que as pessoas subestimam a importância de uma sintaxe concisa. O desempenho não é realmente um motivo para evitá-los, porque na maioria das vezes quando se usa números estão em índices de loop, e incrementar e comparar esses custos não custa nada em um loop não trivial (se você estiver usando int ou Inteiro).

O outro motivo foi que você pode obter NPEs, mas isso é extremamente fácil de evitar com tipos de caixa (e é garantido que será evitado desde que você sempre os inicialize com valores não nulos).

A outra razão foi que (novo Long (1000)) == (novo Long (1000)) é falso, mas essa é apenas outra maneira de dizer que ".equals" não tem suporte sintático para tipos de caixa (ao contrário dos operadores <,> , =, etc), então voltamos ao motivo da "sintaxe mais simples".

Acho que o exemplo de loop não primitivo de Steve Yegge ilustra muito bem meu argumento: http://sites.google.com/site/steveyegge2/language-trickery-and-ejb

Pense sobre isso: com que freqüência você usa tipos de função em linguagens que possuem boa sintaxe para eles (como qualquer linguagem funcional, python, ruby ​​e até C) em comparação com java, onde é necessário simulá-los usando interfaces como Runnable e Callable e classes sem nome.


8

Algumas razões para não se livrar das primitivas:

  • Compatibilidade com versões anteriores.

Se for eliminado, qualquer programa antigo nem será executado.

  • Reescrita da JVM.

A JVM inteira teria que ser reescrita para suportar essa coisa nova.

  • Maior presença de memória.

Você precisaria armazenar o valor e a referência, que usa mais memória. Se você possui uma grande variedade de bytes, o uso bytede é significativamente menor do que o uso Bytede.

  • Problemas de ponteiro nulo.

Declarar int ique fazer coisas com iisso resultaria em nenhum problema, mas declarar Integer ie fazer o mesmo resultaria em um NPE.

  • Questões de igualdade.

Considere este código:

Integer i1 = 5;
Integer i2 = 5;

i1 == i2; // Currently would be false.

Seria falso. Os operadores teriam que estar sobrecarregados, e isso resultaria em uma grande reescrita das coisas.

  • Lento

Os wrappers de objetos são significativamente mais lentos que seus equivalentes primitivos.


i1 == i2; seria falso somente se i1> = 128. Assim, o exemplo atual é errado
Geniy

7

Os objetos são muito mais pesados ​​que os tipos primitivos; portanto, os tipos primitivos são muito mais eficientes que as instâncias de classes de wrapper.

Os tipos primitivos são muito simples: por exemplo, um int é de 32 bits, ocupa exatamente 32 bits de memória e pode ser manipulado diretamente. Um objeto Inteiro é um objeto completo, que (como qualquer objeto) deve ser armazenado no heap e só pode ser acessado através de uma referência (ponteiro) a ele. Provavelmente, também ocupa mais de 32 bits (4 bytes) de memória.

Dito isto, o fato de Java fazer uma distinção entre tipos primitivos e não primitivos também é um sinal de idade da linguagem de programação Java. As linguagens de programação mais recentes não têm essa distinção; o compilador dessa linguagem é inteligente o suficiente para descobrir por si mesmo se você estiver usando valores simples ou objetos mais complexos.

Por exemplo, em Scala não há tipos primitivos; existe uma classe Int para números inteiros e um Int é um objeto real (no qual você pode usar métodos etc.). Quando o compilador compila seu código, ele usa entradas primitivas nos bastidores, portanto, usar um Int é tão eficiente quanto usar um int primitivo em Java.


1
Eu teria assumido que o JRE seria "inteligente" o suficiente para fazer isso com as primitivas embrulhadas em Java também. Falhou.
Naftuli Kay

7

Além do que outros disseram, variáveis ​​locais primitivas não são alocadas do heap, mas na pilha. Mas os objetos são alocados da pilha e, portanto, precisam ser coletados como lixo.


3
Desculpe, isso está errado. Uma JVM inteligente pode fazer análise de escape em qualquer alocação de objeto e, se não puder escapar, alocá-lo na pilha.
usar o seguinte comando

2
Sim, isso está começando a ser um recurso das JVMs modernas. Em cinco anos, o que você diz será verdadeiro para a maioria das JVMs em uso. Hoje não é. Eu quase comentei sobre isso, mas decidi não comentar. Talvez eu devesse ter dito alguma coisa.
Eddie

6

Tipos primitivos têm muitas vantagens:

  • Código mais simples para escrever
  • O desempenho é melhor, pois você não está instanciando um objeto para a variável
  • Como eles não representam uma referência a um objeto, não há necessidade de procurar nulos.
  • Use tipos primitivos, a menos que você precise tirar proveito dos recursos de boxe.

5

É difícil saber que tipo de otimizações estão acontecendo ocultamente.

Para uso local, quando o compilador tiver informações suficientes para fazer otimizações, excluindo a possibilidade do valor nulo, espero que o desempenho seja o mesmo ou semelhante .

No entanto, matrizes de primitivas são aparentemente muito diferentes das coleções de primitivas em caixa. Isso faz sentido, uma vez que poucas otimizações são possíveis dentro de uma coleção.

Além disso, Integerpossui uma sobrecarga lógica muito maior em comparação com int: agora você precisa se preocupar se int a = b + c;lança ou não uma exceção.

Eu usaria as primitivas o máximo possível e confiaria nos métodos de fábrica e no autoboxing para me fornecer os tipos de caixas semanticamente mais poderosos quando necessários.


5
int loops = 100000000;

long start = System.currentTimeMillis();
for (Long l = new Long(0); l<loops;l++) {
    //System.out.println("Long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around Long: "+ (System.currentTimeMillis()- start));

start = System.currentTimeMillis();
for (long l = 0; l<loops;l++) {
    //System.out.println("long: "+l);
}
System.out.println("Milliseconds taken to loop '"+loops+"' times around long: "+ (System.currentTimeMillis()- start));

Milissegundos levados para fazer o loop '100000000' vezes em torno de Long: 468

Milissegundos levados para repetir o tempo '100000000' por muito tempo: 31

Em uma nota lateral, eu não me importaria de ver algo assim descobrir o caminho para Java.

Integer loop1 = new Integer(0);
for (loop1.lessThan(1000)) {
   ...
}

Onde o loop for incrementa automaticamente o loop1 de 0 a 1000 ou

Integer loop1 = new Integer(1000);
for (loop1.greaterThan(0)) {
   ...
}

Onde o loop for diminui automaticamente o loop1 1000 para 0.


2
  1. Você precisa de primitivas para executar operações matemáticas
  2. As primitivas ocupam menos memória conforme respondidas acima e têm melhor desempenho

Você deve perguntar por que o tipo de classe / objeto é necessário

A razão para ter um tipo de objeto é facilitar nossa vida quando lidamos com coleções. As primitivas não podem ser adicionadas diretamente à Lista / Mapa, em vez de você precisar escrever uma classe de wrapper. O tipo de classe Readymade Integer ajuda você aqui, além de ter muitos métodos utilitários como Integer.pareseInt (str)


2

Concordo com as respostas anteriores, o uso de objetos wrapper primitivos pode ser caro. Porém, se o desempenho não for crítico em seu aplicativo, você evitará estouros ao usar objetos. Por exemplo:

long bigNumber = Integer.MAX_VALUE + 2;

O valor de bigNumberé -2147483647 e você esperaria que fosse 2147483649. É um erro no código que seria corrigido ao fazer:

long bigNumber = Integer.MAX_VALUE + 2l; // note that '2' is a long now (it is '2L').

E bigNumberseria 2147483649. Às vezes, esses tipos de bugs são fáceis de serem esquecidos e podem levar a comportamentos ou vulnerabilidades desconhecidas (consulte CWE-190 ).

Se você usar objetos wrapper, o código equivalente não será compilado.

Long bigNumber = Integer.MAX_VALUE + 2; // Not compiling

Portanto, é mais fácil interromper esse tipo de problema usando objetos de wrapper primitivos.

Sua pergunta já está tão respondida, que respondo apenas para adicionar um pouco mais de informações não mencionadas anteriormente.


1

Porque o JAVA executa todas as operações matemáticas em tipos primitivos. Considere este exemplo:

public static int sumEven(List<Integer> li) {
    int sum = 0;
    for (Integer i: li)
        if (i % 2 == 0)
            sum += i;
        return sum;
}

Aqui, as operações de lembrete e unário mais não podem ser aplicadas no tipo Inteiro (Referência), o compilador executa o unboxing e realiza as operações.

Portanto, verifique quantas operações de caixa automática e unboxing acontecem no programa java. Desde então, leva tempo para executar essas operações.

Geralmente, é melhor manter argumentos do tipo Reference e resultado do tipo primitivo.


1

Os tipos primitivos são muito mais rápidos e requerem muito menos memória . Portanto, podemos preferir usá-los.

Por outro lado, a especificação atual da linguagem Java não permite o uso de tipos primitivos nos tipos parametrizados (genéricos), nas coleções Java ou na API Reflection.

Quando nosso aplicativo precisa de coleções com um grande número de elementos, devemos considerar o uso de matrizes com o tipo mais "econômico" possível.

* Para informações detalhadas, consulte a fonte: https://www.baeldung.com/java-primitives-vs-objects


0

Para ser breve: os tipos primitivos são mais rápidos e requerem menos memória que os in a box

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.