📦 ¿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.
🔒 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.
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.
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).
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.
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[]
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.
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.30000000447... (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
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.
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.
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.
// 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.
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í |
== 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.
// 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.
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
Integer.parseInt("42") para convertir String a int, y String.valueOf(42) para convertir int a String.🔹 Conversión con String
// 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 |
|---|---|---|
byte | Byte | Byte.parseByte() |
short | Short | Short.parseShort() |
int | Integer | Integer.parseInt() |
long | Long | Long.parseLong() |
float | Float | Float.parseFloat() |
double | Double | Double.parseDouble() |
boolean | Boolean | Boolean.parseBoolean() |
char | Character | — |
Desde Java 5, el compilador realiza automáticamente la conversión entre primitivos y sus wrappers:
// 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
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:
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.
💬 Foro de discusión
¿Tienes dudas sobre Tipos de datos en Java: primitivos y de referencia? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!