Saltar a contenido

4. Variables y tipos primitivos

UD1 — Fundamentos de Programación

IES Thiar · Curso 2026 - 2027

Aspecto Valor
Resultado de aprendizaje RA1 — Reconoce la estructura de un programa informático, identificando y relacionando los elementos propios del lenguaje de programación utilizado.
Criterios de evaluación cubiertos CE 1.4 — Se han identificado los distintos tipos de variables y la utilidad específica de cada uno. CE 1.5 — Se ha modificado el código de un programa para crear y utilizar variables.
Marco normativo RD 405/2023 · RD 450/2010 · RD 659/2023
Tecnología base Java 25 LTS — tipos primitivos + var (inferencia, Java 10+).

Introducción de la sección

En la Sección 3 dejaste funcionando un programa que imprimía "Hola, mundo". Bonito, pero limitado: no sirve para nada si no puede manejar información que cambie. Una calculadora que solo dijera "Hola" sería una calculadora inútil; lo que quieres es que sume, reste, guarde resultados y los muestre. Para eso necesitamos variables: espacios de memoria con un nombre donde el programa guarda datos que pueden cambiar durante la ejecución. Y, como Java es un lenguaje fuertemente tipado, cada variable tiene que tener un tipo declarado que diga qué clase de dato va a contener: un entero, un decimal, un carácter, un booleano...

El objetivo de esta sección es que entiendas los ocho tipos primitivos que ofrece Java (son los bloques atómicos sobre los que se construye todo lo demás), que aprendas a declarar e inicializar variables correctamente, que conozcas la inferencia con var (Java 10+) que tanto limpia el código, y que veas la diferencia crucial entre variables locales (que no tienen valor por defecto) y campos de clase (que sí lo tienen). Cuando acabes, sabrás elegir el tipo adecuado para cada dato y evitarás los dos errores que más sufre el principiante: olvidar inicializar y elegir un tipo demasiado pequeño para el valor que quieres guardar.


4.1 Qué es una variable: el cajón con etiqueta

La analogía fundamental

Analogía

Imagina que la memoria RAM del ordenador es una mesa enorme llena de cajones, todos del mismo tamaño inicial. Cada cajón puede contener algo distinto: billetes, monedas, llaves, notas... Para que tú (y el programa) sepáis qué hay en cada cajón sin tener que abrirlo, le pones una etiqueta en la frontal con un nombre: "ahorro del mes", "edad del alumno", "nombre del cliente". Ese cajón etiquetado es una variable. Cuando metes algo dentro, estás asignando un valor a la variable; cuando lo abres para ver qué hay, estás leyendo su valor. Si en algún momento decides meter otra cosa distinta (porque el ahorro ha subido, porque el alumno ha cumplido años), el valor cambia, pero el cajón y su etiqueta siguen siendo los mismos.

Una variable es, por tanto, tres cosas a la vez:

  1. Un nombre (la etiqueta) que permite referenciarla desde el código.
  2. Un tipo (el tamaño y forma del cajón) que determina qué clase de dato puede contener.
  3. Un valor (lo que hay dentro), que puede cambiar durante la ejecución.

Y, por debajo, hay una cuarta cosa que tú no ves pero que el compilador y la JVM manejan: una dirección de memoria donde físicamente se guarda el valor. En lenguajes como C tendrías que gestionar esa dirección a mano con punteros; en Java, te olvidas, la JVM lo hace por ti.

Declarar vs inicializar

Hay dos operaciones distintas que el principiante suele confundir:

  • Declarar: crear el cajón y ponerle etiqueta y tipo. El cajón existe, pero puede estar vacío.
  • Inicializar: meter por primera vez un valor dentro del cajón.
1
2
3
4
int edad;           // declaración: el cajón existe, pero no puedes abrirlo hasta que metas algo
edad = 18;          // inicialización: metemos el valor 18

int altura = 175;   // declaración + inicialización en una sola línea (lo más habitual)

