Proyectos · Java · Intermedio

Agenda de contactos

Un programa de consola con menú para añadir, listar y buscar contactos, que comprueba los datos y guarda la agenda en un archivo para la próxima vez.

  • 5 pasos
  • 1 hora y media aproximadamente
  • 105 líneas de código

Lo que vas a construir

Una agenda es la aplicación perfecta para practicar Java «de verdad»: datos con forma propia (un record), una lista que crece, un menú que se repite, búsquedas con streams y un archivo donde guardar. Se ejecuta en la terminal, sin necesidad de proyecto ni de herramientas extra.

Lo que vas a practicar

  • Datos con record
  • Listas con ArrayList
  • Un menú con while, Scanner y switch
  • Buscar y ordenar con streams
  • Leer y escribir archivos con Files

Cómo ejecutarlo: Con Java 21 o posterior, guarda el código como Agenda.java y ejecútalo directamente con java Agenda.java (no hace falta compilar antes).

Así funciona al final
1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 1
Nombre: Marta Ruiz
Teléfono: 622333444
Guardado.

1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 4
Agenda guardada en contactos.csv.
¡Hasta luego!

Antes de empezar, conviene haber hecho estas lecciones

Paso 1 de 5

El contacto como record

Un record declara en una línea una clase para guardar datos: Java crea solo el constructor, los métodos para leer cada campo (nombre(), telefono()), equals y toString.

Los contactos se guardan en un ArrayList, una lista que crece según se añaden elementos.

Agenda.java
import java.util.ArrayList;
import java.util.List;

public class Agenda {
    record Contacto(String nombre, String telefono) {}

    public static void main(String[] args) {
        List<Contacto> contactos = new ArrayList<>();
        contactos.add(new Contacto("Ana López", "600111222"));
        contactos.add(new Contacto("Luis Pérez", "611222333"));

        for (Contacto c : contactos) {
            System.out.println(c.nombre() + " - " + c.telefono());
        }
        System.out.println(contactos.size() + " contactos");
    }
}
Al ejecutarlo
Ana López - 600111222
Luis Pérez - 611222333
2 contactos

Paso 2 de 5

Un menú que se repite

El programa muestra un menú en un bucle while hasta que se elige salir. Scanner lee lo que se escribe con nextLine() y el switch con flechas llama al método de cada opción.

La lista y el Scanner pasan a ser campos static de la clase para que todos los métodos puedan usarlos.

Agenda.javaLo nuevo, resaltado
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Agenda {
    record Contacto(String nombre, String telefono) {}

    static final List<Contacto> contactos = new ArrayList<>();
    // Con el juego de caracteres de la consola, para que las tildes se lean bien también en Windows
    static final Scanner teclado = new Scanner(System.in,
        System.console() != null ? System.console().charset() : Charset.defaultCharset());

    public static void main(String[] args) {
        boolean seguir = true;
        while (seguir) {
            System.out.print("\n1) Añadir  2) Listar  3) Salir\nOpción: ");
            String opcion = teclado.nextLine().trim();
            switch (opcion) {
                case "1" -> anadir();
                case "2" -> listar();
                case "3" -> seguir = false;
                default -> System.out.println("Opción no válida");
            }
        }
        System.out.println("¡Hasta luego!");
    }

    static void anadir() {
        System.out.print("Nombre: ");
        String nombre = teclado.nextLine().trim();
        System.out.print("Teléfono: ");
        String telefono = teclado.nextLine().trim();
        contactos.add(new Contacto(nombre, telefono));
        System.out.println("Guardado.");
    }

    static void listar() {
        if (contactos.isEmpty()) {
            System.out.println("La agenda está vacía.");
            return;
        }
        for (Contacto c : contactos) {
            System.out.println(c.nombre() + " - " + c.telefono());
        }
    }
}
Al ejecutarlo
1) Añadir  2) Listar  3) Salir
Opción: 1
Nombre: Ana López
Teléfono: 600111222
Guardado.

1) Añadir  2) Listar  3) Salir
Opción: 2
Ana López - 600111222

1) Añadir  2) Listar  3) Salir
Opción: 3
¡Hasta luego!

Paso 3 de 5

Buscar y ordenar con streams

Un stream recorre la lista y encadena operaciones: filter se queda con los contactos cuyo nombre contiene el texto buscado (pasando ambos a minúsculas) y toList() junta el resultado.

Al listar, sorted(Comparator.comparing(...)) los ordena por nombre sin distinguir mayúsculas, sin cambiar el orden de la lista original.

Agenda.javaLo nuevo, resaltado
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

public class Agenda {
    record Contacto(String nombre, String telefono) {}

    static final List<Contacto> contactos = new ArrayList<>();
    // Con el juego de caracteres de la consola, para que las tildes se lean bien también en Windows
    static final Scanner teclado = new Scanner(System.in,
        System.console() != null ? System.console().charset() : Charset.defaultCharset());

