Quando usar primitivo vs classe em Java?

54

Eu vejo que Java tem booleano (classe) vs booleano (primitivo). Da mesma forma, há um número inteiro (classe) vs int (primitivo). Qual é a melhor prática sobre quando usar a versão primitiva versus a classe? Eu basicamente sempre deveria estar usando a versão da classe, a menos que eu tenha um motivo específico (de desempenho?) Para não? Qual é a maneira mais comum e aceita de usar cada um?

Casey Patton
fonte
Na minha opinião, essas não são aulas, são caixas. Eles estão disponíveis apenas para que você possa usar primitivas onde os objetos são necessários, ou seja, em coleções. Você não pode adicionar dois números inteiros (você pode fingir, mas realmente o java é o boxing automático | descompactando os valores para você).
Stonemetal

Respostas:

47

No item 5, do Java efetivo, Joshua Bloch diz

A lição é clara: prefira primitivas a primitivas in a box e cuidado com as caixas automáticas não intencionais .

Um bom uso para classes é ao usá-las como tipos genéricos (incluindo classes de coleção, como listas e mapas) ou quando você deseja transformá-las em outro tipo sem conversão implícita (por exemplo, Integerclasse possui métodos doubleValue()ou byteValue().

Edit: A razão de Joshua Bloch é:

// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
    }
    System.out.println(sum);
}

Este programa obtém a resposta certa, mas é muito mais lento do que deveria, devido a um erro tipográfico de um caractere. A variável sumé declarada como um em Longvez de a long, o que significa que o programa constrói cerca de 2 ^ 31 Longinstâncias desnecessárias (aproximadamente uma para cada vez que long ié adicionada à Long sum). Alterar a declaração da soma de Longpara longreduz o tempo de execução de 43 segundos para 6,8 segundos na minha máquina.

m3th0dman
fonte
2
Ajudaria se você listasse os motivos de Bloch, em vez de apenas citar sua conclusão!
vaughandroid
@Baqueta Eu editei o post. O motivo é o desempenho.
M3th0dman
Isso é um pouco mais claro, obrigado. Sinto-me justificado em postar minha própria resposta agora. :)
vaughandroid
Você pode achar interessante a arquitetura lmax - "Demorou um pouco mais de inteligência para subir outra ordem de magnitude. Há várias coisas que a equipe do LMAX achou úteis para chegar lá. Uma era escrever implementações personalizadas das coleções java que foram projetadas para ser amigável ao cache e cuidadoso com o lixo. Um exemplo disso é usar java longs primitivos como chaves hashmap com uma implementação de mapa baseada em matriz, especialmente escrita, apoiada . "
É parece algo JIT deve tratar
DeFreitas
28

A prática padrão é seguir as primitivas, a menos que você esteja lidando com genéricos (certifique-se de estar ciente de caixas automáticas e unboxing !).

Existem várias boas razões para seguir a convenção:

1. Você evita erros simples:

Existem alguns casos sutis e não intuitivos, que geralmente envolvem iniciantes. Até mesmo codificadores experientes escorregam e cometem esses erros algumas vezes (espero que isso seja seguido por palavrões quando eles depurarem o código e encontrarem o erro!).

O erro mais comum é usar em a == bvez de a.equals(b). As pessoas estão acostumadas a fazer uso de a == bprimitivas, portanto isso é fácil quando você está usando os wrappers Object.

Integer a = new Integer(2);
Integer b = new Integer(2);
if (a == b) { // Should be a.equals(b)
    // This never gets executed.
}
Integer c = Integer.valueOf(2);
Integer d = Integer.valueOf(2);
if (c == d) { // Should be a.equals(b), but happens to work with these particular values!
    // This will get executed
}
Integer e = 1000;
Integer f = 1000;
if (e == f) { // Should be a.equals(b)
    // Whether this gets executed depends on which compiler you use!
}

2. Legibilidade:

Considere os dois exemplos a seguir. A maioria das pessoas diria que o segundo é mais legível.

Integer a = 2;
Integer b = 2;
if (!a.equals(b)) {
    // ...
}
int c = 2;
int d = 2;
if (c != d) {
    // ...
}

3. Desempenho:

O fato é que é mais lento usar os wrappers Object para primitivas do que apenas usar as primitivas. Você está adicionando o custo da instanciação de objetos, chamadas de métodos etc. às coisas que usa em todo o lugar .

A citação de Knuth "... digamos cerca de 97% do tempo: a otimização prematura é a raiz de todo mal" não se aplica aqui. Ele estava falando sobre otimizações que tornam o código (ou sistema) mais complicado - se você concorda com o ponto 2, essa é uma otimização que torna o código menos complicado!

4. É a convenção:

Se você fizer escolhas estilísticas diferentes para 99% dos outros programadores Java, existem duas desvantagens:

  • Você encontrará o código de outras pessoas mais difícil de ler. 99% dos exemplos / tutoriais / etc lá fora usarão primitivos. Sempre que você ler um, terá a sobrecarga cognitiva extra de pensar em como ficaria no estilo com o qual está acostumado.
  • Outras pessoas acharão seu código mais difícil de ler. Sempre que você faz perguntas no Stack Overflow, você precisa filtrar as respostas / comentários, perguntando "por que você não está usando primitivas?". Se você não acredita em mim, basta olhar para as batalhas que as pessoas enfrentam, como posicionamento de colchetes, que nem sequer afeta o código gerado!

Normalmente eu listaria alguns contrapontos, mas honestamente não consigo pensar em nenhuma boa razão para não ir à convenção aqui!

vaughandroid
fonte
2
Não aconselha as pessoas a comparar objetos ==. Objetos devem ser comparados com equals().
Tulains Córdova
2
@ user61852 Eu não estava sugerindo isso como uma coisa a fazer, mas como um erro comum que é cometido! Devo deixar isso mais claro?
vaughandroid
Sim, você não menciona que os objetos devem ser comparados com equals()... você fornece uma solução alternativa para que comparar objetos com ==produza os resultados esperados.
Tulains Córdova
Bom ponto. Mudou isso.
precisa saber é o seguinte
Eu adicionei equals()ao segundo trecho de código e mudei meu voto.
Tulains Córdova
12

Normalmente eu vou com os primitivos. No entanto, uma peculiaridade de usar classes como Integere Booleané a possibilidade de atribuir nulla essas variáveis. Claro, isso significa que você tem que fazer nullverificações o tempo todo, mas ainda melhor para obter um NullPointerException do que ter erros de lógica, devido ao uso de alguns intou booleanvariável que não foi inicializado corretamente.

Obviamente, desde o Java 8, você pode (e provavelmente deveria) ir um passo além e, em vez de, por exemplo, Integerusar Optional<Integer>para variáveis ​​que podem ou não ter um valor.

Além disso, apresenta a possibilidade de usar nullpara atribuir a essas variáveis ​​um valor " desconhecido " ou " curinga ". Isso pode ser útil em algumas situações, por exemplo, na lógica ternária . Ou você pode verificar se um determinado objeto corresponde a algum modelo; nesse caso, você pode usar nullpara aquelas variáveis ​​no modelo que podem ter qualquer valor no objeto.

tobias_k
fonte
2
(Não foi meu voto negativo, mas ...) O Java já detecta variáveis ​​não inicializadas e não permite que você leia uma variável até que todo caminho de código que antecede seu uso tenha atribuído definitivamente um valor. Portanto, você não ganha muito com a capacidade de atribuir nullcomo padrão. Pelo contrário: seria melhor não "inicializar" a variável. Definir qualquer valor padrão, mesmo null, desliga o compilador ... mas também evita a detecção de falta de atribuição útil em todos os caminhos de código. Assim, um erro que o compilador poderia ter detectado desliza para o tempo de execução.
precisa saber é
@cHao Mas e se não houver um valor padrão sensível para inicializar a variável? Você pode configurá-lo como 0.0, ou -1, ou Integer.MAX_VALUE, ou False, mas, no final, você não sabe se esse é o valor padrão ou um valor real que foi atribuído a essa variável. Nos casos em que isso é importante, ter um nullvalor pode ser mais claro.
tobias_k
Não é mais claro, é mais fácil dizer ao compilador para não avisá-lo sobre lógica pouco clara até que um bug já tenha se propagado. : P Nos casos em que não há um padrão sensível, não inicialize a variável. Defina-o apenas quando tiver um valor sensato para colocar lá. Isso permite que o Java o interrompa no momento da compilação, se seu valor não estiver definido corretamente.
cHao
@cHao eu quis dizer, pode haver casos em que você não pode inicializar a variável e precisa lidar com ela em tempo de execução. Nesses casos, um "padrão" como "nulo", que pode ser claramente identificado como padrão, ou "não inicializado", pode ser melhor do que qualquer inicialização em tempo de compilação que também possa ser um valor válido.
tobias_k
Você tem esse caso em mente? Os casos em que consigo pensar estão nos limites da interface (por exemplo, como tipos de parâmetro ou retorno) ... mas mesmo assim, eles seriam muito melhores se agrupados. (Os nus nullvêm com uma série de problemas, incluindo paranóia nula.) Em uma função, uma variável que pode realmente não ser inicializada no ponto de uso normalmente indica casos não descobertos. (A análise de atribuição definida é simplista, portanto, são possíveis falsos positivos. Mas muitas vezes você pode resolvê-los simplificando a lógica.)
cHao
2

Nas palavras dos leigos:

Você usa os wrappers quando precisa adicionar itens às coleções.

As coleções não podem conter primitivas.

Tulains Córdova
fonte
0

O Java apresenta a autoboxing como m3th0dman apontou. Pense no nível mais baixo possível e você verá que a autoboxing (entrada ou saída) de um valor primitivo implicará ciclos de relógio gastos em algumas tarefas que você não precisa se estiver trabalhando com tipos de dados nativos em seu aplicativo.

Como regra geral, você deve tentar usar tipos de dados nativos sempre que possível.

Silvarion
fonte