Java es estricto con esto: si intentas leer una variable local que has declarado pero no inicializado, el compilador te lo impide con un error: variable edad might not have been initialized. Es una protección: evita que leas "basura" sin darte cuenta. En la Sección 4.6 verás que esta regla solo aplica a variables locales; los campos de clase sí tienen valores por defecto.

El tipo importa: tipado fuerte

Java es un lenguaje fuertemente tipado: cada variable tiene un tipo declarado de forma explícita (o inferida por var), y solo puede contener valores de ese tipo. No puedes meter un String en una variable int, ni un int en un boolean. El compilador lo rechaza antes de que el programa se ejecute.

1
2
3
4
int edad = 18;          // bien
edad = "dieciocho";     // ERROR: no puedes meter un String en un int
boolean mayor = true;   // bien
mayor = 1;              // ERROR: en Java, boolean y int no son intercambiables (a diferencia de C)

Esta exigencia aparentemente rígida es una virtud: el 80% de los errores de tipos se detectan en compilación, no en ejecución. Es lo que diferencia a Java de lenguajes dinámicos como Python o JavaScript, donde x = 5; x = "hola" es legal pero genera bugs difíciles de rastrear.


4.2 Los ocho tipos primitivos de Java

Qué es un tipo primitivo

Un tipo primitivo es un tipo de dato básico que no es un objeto. Java tiene ocho, y solo ocho: están predefinidos en el lenguaje, no se pueden crear nuevos, y se almacenan directamente por valor (no por referencia). Son los bloques atómicos con los que se construye todo lo demás: los objetos, las colecciones, los arrays, los registros de base de datos...

La razón de que existan los primitivos es rendimiento: un int ocupa exactamente 4 bytes en memoria y se opera con él directamente con una instrucción de la CPU. Un objeto equivalente (Integer) ocuparía bastante más y requeriría una indirección. En Java, los primitivos son la excepción a la regla "todo es un objeto"; en Kotlin o Scala, lenguajes que corren sobre la misma JVM, ya no existen y todo es objeto (aunque el compilador los convierte a primitivos por debajo cuando puede).

Tabla maestra de los ocho tipos

flowchart TD
    P["Tipos primitivos de Java (8)"] --> ENT["Enteros (4)"]
    P --> DEC["Decimales (2)"]
    P --> BOOL["Booleano (1)"]
    P --> CAR["Carácter (1)"]

    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"]

    BOOL --> BOOLEAN["boolean - true/false"]

    CAR --> CHAR["char - 16 bits Unicode"]

    style P fill:#1565c0,color:#fff,stroke:#0d47a1
    style ENT fill:#2e7d32,color:#fff,stroke:#1b5e20
    style DEC fill:#e65100,color:#fff,stroke:#bf360c
    style BOOL fill:#6a1b9a,color:#fff,stroke:#4a148c
    style CAR fill:#00838f,color:#fff,stroke:#006064
    style BYTE fill:#e8f5e9,stroke:#2e7d32,color:#000
    style SHORT fill:#e8f5e9,stroke:#2e7d32,color:#000
    style INT fill:#e8f5e9,stroke:#2e7d32,color:#000
    style LONG fill:#e8f5e9,stroke:#2e7d32,color:#000
    style FLOAT fill:#fff3e0,stroke:#e65100,color:#000
    style DOUBLE fill:#fff3e0,stroke:#e65100,color:#000
    style BOOLEAN fill:#f3e5f5,stroke:#6a1b9a,color:#000
    style CHAR fill:#e0f7fa,stroke:#00838f,color:#000

Diagrama 4.1 — Los ocho tipos primitivos de Java organizados por categoría.

