Saltar a contenido

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 float y double. 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 de float), según el estándar internacional IEEE 754 que Java sigue al pie de la letra. En la práctica: double no 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ás double casi siempre que necesites decimales.

Dato clave sobre char. Un char se escribe siempre entre comillas simples: 'A'. Si usas comillas dobles ("A"), ya no es un char, es un String (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:

1
2
3
4
int edad;        // 1. Declaración: reservas el "recipiente" y le pones nombre y tipo
edad = 25;        // 2. Inicialización: le metes un valor dentro

int nota = 8;     // Ambos pasos en una sola línea, lo más habitual
  • 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 if o bucles (llegan en UD3), así que por ahora tu único bloque será el del propio main. Pero el concepto de ámbito es tan importante que conviene fijarlo ya: en cuanto empieces a anidar bloques dentro de main, 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:

1
2
3
var edad = 25;         // el compilador infiere: int
var nombre = "Ana";     // el compilador infiere: String
var precioUnidad = 9.99; // el compilador infiere: double

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, con var o 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 que int edad = 25;. Si después intentas edad = "veinticinco";, el compilador te lo va a rechazar con el mismo error que si hubieras escrito int explícitamente. var no 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:

1
2
3
int poblacion = 1_000_000;   // separador de legibilidad
int colorBlanco = 0xFFFFFF;   // literal hexadecimal
int mascara = 0b1010_1010;    // literal binario (combinado con el separador)
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:

1
2
3
long poblacionMundial = 8_100_000_000L;  // no cabría en un int: necesita la L
float precioAproximado = 19.99f;          // sin la f, sería un double
double notaMedia = 8.4567d;               // la d es opcional, double ya es el tipo por defecto
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

package dam.programacion.ud1;

/**
 * Ejemplo de la Sección 4: declaración, inicialización y tipos primitivos.
 *
 * @author Alumno o alumna de DAM
 */
public class TiposPrimitivos {

    public static void main(String[] args) {

        // --- Números enteros ---
        byte edadMascota = 3;                     // cabe de sobra en un byte
        short capacidadAula = 30;                  // legado, poco uso real hoy
        int numeroAlumnos = 1_250;                 // el entero "por defecto"
        long poblacionMundial = 8_100_000_000L;    // no cabría en un int: necesita el sufijo L

        // --- Números decimales ---
        float precioAproximado = 19.99f;           // necesita el sufijo f
        double notaMedia = 8.4567;                 // double es el decimal por defecto

        // --- Lógico ---
        boolean haAprobado = notaMedia >= 5.0;

        // --- Carácter ---
        char inicialNombre = 'J';

        // --- Inferencia con var ---
        var nombreCiclo = "DAM";                   // el compilador infiere String
        var totalCreditos = 2000;                  // el compilador infiere int

        // --- Literales hexadecimal y binario ---
        int colorBlanco = 0xFFFFFF;                 // hexadecimal: blanco puro en RGB
        int mascara = 0b1010_1010;                  // binario, con separador de legibilidad

        System.out.println("Ciclo: " + nombreCiclo + " (" + totalCreditos + " horas totales)");
        System.out.println("Alumnos matriculados: " + numeroAlumnos);
        System.out.println("Población mundial aproximada: " + poblacionMundial);
        System.out.println("¿Ha aprobado con nota " + notaMedia + "? " + haAprobado);
        System.out.println("Inicial del nombre: " + inicialNombre);
        System.out.printf("Color blanco en decimal: %d%n", colorBlanco);
        System.out.printf("Máscara binaria en decimal: %d%n", mascara);
    }
}

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

  1. ¿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.
  2. ¿Por qué var contador = 10; contador = "diez"; no compila, aunque a simple vista var parezca "flexible"?
  3. Escribe el literal 250000 usando el separador de legibilidad _, y explica qué diferencia hay a nivel de compilación entre escribirlo con o sin ese separador.
  4. 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().