4. Variables y tipos primitivos
Ficha de la sección
| Campo | Valor |
|---|---|
| Unidad de Programación | UD1 — Fundamentos de Programación |
| Resultado de Aprendizaje | RA1 — Reconoce la estructura de un programa informático, identificando y relacionando los elementos propios del lenguaje de programación utilizado |
| Duración orientativa | Aproximadamente 4 de las 28 horas totales de UD1 |
| Criterios de Evaluación implicados | CE 1.4 (Se han identificado los distintos tipos de variables y la utilidad específica de cada uno) y CE 1.5 (Se ha modificado el código de un programa para crear y utilizar variables) |
| Encaja en el roadmap | Ya sabes montar el esqueleto de una clase con su main (Sección 3). Ahora toca llenar ese esqueleto con datos reales: esta sección es la base de absolutamente todo lo que vendrá después, desde las operaciones de la Sección 7 hasta las colecciones de UD6 |
Antes de empezar
En la Sección 1 usamos la cocina como analogía para entender qué es un algoritmo. Vamos a tirar del mismo hilo: si tu programa es una cocina, las variables son los recipientes donde guardas los ingredientes mientras cocinas. Y aquí está el matiz importante que distingue a Java de otros lenguajes: no puedes usar cualquier recipiente para cualquier cosa. No guardas una pizca de sal en una garrafa de 5 litros, ni intentas meter un caldo entero en una cucharilla de café. Cada recipiente tiene una capacidad fija, y usar el que no toca, o bien desperdicia espacio, o bien directamente no cabe.
Eso es exactamente lo que son los tipos primitivos en Java: recipientes de tamaño fijo y conocido de antemano, pensados cada uno para un tipo de dato concreto. Vamos a conocerlos todos.
4.1. Los ocho tipos primitivos
Java tiene exactamente ocho tipos primitivos, ni uno más ni uno menos, agrupados en cuatro familias:
flowchart TD
T["8 tipos primitivos de Java"] --> ENT["Números enteros"]
T --> DEC["Números decimales\n(coma flotante)"]
T --> LOG["Lógico"]
T --> TXT["Carácter"]
ENT --> BYTE["byte (8 bits)"]
ENT --> SHORT["short (16 bits)"]
ENT --> INT["int (32 bits)"]
ENT --> LONG["long (64 bits)"]
DEC --> FLOAT["float (32 bits)"]
DEC --> DOUBLE["double (64 bits)"]
LOG --> BOOL["boolean"]
TXT --> CHAR["char (16 bits, Unicode)"]
style ENT fill:#1565c0,color:#fff,stroke:#0d47a1
style DEC fill:#2e7d32,color:#fff,stroke:#1b5e20
style LOG fill:#e65100,color:#fff,stroke:#bf360c
style TXT fill:#6a1b9a,color:#fff,stroke:#4a148c
| Tipo | Tamaño | Rango aproximado | Analogía de capacidad (cocina) | Uso típico |
|---|---|---|---|---|
byte |
8 bits | -128 a 127 | Cucharilla de café: solo para cantidades minúsculas | Datos muy pequeños, ficheros binarios (UD5) |
short |
16 bits | -32.768 a 32.767 | Vaso de chupito: algo más de capacidad, poco uso práctico hoy | Legado; en código moderno casi siempre se usa int en su lugar |
int |
32 bits | aprox. ±2.147 millones | Jarra estándar de cocina: la que usas el 90% de las veces | El entero por defecto de Java |
long |
64 bits | aprox. ±9,2 trillones | Garrafa grande de 5 litros: para cantidades enormes | Marcas de tiempo, identificadores grandes, cálculos con cifras muy altas |
float |
32 bits | ~7 dígitos de precisión decimal | Báscula de cocina básica | Poco usado hoy salvo con restricciones de memoria |
double |
64 bits | ~15 dígitos de precisión decimal | Báscula de precisión de laboratorio | El decimal por defecto de Java |
boolean |
valor lógico | true / false |
El interruptor de la luz | Condiciones, banderas, resultados lógicos |
char |
16 bits | Un carácter Unicode | Una ficha de Scrabble | Guardar exactamente un carácter: letra, dígito o símbolo |
Dato clave sobre
floatydouble. Sus nombres engañan a mucha gente: "double" no significa "el doble de grande en valor", sino doble precisión (frente a la precisión simple defloat), según el estándar internacional IEEE 754 que Java sigue al pie de la letra. En la práctica:doubleno solo ocupa más espacio, también es más exacto. Por eso, salvo que tengas una razón de peso (por ejemplo, memoria muy limitada), usarásdoublecasi siempre que necesites decimales.Dato clave sobre
char. Uncharse escribe siempre entre comillas simples:'A'. Si usas comillas dobles ("A"), ya no es unchar, es unString(una secuencia de caracteres, aunque solo tenga uno), que veremos en detalle en la Sección 5. Confundir ambos es uno de los primeros tropiezos clásicos, y el compilador te lo hará saber sin piedad.
4.2. Declaración, inicialización y ámbito
Trabajar con una variable en Java implica, en general, dos pasos que puedes hacer por separado o juntos:
- Declarar una variable es decirle a Java "quiero un recipiente de este tipo, con este nombre". En ese momento, el recipiente existe, pero puede estar vacío.
- Inicializar una variable es meterle un valor dentro por primera vez.
Ámbito (scope): ¿desde dónde se puede usar una variable?
El ámbito de una variable es la región del código donde esa variable existe y se puede usar. En Java, ese ámbito viene marcado por los bloques { } que ya conociste en la Sección 3: una variable declarada dentro de un bloque solo existe desde el punto en que se declara hasta el cierre de ese mismo bloque. En cuanto el bloque se cierra, la variable desaparece, como si nunca hubiera existido.
flowchart TB
subgraph M["public static void main(String[] args) { ... }"]
direction TB
A["int edad = 25;\n← visible desde aquí en adelante, dentro de este bloque"]
subgraph B["{ ... } un bloque interior\n(por ejemplo, dentro de un if — lo veremos en UD3)"]
direction TB
C["Aquí SÍ se puede usar 'edad'"]
D["int nota = 8;\n← solo visible dentro de este bloque interior"]
end
E["Aquí YA NO se puede usar 'nota':\nha salido de su ámbito"]
end
style A fill:#1565c0,color:#fff,stroke:#0d47a1
style D fill:#e65100,color:#fff,stroke:#bf360c
style E fill:#b71c1c,color:#fff,stroke:#7f0000
Nota para el aula. Todavía no hemos visto estructuras como
ifo bucles (llegan en UD3), así que por ahora tu único bloque será el del propiomain. Pero el concepto de ámbito es tan importante que conviene fijarlo ya: en cuanto empieces a anidar bloques dentro demain, esta regla del "lo que se declara dentro, muere dentro" te va a explicar de golpe varios errores de compilación que, si no, parecerían misteriosos.
4.3. Inferencia con var (Java 10+)
Desde Java 10, puedes sustituir el tipo explícito por la palabra var, y dejar que el compilador infiera el tipo a partir del valor que le asignas:
Mito frecuente, y muy importante de desmontar. "Con
var, la variable puede cambiar de tipo más adelante, como en JavaScript o Python." Completamente falso. Java sigue siendo, convaro sin él, un lenguaje de tipado estático: el compilador decide el tipo real en el momento de la compilación, observando el valor inicial, y ese tipo queda fijado para siempre en esa variable.var edad = 25;es, a todos los efectos posteriores, exactamente lo mismo queint edad = 25;. Si después intentasedad = "veinticinco";, el compilador te lo va a rechazar con el mismo error que si hubieras escritointexplícitamente.varno es "tipado dinámico disfrazado": es simplemente ahorrarte escribir un tipo que ya es obvio por el valor que le das.
Dos reglas estrictas sobre var que conviene tener claras desde ya:
| Regla | Ejemplo que falla | Por qué |
|---|---|---|
var exige inicialización inmediata |
var x; |
El compilador necesita el valor inicial para poder inferir el tipo; sin él, no tiene de qué tirar |
var solo se usa en variables locales |
var como campo de una clase o como tipo de un parámetro de método |
Es una limitación del propio lenguaje (JEP 286): fuera de variables locales dentro de un método, var no está permitido |
4.4. Literales numéricos modernos
Java moderno incorpora tres formas de escribir literales numéricos que hacen tu código más legible o más adecuado a ciertos contextos:
| Literal | Ejemplo | Valor decimal equivalente | Para qué se usa |
|---|---|---|---|
Separador _ |
1_000_000 |
1000000 | Pura legibilidad: el guion bajo se ignora al compilar, solo ayuda al ojo humano a contar ceros |
Hexadecimal 0x |
0xFF |
255 | Colores (RGB), direcciones de memoria, máscaras de bits |
Binario 0b |
0b1010 |
10 | Trabajar directamente a nivel de bits, banderas binarias |
Dato clave. El separador
_se puede combinar con hexadecimal y binario sin ningún problema (0b1010_1010), y es especialmente útil ahí, porque agrupar los bits de cuatro en cuatro u ocho en ocho hace que un número binario largo deje de ser una sopa de ceros y unos imposible de leer.
4.5. Sufijos de tipo
Por defecto, Java interpreta cualquier literal entero como int y cualquier literal decimal como double. Si quieres un literal de un tipo distinto, necesitas indicárselo con un sufijo:
| Sufijo | Tipo que indica | Ejemplo | ¿Es obligatorio? |
|---|---|---|---|
L (evita la minúscula l, se confunde con el 1) |
long |
10_000_000_000L |
Sí, cuando el valor no cabe en el rango de un int |
f / F |
float |
3.14f |
Sí, siempre que quieras que el literal sea float |
d / D |
double |
3.14d |
No, es opcional: los literales decimales ya son double por defecto |
| Error típico | Ejemplo | Mensaje habitual del compilador | Solución |
|---|---|---|---|
Olvidar la L en un long grande |
long x = 10000000000; |
error: integer number too large |
long x = 10_000_000_000L; |
Olvidar la f en un float |
float precio = 19.99; |
error: incompatible types: possible lossy conversion from double to float |
float precio = 19.99f; |
Confundir char con String |
char letra = "A"; |
error: incompatible types: String cannot be converted to char |
char letra = 'A'; |
4.6. Valores por defecto de campos (frente a variables locales no inicializadas)
Aquí llega una de las distinciones que más primeros dolores de cabeza provoca, y que conviene fijar bien desde ya, aunque su explicación completa (qué es exactamente un "campo") llegue en UD4.
- Un campo (una variable declarada directamente dentro de una clase, fuera de cualquier método) recibe automáticamente un valor por defecto si no lo inicializas explícitamente.
- Una variable local (declarada dentro de un método, como todo lo que hemos visto hasta ahora dentro de
main) nunca recibe un valor por defecto. Si intentas usarla antes de asignarle un valor, el compilador te lo impide directamente.
| Campos (atributos de una clase) | Variables locales (dentro de un método) | |
|---|---|---|
| ¿Reciben un valor por defecto? | Sí, automáticamente | No, nunca |
| ¿Qué ocurre si se usan sin inicializar? | Toman su valor por defecto sin avisar | Error de compilación: "variable might not have been initialized" |
| ¿Dónde se explican en profundidad? | UD4 — Clases y Objetos Propios | Aquí mismo, en esta sección |
| Tipo | Valor por defecto (solo aplica a campos) |
|---|---|
byte, short, int |
0 |
long |
0L |
float |
0.0f |
double |
0.0d |
boolean |
false |
char |
'\u0000' (el carácter nulo, no imprimible) |
Nota para el aula. Por qué esta diferencia de comportamiento no es un capricho: Java asume que, si declaras un campo, es razonable que quede "listo para usarse" desde el instante en que se crea el objeto, aunque sea con un valor neutro. Pero dentro de un método, obligarte a inicializar tus variables locales explícitamente es una medida de seguridad del lenguaje: evita que uses, por descuido, un valor que no pusiste tú a propósito. Es Java protegiéndote de ti mismo, y con el tiempo lo vas a agradecer.
Programa completo: todas las piezas juntas
Resumen de la sección
| Idea clave | En una frase |
|---|---|
| 8 tipos primitivos | byte, short, int, long (enteros) · float, double (decimales) · boolean (lógico) · char (un carácter) |
| Declaración vs. inicialización | Declarar reserva el recipiente; inicializar mete el valor dentro |
| Ámbito | Una variable solo existe desde su declaración hasta el cierre del bloque { } donde vive |
var |
Tipado estático con inferencia: el tipo se fija al compilar y no cambia después |
| Literales modernos | _ (legibilidad), 0x (hexadecimal), 0b (binario) |
| Sufijos | L (long, obligatorio si no cabe en int), f (float, obligatorio), d (double, opcional) |
| Valores por defecto | Solo los campos los tienen automáticamente; las variables locales, nunca |
Comprueba lo que has entendido
- ¿Qué tipo primitivo elegirías para guardar el número de estudiantes matriculados en un ciclo, y cuál para guardar la población de un país entero? Justifica la diferencia.
- ¿Por qué
var contador = 10; contador = "diez";no compila, aunque a simple vistavarparezca "flexible"? - Escribe el literal
250000usando el separador de legibilidad_, y explica qué diferencia hay a nivel de compilación entre escribirlo con o sin ese separador. - Si declaras
int total;como campo de una clase y no lo inicializas, ¿qué valor tiene? ¿Y si esa misma declaración estuviera dentro de un método?
Qué viene ahora
Ya sabes declarar, inicializar y tipar correctamente cualquier dato numérico, lógico o de un único carácter. Pero para textos completos (nombres, frases, párrafos) los tipos primitivos se quedan cortos: ahí entra en juego String, que técnicamente no es un tipo primitivo, sino una clase del API de Java. En la Sección 5 de UD1 veremos el tipo String, la concatenación, los text blocks multilínea de Java moderno, y la trampa clásica de == frente a .equals().