Tipo Categoría Tamaño Rango Valor por defecto Uso típico
byte Entero 8 bits -128 a 127 0 Datos binarios, ficheros, flujos de red
short Entero 16 bits -32.768 a 32.767 0 Raramente usado (casi nunca)
int Entero 32 bits -2.147.483.648 a 2.147.483.647 0 El entero por defecto. Edades, contadores, IDs...
long Entero 64 bits -9,2·10¹⁸ a 9,2·10¹⁸ 0 Cantidades grandes: timestamps Unix, IDs de BD, población mundial
float Decimal 32 bits ~±3,4·10³⁸ (7 dígitos significativos) 0.0f Decimales con poca precisión (gráficos, raramente)
double Decimal 64 bits ~±1,7·10³⁰⁸ (15 dígitos significativos) 0.0 El decimal por defecto. Notas, precios, medidas físicas
boolean Lógico 1 bit (JVM-dependent) true / false false Condiciones, flags, estados
char Carácter 16 bits 0 a 65.535 (Unicode BMP) '\u0000' Un solo carácter Unicode: 'A', '7', 'ñ', '€'

El entero por defecto es int

Cuando escribes un literal entero en Java (como 42 o 1000), el compilador lo interpreta como int por defecto. Si quieres que sea long, tienes que añadir el sufijo L:

1
2
3
int poblacionEspaña = 48_000_000;        // bien
long poblacionMundial = 8_000_000_000;   // ERROR: se pasa del rango de int
long poblacionMundial = 8_000_000_000L;  // bien: la L indica que es long

Cuidado con la L mayúscula. Aunque el compilador acepta tanto 8_000_000_000l (minúscula) como 8_000_000_000L (mayúscula), la minúscula se confunde con el 1 en muchas fuentes. Usa siempre L mayúscula: es convención oficial de Oracle.

El decimal por defecto es double

Igual que con los enteros, un literal decimal (3.14) se interpreta como double. Si quieres float, añade f:

1
2
3
double pi = 3.141592653589793;   // bien, 15 dígitos significativos
float piCorto = 3.141592653589793;  // ERROR: no cabe en float (7 dígitos)
float piCorto = 3.141592653589793f; // bien: la f fuerza a float (con pérdida de precisión)

Pista

En este ciclo, salvo que se te pida explícitamente lo contrario, usa siempre double para decimales. float se reserva para casos donde la memoria o el rendimiento son críticos (gráficos 3D, machine learning con millones de valores). Para notas, precios y medidas, double es la opción correcta.

char es especial

El char parece simple pero tiene dos peculiaridades que conviene saber desde el principio:

  1. Usa comillas simples para sus literales: 'A', no "A" (las dobles son para String).
  2. Es en realidad un entero sin signo de 16 bits que representa un código Unicode. Puedes asignarle un número directamente o usar secuencias de escape.
1
2
3
4
5
char letra = 'A';            // bien
char letraNum = 65;          // también bien: 65 es el código Unicode de 'A'
char euro = '€';             // bien, Unicode
char nuevaLinea = '\n';      // secuencia de escape
char unicode = '\u20AC';     // '\u20AC' es el código Unicode del euro

Secuencias de escape más usadas: \n (nueva línea), \t (tabulador), \\ (barra invertida), \' (comilla simple), \" (comilla doble), \uXXXX (carácter Unicode).


4.3 Declaración, inicialización y ámbito

Tres formas de declarar

// Forma 1: declarar y luego inicializar (útil si la inicialización depende de cálculos)
int edad;
// ... otras instrucciones ...
edad = 18;

// Forma 2: declarar e inicializar a la vez (lo más común)
int edad = 18;

// Forma 3: declarar varias del mismo tipo en una línea
int a = 1, b = 2, c = 3;     // legal, pero poco legible: evítalo salvo casos triviales

Pista

La forma 2 es la que vas a usar el 90% de las veces. La forma 3 parece tentadora por ahorrar líneas, pero dificulta la lectura y los diffs de git. Mejor una variable por línea.

El ámbito: dónde vive una variable

El ámbito (scope) de una variable es la porción de código donde esa variable existe y es accesible. En Java, el ámbito se define por las llaves { } que la contienen. Una variable declarada dentro de un bloque deja de existir al cerrar ese bloque.

