Tipos de datos en Java: primitivos y de referencia

📅 Actualizado en febrero 2026 ✍️ Ángel López ⏱️ 18 min de lectura ✓ Nivel principiante ★ ★ ★ ★ ★ (5/5)

📦 ¿Qué son los tipos de datos en Java?

Un tipo de dato define la naturaleza de la información que una variable puede almacenar y las operaciones que se pueden realizar sobre ella. En Java, cada variable, cada parámetro de método y cada expresión tiene un tipo de dato asociado que el compilador verifica antes de ejecutar el programa.

Los tipos de datos en Java se dividen en dos grandes familias:

Tipos primitivos — son los 8 tipos básicos integrados en el lenguaje (int, double, boolean, char, byte, short, long, float). Almacenan directamente el valor en la pila de memoria (stack).

Tipos de referencia — son todos los demás: clases, interfaces, arrays y enumeraciones. No almacenan el valor directamente, sino una referencia (dirección de memoria) al objeto que reside en el heap.

💡
Concepto clave: Java es un lenguaje fuertemente tipado (strongly typed). Esto significa que toda variable debe declararse con un tipo antes de usarse, y el compilador impide asignar valores incompatibles. Esta característica previene una enorme cantidad de errores en tiempo de ejecución.

🔒 Java como lenguaje de tipado estático

A diferencia de lenguajes como Python o JavaScript, donde el tipo de una variable se determina en tiempo de ejecución (tipado dinámico), Java emplea tipado estático: el tipo de cada variable se establece en tiempo de compilación y no puede cambiar después.

Java
int edad = 25;           // Correcto: int almacena un entero
edad = "veinticinco";    // ERROR de compilación: tipo incompatible

double precio = 19.99;    // Correcto: double almacena decimales
boolean activo = true;    // Correcto: boolean almacena true/false

Esta rigidez tiene ventajas importantes: el compilador detecta errores de tipo antes de que el programa se ejecute, el código es más legible porque se sabe qué contiene cada variable, y la JVM puede optimizar mejor el rendimiento al conocer los tipos de antemano.

🧱 Los 8 tipos primitivos de Java

Java define exactamente 8 tipos de datos primitivos. Son los bloques más elementales del lenguaje: no son objetos, no tienen métodos y se almacenan directamente en la pila de memoria (stack), lo que los hace extremadamente eficientes.

Tipo Categoría Tamaño Rango de valores Valor por defecto
byte Entero 1 byte (8 bits) -128 a 127 0
short Entero 2 bytes (16 bits) -32.768 a 32.767 0
int Entero 4 bytes (32 bits) -2.147.483.648 a 2.147.483.647 0
long Entero 8 bytes (64 bits) -9,2 × 1018 a 9,2 × 1018 0L
float Decimal 4 bytes (32 bits) ±3,4 × 1038 (precisión ~7 dígitos) 0.0f
double Decimal 8 bytes (64 bits) ±1,7 × 10308 (precisión ~15 dígitos) 0.0d
boolean Lógico 1 bit* true o false false
char Carácter 2 bytes (16 bits) '\u0000' a '\uffff' (0 a 65.535) '\u0000'

* El tamaño real de boolean en memoria depende de la implementación de la JVM; aunque solo necesita 1 bit, en la práctica suele ocupar al menos 1 byte.

🔢 Tipos enteros: byte, short, int y long

Los cuatro tipos enteros de Java almacenan números sin parte decimal. Todos usan representación en complemento a dos, lo que permite representar tanto valores positivos como negativos.

▶️ int: el tipo entero por defecto

El tipo int es el más utilizado para números enteros. Con 4 bytes puede almacenar valores de aproximadamente ±2.100 millones, lo cual cubre la inmensa mayoría de los casos de uso.

Java
int poblacionMundial = 8_000_000_000;  // ERROR: excede el rango de int
int edad = 30;
int temperatura = -15;
int hexadecimal = 0xFF;               // 255 en hexadecimal
int binario = 0b1010;                 // 10 en binario
int conSeparador = 1_000_000;         // Separador _ para legibilidad (Java 7+)

▶️ long: para números grandes

Cuando el rango de int no es suficiente, se usa long. Los literales de tipo long se marcan con el sufijo L (se recomienda la L mayúscula para no confundirla con el número 1).

Java
long poblacionMundial = 8_000_000_000L;    // Correcto con sufijo L
long distanciaEstrellas = 9_460_730_472_580_800L; // Año luz en metros

🔹 byte y short: ahorro de memoria

Los tipos byte y short se emplean cuando la memoria es un recurso crítico, por ejemplo en arrays muy grandes donde cada byte cuenta.

Java
byte nivelBateria = 100;     // Rango: -128 a 127
short alturaMetros = 8849;    // Everest: cabe en short (-32.768 a 32.767)

