DEV Community

Cassius Rocha
Cassius Rocha

Posted on

Tipos Primitivos X Classes Wrappers em Java


Introdução

Nas primeiras vezes em que coloquei as mãos em um código Java, logo estranhei a ocorrência desses dois tipos de variáveis para números inteiros:

  • int : escrita toda em caixa-baixa ou lowercase, se preferir.

  • Integer : escrita em PascalCase, como uma classe, além de ter a palavra Integer completa.

Tipos int eu já conhecia da linguagem C, que, até então, tinha sido a linguagem que eu mais havia utilizado na faculdade, sobretudo em disciplinas que envolvem estruturas de dados. Agora, Integer era a primeira vez que eu via, escrito assim inteirão.

Ao dar um Google, cheguei nessa definição inicial:

int é um tipo primitivo.
Integer é uma classe wrapper de int.

Em outras palavras:

“Em Java, wrapper é um objeto que empacota um tipo primitivo.”

Ok, mas o que isso quer dizer?

Se você também ainda não pegou 100% o que são classes wrapper em Java, não entendeu bem a diferença entre elas e tipos primitivos ou, até mesmo, está ouvindo falar dessas barbaridades pela primeira vez, não se preocupe!

Neste texto, vamos destrinchar essa definição em partes menores, passo a passo, para entender não só o que são tipos primitivos e wrappers, mas também conceitos que costumam confundir, como autoboxing e unboxing, e, finalmente, decidir quando vale a pena usar int ou Integer.

A ideia é que, ao final, você não só saiba a diferença entre primitivo e wrapper, mas também consiga aplicar esse conhecimento no dia a dia de programação.

Bora lá?


Afinal, o que são Tipos Primitivos?

Sabemos que Java é uma linguagem estaticamente tipada, ou seja, as variáveis devem ser explicitamente declaradas antes de serem usadas. Para declarar uma variável, definimos seu tipo e seu nome:

int idade;

No exemplo acima, o tipo int determina quais valores (números inteiros) a variável idade pode conter e quais operações podem ser realizadas com ela.

Os tipos primitivos representam os valores mais básicos que a linguagem pode manipular. Em Java, existem oito deles:

  • Números inteiros: byte, short, int, long
  • Números de ponto flutuante: float, double
  • Caracteres: char
  • Valores lógicos: boolean (true ou false)

Esses tipos são definidos diretamente pela linguagem e:

1. têm tamanho fixo em memória;
2. possuem valores padrão;
3. não são objetos e, por isso, não possuem métodos.

Vamos analisar essas três afirmações.

1. Tamanho fixo

Cada tipo primitivo tem tamanho em bytes definido pelo Java, garantindo consistência entre plataformas:

Tipo Tamanho
byte 1 byte
short 2 bytes
int 4 bytes
long 8 bytes
float 4 bytes
double 8 bytes
char 2 bytes
boolean 1 bit (representação interna varia)

2. Valores Padrão

Quando primitivos são declarados como variáveis de instâncias, ou seja, como variáveis que pertencem a um objeto, eles recebem um valor padrão se não forem inicializados.

No exemplo que vimos acima, a variável idade tem o valor padrão 0, uma vez que foi apenas declarada:

int idade;

Para alterar o valor padrão devemos atribuir um novo valor:

idade = 18;

Veja os valores padrão para cada Tipo Primitivo:

Tipo Valor padrão
byte, short, int, long 0
float, double 0.0
char '\u0000' (caractere nulo)
boolean false

3. Primitivos não são objetos

Recapitulando o que vimos até aqui sobre os primitivos:

  • são os valores mais básicos da linguagem;
  • têm tamanho fixo na memória;
  • possuem um valor padrão;

Agora vamos para a definição seguinte, que nomeia essa seção, talvez a mais importante do assunto:

“Primitivos não são objetos”

Você se lembra da relação entre classe, métodos e objeto?

Vamos repassar seus conceitos, de forma breve:

Classe

Classe é um modelo que define os atributos (variáveis) e comportamentos (métodos) que seus objetos terão.

Veja o exemplo da classe Pessoa:

class Pessoa { 

    String nome;     
    int idade;

}
Enter fullscreen mode Exit fullscreen mode

A classe Pessoa tem dois atributos: nome e idade.

Métodos

Em programação:

  • funções são blocos de código que executam uma tarefa específica.

  • métodos são funções declaradas dentro de classes, ou seja, são comportamentos específicos das classes em que estão definidas.

  • Em Java, como não é possível declarar uma função que não esteja associada a uma classe, todas as funções são métodos.

Logo:

“Em Java, métodos são blocos de código que executam uma tarefa específica de uma classe”.

Voltemos à classe Pessoa, agora vamos acrescentar o método falar() a ela:

class Pessoa {

    String nome;     
    int idade;

    void falar() {   
        System.out.println("Oi!");
    }

}
Enter fullscreen mode Exit fullscreen mode

Aqui, falar() é um método que não retorna nenhum valor, mas imprime “Oi!” ao ser chamado.

Objeto

Um objeto é cada instância de uma classe, ou seja, algo criado a partir de um molde, com valores próprios para seus atributos.