flowchart TD
    A["class Ejemplo {"] -->|ámbito de clase| B["int contador = 0;  // campo: vive mientras viva el objeto"]
    A --> C["void main() {  // ámbito del método"]
    C --> D["int x = 5;  // ámbito: todo el método main"]
    C --> E["if (x > 3) {"]
    E --> F["int y = 10;  // ámbito: solo dentro del if"]
    F --> G["System.out.println(x + y);  // bien: x e y visibles"]
    E --> H["}"]
    C --> I["System.out.println(x);  // bien: x sigue visible"]
    C --> J["System.out.println(y);  // ERROR: y ya no existe"]
    C --> K["}"]

    style A fill:#1565c0,color:#fff,stroke:#0d47a1
    style C fill:#2e7d32,color:#fff,stroke:#1b5e20
    style E fill:#e65100,color:#fff,stroke:#bf360c
    style F fill:#fff3e0,stroke:#e65100,color:#000
    style J fill:#ffebee,stroke:#c62828,color:#000

Diagrama 4.2 — Ámbito de variables: una variable solo existe dentro del bloque { } donde se declara.

Analogía

El ámbito es como las reglas de una biblioteca por plantas. Una variable declarada dentro de un if es un libro que has cogido en la planta 3: solo lo puedes leer mientras estés en esa planta. Si bajas a la planta 2 (sales del bloque), el libro "desaparece" para ti. No es que se haya destruido, es que ya no tienes acceso a esa estantería.

Tipos de variable según su ámbito

Tipo Dónde se declara Cuándo vive Valor por defecto
Variable local Dentro de un método o bloque { } Mientras se ejecuta ese bloque Ninguno (debes inicializarla antes de leerla)
Campo de instancia Directamente dentro de una clase, sin static Mientras viva el objeto que lo contiene Sí, según su tipo (0, 0.0, false, null...)
Campo estático Dentro de una clase, con static Mientras la clase esté cargada en la JVM Sí, según su tipo
Parámetro En la firma de un método Durante la ejecución de esa llamada (recibe el valor que se le pasa)

En UD1 solo trabajarás con variables locales; los campos los verás en UD4 (Clases y Objetos Propios). Pero conviene que conozcas la diferencia desde ya, porque el comportamiento de los valores por defecto cambia radicalmente entre ambos.


4.4 Inferencia con var (Java 10+)

Qué es var y qué no es

Desde Java 10, puedes declarar variables locales con la palabra var en lugar del tipo explícito. El compilador infiere el tipo a partir del valor con el que la inicializas:

1
2
3
4
5
var edad = 18;                  // el compilador infiere int
var nombre = "Ana";             // infiere String
var notaMedia = 8.75;           // infiere double
var esMayor = true;             // infiere boolean
var inicial = 'A';              // infiere char

Esto se llama inferencia de tipo local y solo funciona con variables locales dentro de métodos. No funciona con campos de clase, ni con parámetros, ni con valores de retorno.

Cuidado, idea equivocada muy común. var no convierte a Java en un lenguaje dinámico. El tipo sigue siendo estático y se decide en compilación, no en ejecución. Una vez inferido, no puedes cambiarlo:

1
2
3
var x = 5;       // x es de tipo int, el compilador lo sabe
x = "hola";      // ERROR: x es int, no String (igual que si lo hubieras declarado int x = 5)
x = 10;          // bien, sigue siendo int

Cuándo usar var y cuándo no

var es una herramienta, no una obligación. La regla de oro es: usa var cuando el tipo sea obvio por el lado derecho; usa el tipo explícito cuando no lo sea.

Situación Recomendación Por qué
var edad = 18; var OK Obvio que es int
var nombre = "Ana"; var OK Obvio que es String
var lista = new ArrayList<String>(); var OK El tipo está a la derecha
var resultado = calcular(); Mejor el tipo explícito El lector no sabe qué devuelve calcular()
var dato = leerDeFichero(); Mejor el tipo explícito El tipo depende del método, no se ve
var x = obtenerAlgo(); Mejor el tipo explícito Idem
1
2
3
4
5
6
7
8
// Buenos usos de var
var nombre = "Ana Pérez";
var edad = 23;
var notas = new ArrayList<Double>();   // obvio por la derecha