// En un array de 10 millones de edades:
byte[] edades = new byte[10_000_000];  // 10 MB en vez de 40 MB con int[]
Buena práctica: Usa int como tipo entero por defecto. Recurre a long solo cuando necesites valores superiores a ±2.100 millones, y a byte/short solo en situaciones donde la optimización de memoria sea medible y necesaria.

🔬 Tipos decimales: float y double

Los tipos de coma flotante representan números con parte decimal siguiendo el estándar IEEE 754. La diferencia clave entre ambos es la precisión.

Java
float pi = 3.14159f;         // Sufijo f obligatorio para float
double piPreciso = 3.141592653589793;  // double: ~15 dígitos de precisión

// Cuidado con la precisión de float
float resultado = 0.1f + 0.2f;
System.out.println(resultado);  // 0.3000000‍0447... (no exactamente 0.3)

// Para cálculos financieros: usar BigDecimal
import java.math.BigDecimal;
BigDecimal precio = new BigDecimal("19.99");
BigDecimal iva = new BigDecimal("1.21");
BigDecimal total = precio.multiply(iva);  // 24.1879 exacto
⚠️
Error frecuente: Nunca uses float ni double para representar dinero o valores que requieran exactitud decimal. Los tipos de coma flotante tienen errores de redondeo inherentes al estándar IEEE 754. Para cálculos financieros, usa siempre BigDecimal.

Además, float y double pueden representar valores especiales: Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY y Float.NaN (Not a Number, resultado de operaciones indefinidas como 0.0/0.0).

⚡ El tipo boolean

El tipo boolean es el más simple de todos: solo admite dos valores, true y false. Es fundamental para las estructuras de control del programa.

Java
boolean esMayorDeEdad = true;
boolean tienePermiso = false;
boolean puedeEntrar = esMayorDeEdad && tienePermiso;  // false

int edad = 20;
boolean esAdulto = edad >= 18;  // true (resultado de expresión relacional)

// Uso en control de flujo
if (esAdulto) {
    System.out.println("Acceso permitido");
}

A diferencia de C o C++, en Java no se puede usar un entero como boolean. La expresión if (1) produce un error de compilación. Solo se aceptan expresiones que evalúen a true o false.

🔤 El tipo char

El tipo char almacena un único carácter Unicode de 16 bits (UTF-16). Se escribe entre comillas simples, a diferencia de los String que usan comillas dobles.

Java
char letra = 'A';
char unicode = '\u00F1';        // ñ en Unicode
char emoji = '\u2764';          // ❤
char numero = 65;               // 'A' (valor numérico Unicode)

// char es numérico: se puede operar con él
char siguiente = (char) ('A' + 1);  // 'B'
int valorAscii = 'Z';                 // 90 (widening automático)

🏗️ Tipos de referencia

Todo lo que no es un tipo primitivo en Java es un tipo de referencia. Esto incluye las clases (como String, Scanner, ArrayList), las interfaces, los arrays y las enumeraciones. La variable no contiene el objeto en sí, sino una referencia (dirección de memoria) que apunta al objeto almacenado en el heap.

Java
// String: el tipo de referencia más usado
String nombre = "Ada Lovelace";
int longitud = nombre.length();  // 12 — los objetos tienen métodos

// Arrays: colecciones de tamaño fijo
int[] numeros = {10, 20, 30, 40, 50};
String[] ciudades = {"Madrid", "Barcelona", "Sevilla"};

// Clases propias
public class Producto {
    String nombre;
    double precio;
    int stock;
}

Producto laptop = new Producto();  // laptop es una referencia al objeto
laptop.nombre = "MacBook Pro";
laptop.precio = 2499.99;

Un aspecto fundamental de los tipos de referencia es que pueden tener el valor null, que indica que la variable no apunta a ningún objeto. Intentar usar un método sobre una referencia null provoca la temida NullPointerException.

Java
String texto = null;
int len = texto.length();  // ¡NullPointerException en tiempo de ejecución!

⚖️ Primitivos vs. referencia: diferencias clave

Característica Tipos primitivos Tipos de referencia
Almacenamiento Valor directo en el stack Referencia en stack → objeto en heap
Valor null ❌ No permitido ✅ Permitido
Métodos ❌ No tienen ✅ Tienen métodos
Comparación == compara valores == compara referencias; .equals() compara contenido
Rendimiento Más rápido (acceso directo) Más lento (indirección + garbage collector)
Valor por defecto 0, 0.0, false, '\u0000' null
Uso en genéricos ❌ No (se usa wrapper) ✅ Sí
⚠️
Error frecuente: Comparar Strings con == en vez de .equals(). El operador == compara las referencias (direcciones de memoria), no el contenido. Dos objetos String pueden contener el mismo texto pero residir en direcciones diferentes. Siempre usa str1.equals(str2) para comparar el contenido de Strings.