Vamos instanciar a classe Pessoa, definida acima:

Pessoa p1 = new Pessoa();  // cria um objeto da classe Pessoa
p1.nome = "Alice";
p1.idade = 30;
p1.falar();  // imprime “Oi!”
Enter fullscreen mode Exit fullscreen mode

Primeiro, instanciamos a classe Pessoa na variável p1.

Em seguida, atribuímos valores aos atributos do objeto.

Finalmente, chamamos o método falar.

Certo, agora que relembramos o que são classes, métodos e objetos, voltemos uma última vez à afirmação que abre esta seção:

"Primitivos não são objetos"

Ou seja, eles não são instâncias de nenhuma classe, por isso são PRIMITIVOS (barulho de explosão do conhecimento ao fundo).

Ora, então:

  • Primitivos não possuem métodos próprios.

Exatamente! E aqui chegamos num ponto crucial do texto:

A principal diferença entre um tipo primitivo e uma classe wrapper é que um primitivo não possui métodos.
Ao enveloparmos um tipo int, por exemplo, na classe Integer, trazemos métodos desta classe que podem ser úteis para trabalhar com nosso int primitivo (barulho de explosão do conhecimento ao fundo 2).

Vamos ver como isso acontece!

Wrappers

O que são?

Ao pé da letra, “wrapper” significa invólucro, é aquilo que envolve outra coisa.

Em Java, classes wrapper são aquelas cujos objetos contêm ou empacotam tipos primitivos. Ao instanciarmos um wrapper, o objeto criado contém um campo no qual podemos guardar um tipo primitivo.

Por exemplo, um wrapper Integer contém um campo do tipo int, ele “empacota” esse valor.

Veja todas as classes Wrapper em Java, para cada primitivo existe uma:

Tipo primitivo Classe Wrapper
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

Por que usar Wrappers?

Quando empacotamos um int, esse valor pode ser tratado como um objeto do tipo Integer. Como vimos, com isso é possível usarmos os métodos da classe Integer. É como se envolvêssemos o tipo primitivo em um manto especial, que traz novas habilidades. Com ele, o primitivo pode fazer mais coisas.

Seguem dois exemplos, para podermos visualizar melhor.

1. Uso em coleções

Em Java, coleções (como ArrayList, HashSet, HashMap) são estruturas que permitem armazenar e manipular grupos de objetos de forma dinâmica e flexível, sem a limitação de tamanho fixo dos arrays.

Elas são muito úteis quando precisamos lidar com listas de elementos cujo tamanho não sabemos de antemão ou que precisa mudar em tempo de execução.

O detalhe é que coleções não aceitam tipos primitivos, apenas objetos.

Por isso, se quisermos guardar inteiros em uma coleção, precisamos usar a classe wrapper Integer.

Veja o exemplo:

import java.util.ArrayList;
import java.util.List;

public class Exemplo1 {

    public static void main(String[] args) {

        // Não é possível: List<int> numeros = new ArrayList<>();
        List<Integer> numeros = new ArrayList<>();

        numeros.add(10);   // autoboxing: int → Integer
        numeros.add(20);
        numeros.add(null); // só objetos podem assumir null

        System.out.println("Números armazenados: " + numeros);

    }
}
Enter fullscreen mode Exit fullscreen mode

No código acima vemos as seguintes vantagens de Integer sobre int:

  • Conseguimos guardar valores inteiros em uma coleção, o que não seria possível com int.

  • Podemos armazenar o valor null, que representa ausência de valor, algo que um primitivo não aceita (lembra da tabela acima? Por padrão um int tem valor 0, o que é diferente de nenhum valor).

2. Métodos utilitários da classe Integer

Outra grande utilidade de usar wrappers é ter acesso a diversos métodos estáticos e de instância que ajudam a manipular valores inteiros. Se tiver esquecido a diferença entre esses tipos de métodos, dá uma espiada aqui. Mas, basicamente, métodos estáticos pertencem à classe e podem ser chamados sem criar um objeto, enquanto métodos de instância pertencem a cada objeto e só podem ser chamados a partir dele.

Veja:

public class ExemploInteger {

    public static void main(String[] args) {

        // Integer declarado explicitamente
        Integer wrapper = 42;

        // métodos de instância
        int resultado = wrapper.compareTo(100); // -1 porque 42 < 100
        boolean igual = wrapper.equals(42);     // true

        // método estático: converter String em int
        int numero = Integer.parseInt("123");
        System.out.println(numero); // 123

    }
}
Enter fullscreen mode Exit fullscreen mode

Analisando o exemplo acima.

O objeto wrapper chama dois métodos:

1. compareTo(Integer outro): compara o valor encapsulado no objeto com outro valor Integer. Retorna:

  • < 0 se o objeto for menor que o outro,
  • 0 se forem iguais,
  • > 0 se for maior.

2. equals(Object obj):

Verifica se o valor do objeto é igual ao valor de outro objeto.
Retorna:

  • true se os valores forem iguais,

  • false caso contrário.

  • Exemplo: wrapper.equals(42) retorna true.