// Mal uso de var (el lector no sabe qué tipo es)
var dato = servicio.obtenerConfiguracion();   // ¿Config? ¿Properties? ¿String?
Map<String, List<Integer>> mapa = servicio.obtenerDatos();  // mejor explícito

var y los tipos primitivos: ¡trampa!

Hay una interacción sutil entre var y los literales numéricos que conviene saber:

1
2
3
4
5
6
7
var x = 5;          // x es int (el literal 5 es int por defecto)
var y = 5L;         // y es long (la L fuerza long)
var z = 5.0;        // z es double (los literales decimales son double)
var w = 5.0f;       // w es float (la f fuerza float)

var a = 5;          // a es int
a = 10_000_000_000; // ERROR: 10 mil millones no cabe en int, aunque a fuera "var"

Si necesitas que var a sea long, tienes que escribir var a = 5L;. La inferencia mira el lado derecho, no el izquierdo.

Ventajas e inconvenientes de var

Ventajas Inconvenientes
Código más corto y legible en casos obvios Puede ocultar el tipo si el lado derecho no lo deja claro
Reduce ruido visual (ArrayList<String> lista = new ArrayList<String>()var lista = new ArrayList<>()) Dificulta la lectura a quien no conozca el API
Facilita refactorings de tipo En revisiones de código, el revisor puede no saber el tipo sin abrir el método

Usaremos var con moderación: cuando el tipo sea obvio (literales, new explícito), lo usaremos; cuando dependa de una llamada a método, declararemos el tipo explícito.


4.5 Literales numéricos: separadores, hex, binario y sufijos

Un literal es un valor escrito directamente en el código: 42, 3.14, "hola", true. Java ofrece varias formas de escribir literales numéricos que pueden hacer tu código más legible (o más confuso, si abusas).

Separador de dígitos: _

Desde Java 7, puedes usar el guión bajo _ como separador visual en literales numéricos. El compilador lo ignora; sirve solo para leer mejor los números grandes:

1
2
3
4
int millon = 1_000_000;              // más legible que 1000000
long mundial = 8_000_000_000L;       // población mundial
double pi = 3.141_592_653_589_793;   // separador en decimales también
int tarjeta = 1234_5678_9012_3456;   // agrupar como en tarjetas de crédito

Cuidado

El _ no puede ir al principio ni al final del literal, ni junto al punto decimal o al sufijo. int x = _5; y int y = 5_; son errores de compilación.

Hexadecimal, binario y octal

Base Prefijo Ejemplo Equivalente decimal
Decimal (ninguno) 255 255
Hexadecimal 0x o 0X 0xFF 255
Binario 0b o 0B 0b11111111 255
Octal 0 (cero inicial) 0377 255
1
2
3
int rojo = 0xFF0000;        // color rojo en RGB (hexadecimal, típico en CSS)
int mascara = 0b1010_1010;  // máscara de bits (binario, típico en bajo nivel)
int permisos = 0755;        // permisos Unix (octal, ¡cuidado con el cero inicial!)

Cuidado con el cero inicial. Un literal que empieza por 0 se interpreta como octal, no como decimal. int x = 010; no es 10, es 8. Es un error clásico que ha causado bugs legendarios. Si quieres escribir 10, escribe 10, sin cero delante.

Sufijos de tipo

Los sufijos le dicen al compilador qué tipo exacto de literal numérico estás escribiendo:

Sufijo Tipo resultante Ejemplo
L o l long 8_000_000_000L
f o F float 3.14f
d o D double 3.14d (raramente necesario, double es el por defecto)

Sin sufijo:

  • Un literal entero es int por defecto.
  • Un literal decimal es double por defecto.