🔄 Conversión de tipos (casting)

La conversión de tipos permite transformar un valor de un tipo a otro. Java distingue dos tipos de conversión entre primitivos:

▶️ Conversión implícita (widening)

Ocurre automáticamente cuando se asigna un tipo más pequeño a uno más grande. No hay pérdida de información.

Java
// Cadena de widening: byte → short → int → long → float → double
int entero = 100;
long grande = entero;      // int → long (automático)
float decimal = grande;    // long → float (automático, posible pérdida de precisión)
double preciso = decimal;  // float → double (automático)

char c = 'A';
int ascii = c;             // char → int (automático): 65

▶️ Conversión explícita (narrowing)

Se requiere un cast explícito (operador de casting) para convertir un tipo grande a uno más pequeño. Puede haber pérdida de información.

Java
double precio = 49.99;
int precioEntero = (int) precio;  // 49 — se pierde la parte decimal

int valor = 300;
byte b = (byte) valor;            // 44 — overflow: 300 excede el rango de byte

long grande = 123456789L;
int reducido = (int) grande;      // 123456789 — cabe en int, sin pérdida
💡
Concepto clave: La conversión entre String y tipos numéricos no es un cast, sino una operación de parsing. Se usa Integer.parseInt("42") para convertir String a int, y String.valueOf(42) para convertir int a String.

🔹 Conversión con String

Java
// String → número
int n = Integer.parseInt("42");
double d = Double.parseDouble("3.14");
boolean b = Boolean.parseBoolean("true");

// Número → String
String s1 = String.valueOf(42);
String s2 = Integer.toString(42);
String s3 = "" + 42;  // Concatenación — funciona pero menos elegante

📦 Autoboxing, unboxing y wrapper classes

Cada tipo primitivo tiene una clase wrapper (envoltorio) correspondiente que lo encapsula como un objeto. Esto es necesario porque las colecciones genéricas de Java (ArrayList, HashMap, etc.) solo trabajan con objetos, no con primitivos.

Primitivo Clase wrapper Método de parsing
byteByteByte.parseByte()
shortShortShort.parseShort()
intIntegerInteger.parseInt()
longLongLong.parseLong()
floatFloatFloat.parseFloat()
doubleDoubleDouble.parseDouble()
booleanBooleanBoolean.parseBoolean()
charCharacter

Desde Java 5, el compilador realiza automáticamente la conversión entre primitivos y sus wrappers:

Java
// Autoboxing: primitivo → wrapper (automático)
Integer num = 42;              // int 42 → objeto Integer
Double precio = 19.99;         // double 19.99 → objeto Double

// Unboxing: wrapper → primitivo (automático)
int valor = num;               // Integer → int
double p = precio;             // Double → double

// Uso práctico: ArrayList solo acepta objetos
ArrayList<Integer> lista = new ArrayList<>();
lista.add(10);                 // Autoboxing: int 10 → Integer
lista.add(20);
int primero = lista.get(0);    // Unboxing: Integer → int
⚠️
Error frecuente: El unboxing de un wrapper null lanza NullPointerException. Si haces Integer num = null; int x = num; el programa se rompe en tiempo de ejecución porque Java intenta extraer el valor int de un objeto que no existe.

🛒 Ejemplo integrador: sistema de inventario

El siguiente programa integra todos los tipos de datos vistos en un sistema de inventario de productos, mostrando cómo elegir el tipo adecuado para cada dato:

Java
import java.math.BigDecimal;
import java.util.ArrayList;

public class SistemaInventario {

    // Cada tipo de dato elegido por una razón específica
    private long codigoProducto;       // long: códigos de barras grandes
    private String nombre;             // String: texto variable
    private BigDecimal precio;         // BigDecimal: precisión monetaria
    private int stock;                 // int: cantidad suficiente para stock
    private double peso;               // double: peso en kg con decimales
    private char categoria;            // char: 'A', 'B', 'C' (clasificación)
    private boolean activo;            // boolean: disponible o no
    private byte valoracion;           // byte: 0-5 estrellas (rango pequeño)

    public SistemaInventario(long codigo, String nombre,
                             BigDecimal precio, int stock) {
        this.codigoProducto = codigo;
        this.nombre = nombre;
        this.precio = precio;
        this.stock = stock;
        this.activo = stock > 0;    // boolean desde expresión
        this.valoracion = 0;
    }

    public BigDecimal calcularValorInventario() {
        return precio.multiply(new BigDecimal(stock));
    }

    public void vender(int cantidad) {
        if (cantidad > stock) {
            System.out.println("Stock insuficiente");
            return;
        }
        stock -= cantidad;
        activo = stock > 0;
    }