    public static void main(String[] args) {
        boolean seguir = true;
        while (seguir) {
            System.out.print("\n1) Añadir  2) Listar  3) Buscar  4) Salir\nOpción: ");
            String opcion = teclado.nextLine().trim();
            switch (opcion) {
                case "1" -> anadir();
                case "2" -> listar();
                case "3" -> buscar();
                case "4" -> seguir = false;
                default -> System.out.println("Opción no válida");
            }
        }
        System.out.println("¡Hasta luego!");
    }

    static void anadir() {
        System.out.print("Nombre: ");
        String nombre = teclado.nextLine().trim();
        System.out.print("Teléfono: ");
        String telefono = teclado.nextLine().trim();
        contactos.add(new Contacto(nombre, telefono));
        System.out.println("Guardado.");
    }

    static void listar() {
        if (contactos.isEmpty()) {
            System.out.println("La agenda está vacía.");
            return;
        }
        contactos.stream()
            .sorted(Comparator.comparing(Contacto::nombre, String.CASE_INSENSITIVE_ORDER))
            .forEach(c -> System.out.println(c.nombre() + " - " + c.telefono()));
    }

    static void buscar() {
        System.out.print("Buscar: ");
        String texto = teclado.nextLine().trim().toLowerCase();
        List<Contacto> encontrados = contactos.stream()
            .filter(c -> c.nombre().toLowerCase().contains(texto))
            .toList();
        if (encontrados.isEmpty()) {
            System.out.println("No hay ningún contacto con «" + texto + "».");
        } else {
            encontrados.forEach(c -> System.out.println(c.nombre() + " - " + c.telefono()));
        }
    }
}
Al ejecutarlo
1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 1
Nombre: Luis Pérez
Teléfono: 611222333
Guardado.

1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 1
Nombre: Ana López
Teléfono: 600111222
Guardado.

1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 2
Ana López - 600111222
Luis Pérez - 611222333

1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 3
Buscar: lu
Luis Pérez - 611222333

1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 4
¡Hasta luego!

Paso 4 de 5

No admitir datos erróneos

Antes de guardar se comprueba que el nombre no esté vacío ni repetido (con anyMatch y equalsIgnoreCase) y que el teléfono tenga 9 cifras con la expresión regular \d{9} (en un texto de Java la barra se escribe doble: "\\d{9}"). Se quitan los espacios para aceptar «600 111 222».

Cada comprobación que falla avisa y termina el método con return, sin llegar a guardar nada.

Agenda.javaLo nuevo, resaltado
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

public class Agenda {
    record Contacto(String nombre, String telefono) {}

    static final List<Contacto> contactos = new ArrayList<>();
    // Con el juego de caracteres de la consola, para que las tildes se lean bien también en Windows
    static final Scanner teclado = new Scanner(System.in,
        System.console() != null ? System.console().charset() : Charset.defaultCharset());

    public static void main(String[] args) {
        boolean seguir = true;
        while (seguir) {
            System.out.print("\n1) Añadir  2) Listar  3) Buscar  4) Salir\nOpción: ");
            String opcion = teclado.nextLine().trim();
            switch (opcion) {
                case "1" -> anadir();
                case "2" -> listar();
                case "3" -> buscar();
                case "4" -> seguir = false;
                default -> System.out.println("Opción no válida");
            }
        }
        System.out.println("¡Hasta luego!");
    }

    static void anadir() {
        System.out.print("Nombre: ");
        String nombre = teclado.nextLine().trim();
        if (nombre.isEmpty()) {
            System.out.println("El nombre no puede estar vacío.");
            return;
        }
        boolean repetido = contactos.stream().anyMatch(c -> c.nombre().equalsIgnoreCase(nombre));
        if (repetido) {
            System.out.println("Ya existe un contacto con ese nombre.");
            return;
        }
        System.out.print("Teléfono: ");
        String telefono = teclado.nextLine().replace(" ", "");
        if (!telefono.matches("\\d{9}")) {
            System.out.println("El teléfono tiene que tener 9 cifras.");
            return;
        }
        contactos.add(new Contacto(nombre, telefono));
        System.out.println("Guardado.");
    }

    static void listar() {
        if (contactos.isEmpty()) {
            System.out.println("La agenda está vacía.");
            return;
        }
        contactos.stream()
            .sorted(Comparator.comparing(Contacto::nombre, String.CASE_INSENSITIVE_ORDER))
            .forEach(c -> System.out.println(c.nombre() + " - " + c.telefono()));
    }

    static void buscar() {
        System.out.print("Buscar: ");
        String texto = teclado.nextLine().trim().toLowerCase();
        List<Contacto> encontrados = contactos.stream()
            .filter(c -> c.nombre().toLowerCase().contains(texto))
            .toList();
        if (encontrados.isEmpty()) {
            System.out.println("No hay ningún contacto con «" + texto + "».");
        } else {
            encontrados.forEach(c -> System.out.println(c.nombre() + " - " + c.telefono()));
        }
    }
}
Al ejecutarlo
1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 1
Nombre: Ana López
Teléfono: 600 11
El teléfono tiene que tener 9 cifras.