Literales de char y boolean

1
2
3
4
5
6
7
8
char letra = 'A';            // un carácter entre comillas simples
char digito = '7';
char simbolo = '€';
char escape = '\n';          // secuencia de escape
char unicode = '\u20AC';     // código Unicode del euro

boolean mayor = true;        // solo dos valores posibles
boolean vacio = false;

Pista

char va siempre entre comillas simples 'x'; String va entre comillas dobles "x". Mezclarlos es el error de principiante más común: 'hola' no compila (es un char con varios caracteres) y "A" es un String de longitud 1, no un char.


4.6 Valores por defecto: campos vs variables locales

Aquí hay una diferencia crucial que causa más bugs de los que imaginas y que aparece en todos los exámenes. Java trata distinto a las variables locales y a los campos de clase respecto a la inicialización.

Variables locales: el compilador te obliga a inicializar

Si declaras una variable local y la intentas leer sin haberla inicializado, el compilador te lo prohíbe:

1
2
3
4
void main() {
    int edad;
    System.out.println(edad);   // ERROR: variable edad might not have been initialized
}

Java no asume nada: te obliga a asignar un valor antes de leer. Es una protección fuerte: evita que leas "basura" que hubiera en esa posición de memoria.

Campos de instancia y estáticos: sí tienen valor por defecto

Sin embargo, si declaras un campo dentro de una clase (sin inicializarlo explícitamente), la JVM sí le asigna un valor por defecto automáticamente cuando se crea el objeto:

public class Alumno {
    int edad;              // valor por defecto: 0
    double notaMedia;      // valor por defecto: 0.0
    boolean mayorEdad;     // valor por defecto: false
    char inicial;          // valor por defecto: '\u0000' (carácter nulo)
    String nombre;         // valor por defecto: null (las referencias son null)

    void main() {
        var a = new Alumno();
        System.out.println(a.edad);        // imprime 0
        System.out.println(a.notaMedia);   // imprime 0.0
        System.out.println(a.mayorEdad);   // imprime false
        System.out.println(a.nombre);      // imprime null
    }
}

Tabla de valores por defecto

Tipo del campo Valor por defecto
byte, short, int, long 0 (o 0L para long)
float, double 0.0 (o 0.0f para float)
boolean false
char '\u0000' (carácter nulo)
Cualquier referencia (String, arrays, objetos) null
flowchart LR
    V["Variable declarada"] --> L["¿Es local?"]
    L -->|Sí| NL["No hay valor por defecto<br/>DEBES inicializarla antes de leerla"]
    NL --> ERR["Si no: error de compilación<br/>'might not have been initialized'"]

    L -->|No, es campo| C["¿De instancia o estático?"]
    C --> D["La JVM asigna valor por defecto<br/>0 / 0.0 / false / '\\u0000' / null"]
    D --> OK["Compila y ejecuta sin problema<br/>(¡pero cuidado con null!)"]

    style V fill:#1565c0,color:#fff,stroke:#0d47a1
    style L fill:#fff8e1,stroke:#f57f17,color:#000
    style NL fill:#fff3e0,stroke:#e65100,color:#000
    style ERR fill:#ffebee,stroke:#c62828,color:#000
    style C fill:#e8f5e9,stroke:#2e7d32,color:#000
    style D fill:#e0f2f1,stroke:#00695c,color:#000
    style OK fill:#e8f5e9,stroke:#2e7d32,color:#000

Diagrama 4.3 — Regla de valores por defecto: las variables locales no tienen; los campos sí.

Cuidado con null. El valor por defecto de cualquier referencia (String, arrays, objetos) es null, que significa "no apunta a nada". Si intentas llamar a un método sobre una referencia null, la JVM lanza una NullPointerException en tiempo de ejecución. Es el error más famoso de Java y lo verás miles de veces. En UD3 aprenderás a manejarlo con try-catch; por ahora, simplemente recuerda: si un campo String vale null, no puedes hacer nombre.length() sin que explote.