    public static void main(String[] args) {
        SistemaInventario producto = new SistemaInventario(
            8_414_532_001_234L,
            "Teclado Mecánico RGB",
            new BigDecimal("89.99"),
            150
        );

        producto.peso = 0.85;
        producto.categoria = 'A';
        producto.valoracion = 5;

        System.out.println("Producto: " + producto.nombre);
        System.out.println("Código: " + producto.codigoProducto);
        System.out.println("Precio: " + producto.precio + " €");
        System.out.println("Valor inventario: " +
                           producto.calcularValorInventario() + " €");
        System.out.println("Activo: " + producto.activo);

        // Conversión de tipos en acción
        int precioRedondeado = producto.precio.intValue();  // 89
        String codigoStr = Long.toString(producto.codigoProducto);
        System.out.println("Precio redondeado: " + precioRedondeado);
        System.out.println("Código como texto: " + codigoStr);
    }
}

✏️ Ejercicios prácticos

Ejercicio 1: Declaración y rangos

Escribe un programa que declare una variable de cada tipo primitivo, le asigne un valor apropiado y muestre por consola el valor y el tamaño de cada tipo usando las constantes MIN_VALUE y MAX_VALUE de las clases wrapper.

Ejercicio 2: Conversión de tipos

Escribe un programa que lea un precio como String (por ejemplo "29.99"), lo convierta a double, aplique un descuento del 15%, y muestre el resultado redondeado a 2 decimales. Luego convierte el precio final a int (truncando) y muéstralo también.

Ejercicio 3: Autoboxing y colecciones

Crea un programa que almacene las notas de 5 estudiantes en un ArrayList<Double>, calcule la media, determine la nota máxima y mínima, y muestre cuántos estudiantes han aprobado (nota >= 5.0). Usa autoboxing/unboxing de forma natural.

Ejercicio 4: Desbordamiento y valores especiales

Escribe un programa que demuestre: (a) el desbordamiento (overflow) de un int al sumar 1 a Integer.MAX_VALUE, (b) la pérdida de precisión al convertir un long grande a float, y (c) los valores especiales POSITIVE_INFINITY y NaN de double. Muestra los resultados por consola con comentarios explicativos.

❓ Preguntas frecuentes sobre Tipos de datos en Java: primitivos y de referencia

Las dudas más comunes respondidas de forma clara y directa.

Java tiene exactamente 8 tipos de datos primitivos: byte, short, int, long, float, double, boolean y char. Cada uno tiene un tamaño fijo en memoria y un rango de valores definido, y son iguales en todas las plataformas donde se ejecute Java.
int es un tipo primitivo que almacena directamente el valor numérico en la pila (stack) y ocupa 4 bytes. Integer es una clase wrapper (envoltorio) que encapsula un int dentro de un objeto almacenado en el heap. Integer puede ser null, tiene métodos útiles (parseInt, compareTo...) y se puede usar en colecciones genéricas como ArrayList<Integer>, mientras que int no.
El autoboxing es la conversión automática que realiza el compilador de Java desde un tipo primitivo a su clase wrapper correspondiente. Por ejemplo, al escribir Integer num = 42, Java convierte automáticamente el int 42 en un objeto Integer. El proceso inverso se llama unboxing. Esta característica fue introducida en Java 5.
En la mayoría de los casos se recomienda usar double porque ofrece mayor precisión (64 bits frente a 32 bits de float). Se usa float cuando la memoria es crítica, como en arrays muy grandes de valores decimales, o cuando se trabaja con APIs que requieren float específicamente. Para cálculos financieros, ninguno de los dos es adecuado: se debe usar BigDecimal.
Si se intenta asignar un literal que excede el rango en tiempo de compilación, el compilador dará un error. Si el desbordamiento ocurre en tiempo de ejecución (por ejemplo, sumar 1 a Integer.MAX_VALUE), Java no lanza excepción: el valor "da la vuelta" (overflow) y se convierte en el valor mínimo del rango. Por eso es importante elegir el tipo adecuado y validar los rangos.
Sí, se puede convertir un String a int usando Integer.parseInt("123") o Integer.valueOf("123"). La diferencia es que parseInt devuelve un int primitivo y valueOf devuelve un objeto Integer. Si el String no contiene un número válido, ambos métodos lanzan NumberFormatException.
String no es un tipo primitivo porque es una clase del paquete java.lang que representa una secuencia de caracteres como un objeto. A diferencia de los primitivos, String tiene métodos (length(), substring(), equals()...), puede ser null, se almacena en el heap y participa del String Pool para optimizar memoria. Aunque Java le da un trato especial permitiendo crear strings con literales entre comillas dobles, internamente sigue siendo un objeto.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Tipos de datos en Java: primitivos y de referencia? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!