1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 1
Nombre: Ana López
Teléfono: 600 111 222
Guardado.

1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 4
¡Hasta luego!

Paso 5 de 5

Guardar la agenda en un archivo

Al empezar, cargar lee contactos.csv con Files.readAllLines y crea un contacto por línea (nombre y teléfono separados por punto y coma). Al salir, guardar escribe la lista con Files.write.

Leer y escribir archivos puede fallar (permisos, disco lleno), por eso Java obliga a tratar IOException con try/catch. Se indica UTF_8 para que las tildes se guarden bien en cualquier sistema.

Agenda.javaLo nuevo, resaltado
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

public class Agenda {
    record Contacto(String nombre, String telefono) {}

    static final List<Contacto> contactos = new ArrayList<>();
    // Con el juego de caracteres de la consola, para que las tildes se lean bien también en Windows
    static final Scanner teclado = new Scanner(System.in,
        System.console() != null ? System.console().charset() : Charset.defaultCharset());
    static final Path ARCHIVO = Path.of("contactos.csv");

    public static void main(String[] args) {
        cargar();
        boolean seguir = true;
        while (seguir) {
            System.out.print("\n1) Añadir  2) Listar  3) Buscar  4) Salir\nOpción: ");
            String opcion = teclado.nextLine().trim();
            switch (opcion) {
                case "1" -> anadir();
                case "2" -> listar();
                case "3" -> buscar();
                case "4" -> seguir = false;
                default -> System.out.println("Opción no válida");
            }
        }
        guardar();
        System.out.println("¡Hasta luego!");
    }

    static void cargar() {
        if (!Files.exists(ARCHIVO)) return;
        try {
            for (String linea : Files.readAllLines(ARCHIVO, StandardCharsets.UTF_8)) {
                String[] partes = linea.split(";", 2);
                if (partes.length == 2) contactos.add(new Contacto(partes[0], partes[1]));
            }
            System.out.println("Cargados " + contactos.size() + " contactos.");
        } catch (IOException e) {
            System.out.println("No se ha podido leer la agenda: " + e.getMessage());
        }
    }

    static void guardar() {
        List<String> lineas = contactos.stream().map(c -> c.nombre() + ";" + c.telefono()).toList();
        try {
            Files.write(ARCHIVO, lineas, StandardCharsets.UTF_8);
            System.out.println("Agenda guardada en " + ARCHIVO + ".");
        } catch (IOException e) {
            System.out.println("No se ha podido guardar: " + e.getMessage());
        }
    }

    static void anadir() {
        System.out.print("Nombre: ");
        String nombre = teclado.nextLine().trim();
        if (nombre.isEmpty()) {
            System.out.println("El nombre no puede estar vacío.");
            return;
        }
        boolean repetido = contactos.stream().anyMatch(c -> c.nombre().equalsIgnoreCase(nombre));
        if (repetido) {
            System.out.println("Ya existe un contacto con ese nombre.");
            return;
        }
        System.out.print("Teléfono: ");
        String telefono = teclado.nextLine().replace(" ", "");
        if (!telefono.matches("\\d{9}")) {
            System.out.println("El teléfono tiene que tener 9 cifras.");
            return;
        }
        contactos.add(new Contacto(nombre, telefono));
        System.out.println("Guardado.");
    }

    static void listar() {
        if (contactos.isEmpty()) {
            System.out.println("La agenda está vacía.");
            return;
        }
        contactos.stream()
            .sorted(Comparator.comparing(Contacto::nombre, String.CASE_INSENSITIVE_ORDER))
            .forEach(c -> System.out.println(c.nombre() + " - " + c.telefono()));
    }

    static void buscar() {
        System.out.print("Buscar: ");
        String texto = teclado.nextLine().trim().toLowerCase();
        List<Contacto> encontrados = contactos.stream()
            .filter(c -> c.nombre().toLowerCase().contains(texto))
            .toList();
        if (encontrados.isEmpty()) {
            System.out.println("No hay ningún contacto con «" + texto + "».");
        } else {
            encontrados.forEach(c -> System.out.println(c.nombre() + " - " + c.telefono()));
        }
    }
}
Al ejecutarlo
1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 1
Nombre: Marta Ruiz
Teléfono: 622333444
Guardado.

1) Añadir  2) Listar  3) Buscar  4) Salir
Opción: 4
Agenda guardada en contactos.csv.
¡Hasta luego!

Retos para seguir

Has terminado el proyecto. Si quieres ir más allá, prueba a añadirle esto por tu cuenta:

  1. Añade una opción para borrar un contacto por su nombre.
  2. Guarda también el correo electrónico y valida que tenga una @ y un punto después.
  3. Exporta la agenda a formato vCard (.vcf) para importarla en el móvil.

Conceptos clave

Repasa la ruta de Java o elige otro proyecto.