Por qué esta diferencia

La razón es una cuestión de seguridad vs rendimiento. Los campos de clase se inicializan a cero cuando se reserva la memoria para el objeto (la JVM lo hace de golpe con memset), así que aprovechar ese cero es gratis. Las variables locales, en cambio, viven en la pila de ejecución, que se reutiliza constantemente; el compilador no puede garantizar qué había antes en esa posición, así que te obliga a inicializarlas para que no leas basura.


4.7 Errores comunes y buenas prácticas

Los siete errores que verás en tus primeras semanas

Error Síntoma Causa Solución
Variable no inicializada error: variable x might not have been initialized Declaraste una variable local y la leíste sin asignarle valor Inicialízala al declararla o antes de leerla
Tipo demasiado pequeño Resultado numérico rarísimo, negativo sin razón int desbordado: superó 2.147.483.647 y "dio la vuelta" Usa long si el valor puede crecer
Confusión int / double en división 5 / 2 da 2 en vez de 2.5 División entera: si ambos operandos son int, el resultado es int Convierte uno a double: 5.0 / 2 o 5 / 2.0
Asignar int a byte error: incompatible types: possible lossy conversion from int to byte byte b = 200; no cabe (rango -128 a 127) Usa int o cambia el valor
Asignar double a float sin f error: incompatible types: possible lossy conversion from double to float float pi = 3.14; (3.14 es double) Añade f: float pi = 3.14f;
Olvidar L en long grande error: integer number too large long mundial = 8_000_000_000; (el literal es int y no cabe) Añade L: 8_000_000_000L
Mezclar char y String error: unclosed character literal o error: incompatible types 'hola' (varios caracteres en comillas simples) o "A" asignado a char Usa " para Strings y ' para un solo char

Desbordamiento silencioso: el int que da la vuelta

El error más sutil de la lista es el desbordamiento de enteros. Java no te avisa cuando un int supera su rango: simplemente "da la vuelta" como un cuentakilómetros.

1
2
3
int max = 2_147_483_647;     // el máximo valor de int
System.out.println(max);     // imprime 2147483647
System.out.println(max + 1); // imprime -2147483648  ¡¡¡negativo!!!

Esto es un bug clásico en sistemas que llevan contadores (visitas, IDs, transacciones bancarias). Si trabajas con cantidades que pueden crecer mucho, usa long desde el principio. Para detectar desbordamientos en tiempo de ejecución, Java 8+ ofrece métodos como Math.addExact(a, b) que lanzan una excepción si hay desbordamiento, en vez de dar la vuelta silenciosamente.

División entera vs división real

Otro clásico que cuesta de entender al principio:

1
2
3
4
5
int a = 5;
int b = 2;
System.out.println(a / b);        // imprime 2, NO 2.5 (división entera)
System.out.println(a / 2.0);      // imprime 2.5 (un operando double → división real)
System.out.println((double) a / b); // imprime 2.5 (casting explícito)

La regla: si los dos operandos son enteros, la división es entera (trunca los decimales). Si al menos uno es double o float, la división es real. Lo verás en detalle en la Sección 7 (Operadores) y la Sección 8 (Conversión de tipos).

Buenas prácticas resumidas

  1. Inicializa siempre al declarar, aunque sea con un valor provisional. int edad = 0; es mejor que int edad; si luego vas a leer la variable antes de reasignarla.
  2. Elige el tipo más pequeño que contenga el valor con margen. No uses long para una edad, pero tampoco uses byte para un ID que puede pasar de 127.
  3. Usa int para enteros y double para decimales por defecto. Solo cambia si hay razón justificada (memoria, rango, precisión).
  4. Usa var cuando el tipo sea obvio, tipo explícito cuando no lo sea.
  5. Usa _ para separar dígitos en números grandes: 1_000_000 se lee mejor que 1000000.
  6. Evita magic numbers: en lugar de if (edad >= 18), declara final int MAYORIA_EDAD = 18; y escribe if (edad >= MAYORIA_EDAD). Lo verás en la Sección 6 (Constantes).
  7. Nombres significativos: edad, notaMedia, nombreCompleto. Nunca e, nm, nc (salvo contadores de bucle como i, j).

