Programación · n.º 7 entre los lenguajes más usados

Aprende Java desde cero

Robusto y multiplataforma: aplicaciones de empresa, servidores y Android.

  • 15 lecciones
  • 19 ejercicios interactivos
  • 27 conceptos clave
  • 27,6 % lo usa*

Qué es Java y para qué sirve

Java compila tu código a bytecode, que ejecuta la máquina virtual de Java (JVM) en cualquier sistema. Es uno de los lenguajes más usados en empresas y bancos, en servidores y en el ecosistema de Android. Es más estricto que Python o JavaScript, y eso ayuda a detectar errores pronto.

  • Aplicaciones de empresa y servidores
  • Aplicaciones Android
  • Procesamiento de datos a gran escala

Para empezar: Instala un JDK (por ejemplo, desde dev.java). Los fragmentos de las lecciones puedes probarlos en jshell, la consola interactiva que viene con el JDK, o dentro del main del primer ejemplo.

Nivel 1 Básico

Lección 1 · Básico

Tu primer programa

En Java todo el código vive dentro de una clase, y el programa empieza en el método main. System.out.println() escribe una línea en la consola.

El compilador javac traduce el código a bytecode y la JVM lo ejecuta. Desde Java 11 puedes hacer las dos cosas de una vez con java Main.java.

Ejemplo práctico
// Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println("¡Hola, mundo!");
        System.out.println(2 + 3); // 5
    }
}

// En la terminal: java Main.java

Ejercicio

Completa la instrucción que escribe una línea.

System.out.("Hola");
Ver la solución

Respuesta: println. System.out.println() escribe el texto y salta de línea.

Lección 2 · Básico

Variables y tipos

Java es de tipado estático: cada variable tiene un tipo fijo. Los más comunes son int (enteros), double (decimales), boolean y String (texto, con comillas dobles).

Con var el compilador deduce el tipo a partir del valor, pero sigue siendo fijo: no puedes guardar después un texto en una variable que empezó siendo un número.

Ejemplo práctico
int unidades = 3;
double precio = 19.99;
boolean enStock = true;
String producto = "Auriculares";
var total = unidades * precio;   // double

System.out.println(producto + ": " + total);

Ejercicio

¿Qué tipo usarías para guardar 19,99?

Ver la solución

Respuesta: double. int solo guarda enteros; para decimales se usa double.

Lección 3 · Básico

Condiciones y switch

if y else funcionan como en otros lenguajes, con la condición entre paréntesis y el bloque entre llaves. Los textos se comparan con .equals(), no con ==.

La expresión switch con flechas (Java 14 o posterior) elige un valor según varios casos de forma compacta.

Ejemplo práctico
String dia = "sábado";

if (dia.equals("sábado") || dia.equals("domingo")) {
    System.out.println("Fin de semana");
}

int horas = switch (dia) {
    case "sábado", "domingo" -> 0;
    case "viernes" -> 6;
    default -> 8;
};

Ejercicio

¿Cómo se comprueba si dos textos son iguales en Java?

Ver la solución

Respuesta: a.equals(b). == compara si son el mismo objeto en memoria; .equals() compara su contenido.

Encuentra los errores

Este fragmento debería mostrar «Fin de semana» y no mostrar «Aprobado» (la nota es 4). Tiene 2 errores clásicos de Java.