Caso fossemos fazer essas comparações com valores primitivos int, teríamos que criar toda a lógica. Sim, é importante sabermos fazer isso! Mas, no dia-a-dia, podemos ganhar tempo conhecendo esses métodos que já estão prontinhos para o uso.

Em seguida, temos o método estático parseInt, chamado diretamente pela classe Integer, algo impossível para um int. Esse método converte uma String em um int.

Resumindo: o uso do do Wrapper Integer pode facilitar e acelerar operações com inteiros.

Vamos dar sequência para outro assunto relacionado aos wrappers:

Autoboxing e Unboxing

Desde o Java 5, não precisamos mais instanciar manualmente os wrappers. O compilador faz isso automaticamente para nós. Esse processo é chamado de autoboxing (quando um primitivo vira wrapper) e unboxing (quando um wrapper vira primitivo).

Antes e depois do Java 5:

// antes do Java 5 (conversão manual)
Integer x = new Integer(5);

// depois do Java 5 (autoboxing)
Integer y = 5;
Enter fullscreen mode Exit fullscreen mode

No segundo caso, o compilador entende que precisa transformar o int 5 em um Integer.

Unboxing automático:

Integer z = 10; // autoboxing
int w = z;      // unboxing automático
Enter fullscreen mode Exit fullscreen mode

Assim, valores podem ir de primitivos para wrappers e de wrappers para primitivos sem código extra da pessoa programadora.

Isso torna o código mais legível e prático. Porém, vale lembrar que cada conversão é um passo adicional, e em situações de alto desempenho pode ser relevante evitar autoboxing e unboxing excessivos.

Quando escolher primitivo ou wrapper?

Nem sempre faz sentido usar um wrapper, e nem sempre faz sentido usar apenas tipos primitivos. Cada um tem suas vantagens dependendo do contexto.

Use primitivos (int, double, boolean etc.) quando:

  • Performance e simplicidade são prioridade.

  • Você está fazendo cálculos numéricos simples.

  • A variável é local e não precisa de habilidades extras.

Exemplo:

int soma = 2 + 3;
System.out.println(soma); // 5
Enter fullscreen mode Exit fullscreen mode

Use wrappers (Integer, Double, Boolean etc.) quando precisar:

  • Armazenar em coleções genéricas (ArrayList, HashMap etc.), que só aceitam objetos.

  • Lidar com valores nulos, algo que primitivos não aceitam.

  • Usar métodos utilitários da classe wrapper, como compareTo, equals, parseInt, entre outros.

Exemplo:
import java.util.ArrayList;
import java.util.List;

public class ExemploWrapper {
    public static void main(String[] args) {
        List<Integer> lista = new ArrayList<>();
        lista.add(10);
        lista.add(null);       // permitido, só objetos aceitam null

        Integer x = 42;
        System.out.println(x.compareTo(100)); // -1
    }
}
Enter fullscreen mode Exit fullscreen mode

A ideia aqui é que primitivos são mais leves e rápidos, mas wrappers são mais flexíveis e poderosos.

Aspecto Tipo primitivo (int, double etc.) Classe Wrapper (Integer, Double etc.)
Performance Mais rápido e ocupa menos memória. Mais lento, pois é um objeto na heap.
Simplicidade Ideal para cálculos diretos e variáveis locais. Requer criação de objeto (implícita com autoboxing).
Valores nulos Não aceita null. Pode ser null, útil para representar ausência de valor.
Coleções Não pode ser usado em coleções genéricas. Pode ser usado em coleções (List, Map etc.).
Métodos utilitários Não possui métodos. Oferece métodos úteis como compareTo, equals, parseInt.
Autoboxing/Unboxing Não se aplica. Converte automaticamente entre primitivo e wrapper.

Conclusão

Ufa, bastante coisa, né?

Neste texto, vimos:

  • O que são tipos primitivos e suas características (tamanho fixo, valores padrão, não são objetos).

  • O que são wrappers e por que existem.

  • Exemplos práticos de como wrappers permitem:

  1. armazenar valores em coleções;

  2. manipular valores com métodos utilitários;

  3. representar null.

  • Como o autoboxing/unboxing simplifica o uso de wrappers no dia a dia.

  • Quando escolher entre primitivos (performance, simplicidade) e wrappers (flexibilidade, utilidade).

Esse conhecimento ajuda a escrever códigos mais conscientes, entendendo as escolhas de design da linguagem e quando usar cada recurso.

Se for o caso, vale a pena sempre voltar no texto para rever algum conceito, deixa ele salvo aí. E não podemos nos esquecer da prática, sem ela o conteúdo fica bem mais difícil de fixar - pelo menos para mim!

Além disso, ainda há temas relacionados que podemos explorar mais a fundo, como:

  • Diferença entre passagem por valor x referência.

  • Impacto de autoboxing/unboxing em performance em cenários reais.

  • Outras classes wrapper e truques úteis em coleções.

Mas aí é papo para um outro texto!

Até lá.

PS: encontrou alguma inconsistência ou erro no texto? Me avisa, por favor :)

Top comments (0)