Ejercicios propuestos

Antes de pasar al resumen, intenta resolver estos ejercicios. Si te atascas, vuelve a leer la subsección correspondiente.

Ejercicio 4.1 (tipos primitivos). Declara una variable de cada tipo primitivo con un literal representativo: un byte con tu edad, un short con el año actual, un int con la población de tu pueblo, un long con la población mundial, un float con el valor de PI (con sufijo f), un double con PI más preciso, un boolean indicando si eres mayor de edad, y un char con tu inicial. Imprime cada una con println.

Ejercicio 4.2 (literales numéricos). Escribe estas declaraciones usando notación alternativa: - int mascara = 255 en hexadecimal (0xFF) - int permisos = 420 en octal (con cero inicial) - long mundial = 8000000000 con separador _ y sufijo L

Ejercicio 4.3 (var). Predice el tipo inferido de cada var sin ejecutarlo y comprueba abriendo el fichero en IntelliJ (al pasar el ratón sobre la variable te muestra el tipo):

1
2
3
4
5
6
var a = 5;
var b = 5L;
var c = 5.0;
var d = 5.0f;
var e = "5";
var f = '5';

Ejercicio 4.4 (ámbito). El siguiente código no compila. Explica por qué y arréglalo:

1
2
3
4
5
6
7
8
public class Test {
    public static void main(String[] args) {
        if (true) {
            int x = 10;
        }
        System.out.println(x);
    }
}

Ejercicio 4.5 (valores por defecto). Predice qué imprime el siguiente programa antes de ejecutarlo:

public class Alumno {
    int edad;
    double notaMedia;
    boolean mayor;
    String nombre;

    public static void main(String[] args) {
        Alumno a = new Alumno();
        System.out.println(a.edad);
        System.out.println(a.notaMedia);
        System.out.println(a.mayor);
        System.out.println(a.nombre);
    }
}

Ejercicio 4.6 (desbordamiento). Predice el resultado sin ejecutarlo y comprueba después:

1
2
3
int max = 2_147_483_647;
System.out.println(max + 1);
System.out.println(max + 2);

Pista

Las soluciones de los ejercicios se discutirán en la puesta en común de la sesión siguiente. Si quieres verificar antes, abre un proyecto de prueba en IntelliJ y ejecuta el código.


Resumen de la sección 4

Antes de pasar a la siguiente sección, asegúrate de que controlas estos puntos:

  1. Una variable es un espacio de memoria con nombre, tipo y valor. Se declara (se crea) y se inicializa (se le asigna un primer valor); pueden hacerse en una sola línea.
  2. Java es fuertemente tipado: cada variable tiene un tipo fijo y solo puede contener valores de ese tipo. El compilador rechaza asignaciones incompatibles.
  3. Hay ocho tipos primitivos: cuatro enteros (byte, short, int, long), dos decimales (float, double), un lógico (boolean) y un carácter (char). int y double son los defaults para enteros y decimales respectivamente.
  4. El ámbito de una variable local es el bloque { } donde se declara. Fuera de ese bloque, la variable deja de existir.
  5. var (Java 10+) permite declarar variables locales sin escribir el tipo explícito: el compilador lo infiere del lado derecho. No convierte Java en dinámico; el tipo sigue siendo estático.
  6. Los literales admiten separador _ (1_000_000), bases hex (0xFF), binaria (0b1010) y octal (0377), y sufijos L (long), f (float), d (double).
  7. Las variables locales no tienen valor por defecto: debes inicializarlas antes de leerlas. Los campos de clase sí (0, 0.0, false, '\u0000', null).
  8. Los errores típicos son: no inicializar, desbordamiento de int, división entera involuntaria, mezclar char y String.