Ver la solución
  • Línea 4: == compara si son el mismo objeto en memoria, no si el texto es igual; aquí son dos objetos distintos y da false. Los textos se comparan con equals. Corrección: if (dia.equals("sábado")) {
  • Línea 7: El ; después de la condición cierra el if sin hacer nada, y el bloque de llaves se ejecuta siempre, apruebes o no. Corrección: if (nota >= 5) {

Lección 4 · Básico

Arrays y bucles

Un array tiene un tamaño fijo y un único tipo: int[] notas = {7, 8, 9};. Las posiciones empiezan en 0 y .length dice cuántas hay.

El for clásico usa un contador; el for mejorado (for (int n : notas)) recorre cada elemento directamente.

Ejemplo práctico
int[] notas = {7, 8, 9};
int suma = 0;

for (int nota : notas) {
    suma += nota;
}
System.out.println("Media: " + (double) suma / notas.length); // 8.0

for (int i = 3; i > 0; i--) {
    System.out.println(i);
}

Ejercicio

En int[] notas = {7, 8, 9}, ¿qué vale notas[1]?

Ver la solución

Respuesta: 8. La posición 0 es la primera (7), así que la 1 es la segunda: 8.

Escribe el código

Escribe el método static int maximo(int[] numeros): devuelve el número más alto del array (que siempre tiene al menos un elemento). Recórrelo con un bucle, sin usar Arrays.stream ni Collections.max.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
static int maximo(int[] numeros) {
    int mayor = numeros[0];
    for (int n : numeros) {
        if (n > mayor) {
            mayor = n;
        }
    }
    return mayor;
}

Se toma el primer elemento como candidato y se recorre el array: cada vez que aparece un número mayor, pasa a ser el nuevo candidato.

Nivel 2 Intermedio

Lección 5 · Intermedio

Métodos

Un método es una función dentro de una clase. Se declara con el tipo que devuelve (void si no devuelve nada), su nombre y sus parámetros con tipo.

Los métodos static pertenecen a la clase y se llaman sin crear un objeto, como main.

Ejemplo práctico
public class Calculadora {
    static double conIva(double precio, double iva) {
        return precio * (1 + iva / 100);
    }

    public static void main(String[] args) {
        System.out.println(conIva(100, 21)); // 121.0
    }
}

Ejercicio

Completa el tipo de un método que no devuelve nada.

static  saludar(String nombre) {
    System.out.println("Hola " + nombre);
}
Ver la solución

Respuesta: void. void indica que el método no devuelve ningún valor.

Escribe el código

Escribe static boolean esPrimo(int n): devuelve true si n es primo (solo es divisible por 1 y por sí mismo) y false si no. Los números menores que 2 no son primos.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
static boolean esPrimo(int n) {
    if (n < 2) {
        return false;
    }
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

Basta con probar divisores hasta la raíz cuadrada de n (i * i <= n): si n tuviera un divisor mayor, también tendría otro menor que ya se habría encontrado.

Conceptos clave

Lección 6 · Intermedio

Clases, objetos y records

Una clase define los datos (atributos) y el comportamiento (métodos) de un tipo de objeto. El constructor, con el mismo nombre que la clase, prepara cada objeto creado con new.

Para clases que solo guardan datos, un record (Java 16 o posterior) genera solo el constructor, los métodos de acceso, equals y toString.

Ejemplo práctico
class Cuenta {
    private double saldo;

    Cuenta(double saldoInicial) {
        saldo = saldoInicial;
    }
    void ingresar(double cantidad) { saldo += cantidad; }
    double getSaldo() { return saldo; }
}

record Producto(String nombre, double precio) {}

Cuenta c = new Cuenta(100);
c.ingresar(50);
System.out.println(c.getSaldo());                    // 150.0
System.out.println(new Producto("Ratón", 12.5));     // Producto[nombre=Ratón, precio=12.5]

Ejercicio

¿Qué palabra crea un objeto nuevo a partir de una clase?

Ver la solución

Respuesta: new. new Clase(...) reserva el objeto y ejecuta su constructor.

Lección 7 · Intermedio

Colecciones: List y Map

Las colecciones crecen según lo necesites. ArrayList es una lista ordenada y HashMap guarda pares clave–valor. Se indica el tipo de los elementos entre < y >.

List.of() y Map.of() crean colecciones que no se pueden modificar, útiles para datos fijos.

Ejemplo práctico
import java.util.*;

List<String> tareas = new ArrayList<>();
tareas.add("Comprar pan");
tareas.add("Llamar al médico");

Map<String, Integer> stock = new HashMap<>();
stock.put("teclado", 10);
stock.put("raton", 0);

System.out.println(tareas.size());                 // 2
System.out.println(stock.getOrDefault("webcam", 0)); // 0

Ejercicio

Necesitas buscar el precio de un producto por su nombre. ¿Qué colección usas?

Ver la solución

Respuesta: HashMap. Un Map asocia cada clave (el nombre) con su valor (el precio).

Encuentra los errores

Este fragmento debería mostrar «3 0» (tres nombres y la edad 0 para Luis, que no está en el mapa), pero se detiene con un error. Tiene 2 fallos.

Ver la solución
  • Línea 1: List.of crea una lista inmutable: add lanzaría UnsupportedOperationException. Para añadir elementos, cópiala en un ArrayList. Corrección: List<String> nombres = new ArrayList<>(List.of("Ana", "Luis"));
  • Línea 6: get devuelve null si la clave no existe, y al guardarlo en un int salta un NullPointerException. getOrDefault da un valor por defecto. Corrección: int edadLuis = edades.getOrDefault("Luis", 0);

Nivel 3 Avanzado

Lección 8 · Avanzado

Herencia e interfaces

Una clase puede heredar de otra con extends y reutilizar su código, pero solo de una. Una interfaz define métodos que las clases se comprometen a tener con implements, y una clase puede implementar varias.

Así puedes tratar objetos distintos de la misma forma: una lista de Figura puede contener círculos y rectángulos.

Ejemplo práctico
interface Figura {
    double area();
}

record Circulo(double radio) implements Figura {
    public double area() { return Math.PI * radio * radio; }
}

record Rectangulo(double ancho, double alto) implements Figura {
    public double area() { return ancho * alto; }
}

List<Figura> figuras = List.of(new Circulo(1), new Rectangulo(2, 3));
for (Figura f : figuras) System.out.println(f.area());

Ejercicio

Completa para que la clase cumpla la interfaz Figura.

class Cuadrado  Figura { … }
Ver la solución

Respuesta: implements. Una clase implementa una interfaz con implements; hereda de otra clase con extends.

Lección 9 · Avanzado

Excepciones

Cuando algo falla, Java lanza una excepción. Con try y catch la capturas; finally se ejecuta siempre. Algunas excepciones (las comprobadas, como IOException) el compilador te obliga a tratarlas o declararlas.

try-with-resources cierra automáticamente archivos y conexiones al terminar.

Ejemplo práctico
import java.nio.file.*;
import java.io.IOException;

try (var lineas = Files.lines(Path.of("datos.txt"))) {
    System.out.println(lineas.count() + " líneas");
} catch (IOException e) {
    System.out.println("No se pudo leer: " + e.getMessage());
}

try {
    int n = Integer.parseInt("doce");
} catch (NumberFormatException e) {
    System.out.println("No es un número");
}

Ejercicio

¿Qué bloque se ejecuta siempre, haya error o no?

Ver la solución

Respuesta: finally. finally se ejecuta al salir del try, tanto si hubo excepción como si no.

Lección 10 · Avanzado

Lambdas y streams

Una lambda es una función corta que se pasa como dato: p -> p.precio() > 10. Los streams encadenan operaciones sobre colecciones: filtrar, transformar, ordenar y reunir el resultado.

El código queda más declarativo: dices qué quieres obtener, no cómo recorrer la lista paso a paso.

Ejemplo práctico
record Producto(String nombre, double precio) {}

var productos = List.of(
    new Producto("Teclado", 25.9),
    new Producto("Ratón", 12.5),
    new Producto("Cable", 4.99));

List<String> caros = productos.stream()
    .filter(p -> p.precio() > 10)
    .map(Producto::nombre)
    .sorted()
    .toList();                       // Java 16+

System.out.println(caros);           // [Ratón, Teclado]

Ejercicio

¿Qué operación de un stream se queda solo con algunos elementos?

Ver la solución

Respuesta: filter. filter conserva los elementos para los que la lambda devuelve true.

Lección 11 · Avanzado

Texto a fondo: StringBuilder, format y bloques de texto

Los String no se pueden modificar: cada + en un bucle crea un texto nuevo. Para construir un texto a trozos, StringBuilder va añadiendo con append() sobre el mismo objeto y, al final, toString() da el resultado.

String.format (o el método formatted) mete valores en una plantilla: %s para texto, %d para enteros y %.2f para decimales con dos cifras. Los bloques de texto entre """ escriben textos de varias líneas tal cual.

Ejemplo práctico
public class Textos {
    public static void main(String[] args) {
        StringBuilder lista = new StringBuilder();
        for (int i = 1; i <= 3; i++) {
            lista.append("Tarea ").append(i).append("\n");
        }
        System.out.print(lista.toString());

        String linea = String.format("%s pagó %.2f € en %d compras", "Ana", 45.5, 3);
        System.out.println(linea); // Ana pagó 45,50 € en 3 compras (con idioma español)

        String json = """
            {
              "nombre": "Ana",
              "activo": true
            }
            """;
        System.out.print(json);
    }
}

Escribe el código

Escribe static String repetir(String texto, int veces) que devuelva el texto repetido veces veces seguidas, construyéndolo con un StringBuilder y un bucle.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
static String repetir(String texto, int veces) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < veces; i++) {
        sb.append(texto);
    }
    return sb.toString();
}

append va añadiendo sobre el mismo objeto, y toString() convierte el resultado en un String. (Java también tiene texto.repeat(veces), que hace justo esto.)

Lección 12 · Avanzado

Optional: cuando un valor puede faltar

Un método que busca algo puede no encontrarlo. Devolver null obliga a quien lo llama a acordarse de comprobarlo, y si se le olvida aparece un NullPointerException. Optional<T> hace explícito que el valor puede no estar.

Con orElse(valor) das un valor por defecto, con map transformas el contenido si existe y con ifPresent ejecutas algo solo cuando hay valor. Evita llamar a get() sin comprobar antes.

Ejemplo práctico
import java.util.*;

public class Buscador {
    static Optional<String> buscarCorreo(Map<String, String> agenda, String nombre) {
        return Optional.ofNullable(agenda.get(nombre));
    }

    public static void main(String[] args) {
        Map<String, String> agenda = Map.of("Ana", "ana@ejemplo.es");

        System.out.println(buscarCorreo(agenda, "Ana").orElse("sin correo"));   // ana@ejemplo.es
        System.out.println(buscarCorreo(agenda, "Luis").orElse("sin correo"));  // sin correo

        buscarCorreo(agenda, "Ana")
            .map(String::toUpperCase)
            .ifPresent(c -> System.out.println("Enviar a " + c));  // Enviar a ANA@EJEMPLO.ES
    }
}

Ejercicio

buscarCorreo(agenda, "Luis") devuelve un Optional vacío. ¿Qué muestra .orElse("sin correo")?

Ver la solución

Respuesta: «sin correo». orElse devuelve el contenido si existe y, si el Optional está vacío, el valor que le pasas.

Lección 13 · Avanzado

enum con datos y switch moderno

Un enum es un tipo con un conjunto cerrado de valores, como los planes de una suscripción. Cada valor puede llevar datos propios (se pasan a un constructor) y métodos, así el precio vive junto al plan y no en un if repartido por el código.

El switch con flechas (case X ->) devuelve un valor y no necesita break. Si lo usas con un enum, el compilador comprueba que cubres todos los casos y te avisa si añades un valor nuevo y te olvidas de tratarlo.

Ejemplo práctico
public class Suscripciones {
    enum Plan {
        BASICO(5), PRO(12), EMPRESA(30);

        private final int precio;

        Plan(int precio) {
            this.precio = precio;
        }

        int precioAnual() {
            return precio * 12;
        }
    }

    static String descripcion(Plan plan) {
        return switch (plan) {
            case BASICO -> "Para empezar";
            case PRO -> "Para profesionales";
            case EMPRESA -> "Para equipos";
        };
    }

    public static void main(String[] args) {
        for (Plan p : Plan.values()) {
            System.out.println(p + ": " + descripcion(p) + ", " + p.precioAnual() + " €/año");
        }
        // BASICO: Para empezar, 60 €/año
        // PRO: Para profesionales, 144 €/año
        // EMPRESA: Para equipos, 360 €/año
    }
}

Encuentra los errores

Este código no compila: tiene 2 errores. Uno impide guardar el precio de cada plan y otro deja un caso sin tratar en el switch.

Ver la solución
  • Línea 4: precio = precio asigna el parámetro a sí mismo y el campo final se queda sin valor (error de compilación). Hay que distinguirlos con this. Corrección: Plan(int precio) { this.precio = precio; }
  • Línea 10: Un switch que devuelve valor tiene que cubrir todos los valores del enum: falta PRO. Corrección: case BASICO -> "Para empezar"; case PRO -> "Para profesionales";

Lección 14 · Avanzado

Fechas y horas con java.time

El paquete java.time representa fechas (LocalDate), horas (LocalTime), ambas juntas (LocalDateTime) y duraciones. Sus objetos no se modifican: plusDays(30) devuelve una fecha nueva y deja la original igual.

ChronoUnit.DAYS.between(a, b) cuenta los días entre dos fechas y DateTimeFormatter.ofPattern("dd/MM/yyyy") convierte una fecha en texto con el formato que quieras (o lee un texto con ese formato con parse).

Ejemplo práctico
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

public class Fechas {
    public static void main(String[] args) {
        LocalDate inicio = LocalDate.of(2026, 1, 15);
        LocalDate entrega = inicio.plusDays(30);

        DateTimeFormatter formato = DateTimeFormatter.ofPattern("dd/MM/yyyy");
        System.out.println(entrega.format(formato));                     // 14/02/2026
        System.out.println(ChronoUnit.DAYS.between(inicio, entrega));    // 30
        System.out.println(entrega.getDayOfWeek());                      // SATURDAY

        LocalDate leida = LocalDate.parse("25/12/2026", formato);
        System.out.println(leida.isAfter(entrega));                      // true
    }
}

Ejercicio

Completa para crear la fecha 1 de marzo de 2026.

LocalDate fecha = LocalDate.(2026, 3, 1);
Ver la solución

Respuesta: of. LocalDate.of(año, mes, día) crea una fecha; los meses van del 1 al 12.

Lección 15 · Avanzado

Pruebas automáticas con JUnit

JUnit es la biblioteca de pruebas más usada en Java. Cada prueba es un método con la anotación @Test que llama a tu código y comprueba el resultado con assertEquals(esperado, obtenido), assertTrue o assertThrows.

Las herramientas de construcción como Maven y Gradle ejecutan todas las pruebas con una orden (mvn test, gradle test) y te dicen cuáles fallan. Si las ejecutas antes de cada cambio importante, detectas los errores al momento.

Ejemplo práctico
// src/main/java/Calculadora.java
public class Calculadora {
    static int dividir(int a, int b) {
        if (b == 0) throw new IllegalArgumentException("No se puede dividir entre 0");
        return a / b;
    }
}

// src/test/java/CalculadoraTest.java (con JUnit 5 añadido al proyecto)
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculadoraTest {
    @Test
    void divideNumerosExactos() {
        assertEquals(4, Calculadora.dividir(12, 3));
    }

    @Test
    void entreCeroLanzaError() {
        assertThrows(IllegalArgumentException.class, () -> Calculadora.dividir(5, 0));
    }
}

Ejercicio

En assertEquals(4, Calculadora.dividir(12, 3)), ¿qué es el 4?

Ver la solución

Respuesta: El valor que esperas obtener. El primer argumento es el valor esperado y el segundo, el que devuelve tu código. Si no coinciden, la prueba falla y JUnit muestra ambos.

¿Has terminado? Sigue practicando

Repasa los conceptos en el diccionario, practica con estas herramientas o prueba otro lenguaje: las ideas que has aprendido (variables, condiciones, funciones, objetos…) te servirán en cualquiera.

Proyectos para practicar Java

  • Texto y programación

    Base64, URL y JSON

    Codifica y decodifica Base64 y URL, y valida o formatea JSON con la línea del error.

  • Texto y programación

    Comparar dos textos

    Encuentra las diferencias entre dos versiones de un texto, línea a línea y palabra a palabra.

Fuentes consultadas

* Porcentaje de personas que dijeron haber usado Java en la Stack Overflow Developer Survey 2026: Technology (pregunta sobre lenguajes de programación, de scripts y de marcado; todas las respuestas). El progreso de las lecciones se guarda solo en este navegador.