📝 Java
← Volver

Java — Guía Completa

Índice

  1. Introducción y características generales
  2. Paradigmas que sigue Java
  3. Sintaxis básica
  4. Programación Orientada a Objetos en Java
    - Clases y objetos
    - Herencia y la palabra reservada extends
    - Interfaces y implements
    - Encapsulamiento
    - Polimorfismo
    - Abstracción
  5. Relaciones entre clases: herencia vs. composición vs. anidamiento
  6. Tipos de datos y variables
  7. Control de flujo
  8. Excepciones
  9. Colecciones
  10. Librerías y frameworks más importantes
  11. Java y la JVM
  12. Buenas prácticas

1. Introducción y características generales

Java es un lenguaje de programación compilado e interpretado, creado por Sun Microsystems en 1995 (actualmente mantenido por Oracle). Sus características más importantes son:


2. Paradigmas que sigue Java

Java es principalmente un lenguaje multi-paradigma, aunque su diseño central es:

2.1 Programación Orientada a Objetos (POO)

Es el paradigma principal. Todo el código se organiza en clases que definen atributos (estado) y métodos (comportamiento). Los cuatro pilares de la POO están presentes: encapsulamiento, herencia, polimorfismo y abstracción.

2.2 Programación Imperativa

Java describe paso a paso cómo se ejecutan las instrucciones: bucles for, asignaciones, condicionales if/else, etc.

2.3 Programación Funcional (desde Java 8)

A partir de Java 8 se incorporaron expresiones lambda, streams e interfaces funcionales, permitiendo aplicar conceptos funcionales como funciones de orden superior, inmutabilidad y composición de funciones.

// Ejemplo funcional con streams y lambdas
List<String> nombres = Arrays.asList("Ana", "Luis", "Pedro");
nombres.stream()
       .filter(n -> n.startsWith("A"))
       .forEach(System.out::println);

2.4 Programación Genérica

Mediante generics, Java permite escribir clases y métodos parametrizados por tipos, evitando duplicación de código y aumentando la seguridad de tipos.

public class Caja<T> {
    private T contenido;
    public void guardar(T item) { this.contenido = item; }
    public T obtener() { return contenido; }
}

3. Sintaxis básica

3.1 Estructura de un programa Java

// Declaración del paquete (opcional)
package com.ejemplo;

// Importaciones
import java.util.List;
import java.util.ArrayList;

// Declaración de la clase pública
public class HolaMundo {

    // Método principal — punto de entrada
    public static void main(String[] args) {
        System.out.println("Hola, mundo!");
    }
}

3.2 Variables y tipos primitivos

Tipo Tamaño Ejemplo
byte 8 bits byte b = 100;
short 16 bits short s = 30000;
int 32 bits int i = 42;
long 64 bits long l = 100L;
float 32 bits float f = 3.14f;
double 64 bits double d = 3.14159;
char 16 bits char c = 'A';
boolean 1 bit boolean ok = true;

3.3 Modificadores de acceso

Modificador Clase Paquete Subclase Mundo
public
protected
(paquete)
private

3.4 Palabras reservadas clave

Palabra Uso principal
class Declara una clase
interface Declara una interfaz
extends Herencia de clase
implements Implementación de interfaces
abstract Clase o método abstracto
final Clase no heredable, método no sobreescribible, constante
static Miembro de clase (no de instancia)
this Referencia al objeto actual
super Referencia a la clase padre
new Crea una nueva instancia
null Referencia vacía
instanceof Comprueba el tipo de un objeto
void Método sin valor de retorno

4. Programación Orientada a Objetos en Java

4.1 Clases y objetos

Una clase es la plantilla; un objeto es la instancia concreta.

public class Persona {
    // Atributos (estado)
    private String nombre;
    private int edad;

    // Constructor
    public Persona(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    // Métodos (comportamiento)
    public String getNombre() { return nombre; }
    public int getEdad()      { return edad; }

    public void saludar() {
        System.out.println("Hola, soy " + nombre);
    }
}

// Uso
Persona p = new Persona("Ana", 30);
p.saludar(); // "Hola, soy Ana"

4.2 Herencia y la palabra reservada extends

La herencia es uno de los pilares de la POO. Permite que una clase (subclase o clase hija) adquiera los atributos y métodos de otra clase (superclase o clase padre), añadiendo o redefiniendo comportamiento.

En Java, la herencia simple se expresa con la palabra reservada extends:

// Clase padre (superclase)
public class Animal {
    protected String nombre;

    public Animal(String nombre) {
        this.nombre = nombre;
    }

    public void hacerSonido() {
        System.out.println("Sonido genérico");
    }
}

// Clase hija (subclase) — hereda de Animal
public class Perro extends Animal {

    private String raza;

    public Perro(String nombre, String raza) {
        super(nombre); // llama al constructor del padre
        this.raza = raza;
    }

    @Override
    public void hacerSonido() {
        System.out.println(nombre + " dice: ¡Guau!");
    }

    public String getRaza() { return raza; }
}

Puntos clave sobre extends:

// Uso
Perro miPerro = new Perro("Rex", "Labrador");
miPerro.hacerSonido();      // "Rex dice: ¡Guau!"
System.out.println(miPerro.getRaza()); // "Labrador"

// Un Perro ES-UN Animal (relación "is-a")
Animal a = new Perro("Toby", "Pastor Alemán");
a.hacerSonido(); // polimorfismo: "Toby dice: ¡Guau!"

Herencia en cadena

public class Cachorro extends Perro {
    // hereda de Perro, que hereda de Animal
}

La clase final impide la herencia

public final class CuentaBancaria {
    // Ninguna clase puede extender esta
}

4.3 Interfaces y implements

Una interfaz define un contrato (qué debe hacer una clase) sin implementar el cómo (salvo los métodos default desde Java 8). Una clase puede implementar múltiples interfaces.

public interface Volador {
    void volar(); // método abstracto por defecto

    default String descripcion() {
        return "Soy un ser que vuela";
    }
}

public interface Nadador {
    void nadar();
}

// Una clase puede implementar varias interfaces
public class Pato extends Animal implements Volador, Nadador {
    public Pato(String nombre) { super(nombre); }

    @Override
    public void volar() { System.out.println(nombre + " vuela bajo"); }

    @Override
    public void nadar() { System.out.println(nombre + " nada"); }

    @Override
    public void hacerSonido() { System.out.println("¡Cuac!"); }
}

Diferencia clave entre extends e implements:

Aspecto extends implements
Se usa con Clases Interfaces
Cantidad Solo una clase Múltiples interfaces
Hereda implementación Solo métodos default
Relación "es-un" (herencia real) "puede-hacer" (contrato)

4.4 Encapsulamiento

Ocultar el estado interno y exponer solo lo necesario mediante getters y setters.

public class CuentaBancaria {
    private double saldo; // privado: no accesible desde fuera

    public double getSaldo() { return saldo; }

    public void depositar(double cantidad) {
        if (cantidad > 0) saldo += cantidad;
    }

    public void retirar(double cantidad) {
        if (cantidad > 0 && cantidad <= saldo) saldo -= cantidad;
    }
}

4.5 Polimorfismo

Un mismo método puede comportarse de forma distinta según el objeto real que lo ejecuta.

Animal[] animales = { new Perro("Rex", "Labrador"), new Pato("Donald") };

for (Animal a : animales) {
    a.hacerSonido(); // cada uno ejecuta su propia versión
}
// Salida:
// Rex dice: ¡Guau!
// ¡Cuac!

4.6 Abstracción

Las clases abstractas no se pueden instanciar directamente; sirven como base para otras clases.

public abstract class Figura {
    protected String color;

    public abstract double calcularArea(); // subclases deben implementarlo

    public void mostrarColor() {
        System.out.println("Color: " + color);
    }
}

public class Circulo extends Figura {
    private double radio;

    public Circulo(double radio, String color) {
        this.radio = radio;
        this.color = color;
    }

    @Override
    public double calcularArea() {
        return Math.PI * radio * radio;
    }
}

5. Relaciones entre clases: herencia vs. composición vs. anidamiento

Es fundamental distinguir los distintos tipos de relaciones entre clases, ya que son conceptos frecuentes en exámenes:

5.1 Herencia ("es-un") — extends

// Un Gato ES UN Animal
public class Gato extends Animal { ... }

5.2 Composición ("tiene-un")

Una clase contiene una instancia de otra clase como atributo. No se usa extends.

public class Motor {
    private int cilindros;
    public void arrancar() { System.out.println("Motor arrancando"); }
}

public class Coche {
    // Composición: un Coche TIENE UN Motor
    private Motor motor;

    public Coche() {
        this.motor = new Motor();
    }

    public void conducir() {
        motor.arrancar();
        System.out.println("Coche en marcha");
    }
}

La composición es preferida a la herencia cuando la relación no es genuinamente "es-un", ya que reduce el acoplamiento.

5.3 Agregación

Similar a la composición, pero el objeto contenido puede existir de forma independiente.

public class Universidad {
    private List<Estudiante> estudiantes; // los estudiantes existen sin la universidad
}

5.4 Clases anidadas (nested classes)

Una clase puede declararse dentro de otra clase. No tienen relación de herencia.

public class Externa {
    private int valor = 10;

    // Clase interna (nested)
    class Interna {
        void mostrar() {
            System.out.println("Valor: " + valor); // accede a miembros de la externa
        }
    }

    // Clase estática anidada
    static class AnidadaEstatica {
        void mostrar() { System.out.println("Clase estática anidada"); }
    }
}

6. Tipos de datos y variables

6.1 Strings

String es una clase (no un tipo primitivo). Los strings son inmutables en Java.

String saludo = "Hola";
String nombre = new String("Mundo");
String completo = saludo + ", " + nombre + "!";

// Métodos útiles
saludo.length();          // 4
saludo.toUpperCase();     // "HOLA"
saludo.substring(1, 3);   // "ol"
saludo.equals("Hola");    // true (comparar contenido, no con ==)
saludo.contains("ol");    // true

Para concatenaciones intensivas, usar StringBuilder:

StringBuilder sb = new StringBuilder();
sb.append("Hola").append(", ").append("mundo");
String resultado = sb.toString();

6.2 Arrays

int[] numeros = new int[5];         // array de 5 enteros
int[] inicializado = {1, 2, 3, 4, 5};
String[] colores = {"rojo", "verde", "azul"};

// Acceso
System.out.println(colores[0]); // "rojo"
System.out.println(numeros.length); // 5

7. Control de flujo

7.1 Condicionales

int nota = 75;

if (nota >= 90) {
    System.out.println("Sobresaliente");
} else if (nota >= 70) {
    System.out.println("Notable");
} else if (nota >= 50) {
    System.out.println("Aprobado");
} else {
    System.out.println("Suspenso");
}
// switch
String dia = "LUNES";
switch (dia) {
    case "LUNES":
    case "MARTES":
        System.out.println("Inicio de semana");
        break;
    case "VIERNES":
        System.out.println("Fin de semana laboral");
        break;
    default:
        System.out.println("Otro día");
}

7.2 Bucles

// for clásico
for (int i = 0; i < 5; i++) {
    System.out.println(i);
}

// for-each (enhanced for)
int[] nums = {1, 2, 3};
for (int n : nums) {
    System.out.println(n);
}

// while
int i = 0;
while (i < 5) {
    System.out.println(i++);
}

// do-while (ejecuta al menos una vez)
int j = 0;
do {
    System.out.println(j++);
} while (j < 5);

8. Excepciones

Java usa excepciones para gestionar errores en tiempo de ejecución.

// try-catch-finally
try {
    int resultado = 10 / 0; // lanza ArithmeticException
} catch (ArithmeticException e) {
    System.out.println("Error: " + e.getMessage());
} finally {
    System.out.println("Bloque finally: siempre se ejecuta");
}

Jerarquía de excepciones

Throwable
├── Error (errores graves del sistema: OutOfMemoryError)
└── Exception
    ├── RuntimeException (no comprobadas: NullPointerException, ArrayIndexOutOfBoundsException)
    └── IOException (comprobadas: deben ser capturadas o declaradas)

Excepciones comprobadas vs. no comprobadas

Crear excepciones personalizadas

public class SaldoInsuficienteException extends Exception {
    public SaldoInsuficienteException(String mensaje) {
        super(mensaje);
    }
}

// Uso
public void retirar(double cantidad) throws SaldoInsuficienteException {
    if (cantidad > saldo) {
        throw new SaldoInsuficienteException("Saldo insuficiente");
    }
    saldo -= cantidad;
}

9. Colecciones

El framework de colecciones de Java (java.util) es fundamental.

Interfaces principales

Interfaz Características Implementaciones comunes
List Ordenada, permite duplicados ArrayList, LinkedList
Set Sin duplicados HashSet, TreeSet
Map Pares clave-valor HashMap, TreeMap
Queue FIFO LinkedList, PriorityQueue
Deque Doble extremo ArrayDeque
// List
List<String> lista = new ArrayList<>();
lista.add("Java");
lista.add("Python");
lista.get(0);      // "Java"
lista.size();      // 2

// Map
Map<String, Integer> edades = new HashMap<>();
edades.put("Ana", 30);
edades.put("Luis", 25);
edades.get("Ana");         // 30
edades.containsKey("Luis"); // true

// Set
Set<String> conjunto = new HashSet<>();
conjunto.add("a");
conjunto.add("a"); // ignorado (duplicado)
conjunto.size();   // 1

10. Librerías y frameworks más importantes

10.1 JUnit — pruebas unitarias

JUnit es el framework estándar para pruebas unitarias en Java. La versión actual es JUnit 5 (también llamado JUnit Jupiter).

Conceptos clave

Anotaciones principales de JUnit 5

Anotación Función
@Test Marca un método como prueba
@BeforeEach Ejecuta antes de cada prueba
@AfterEach Ejecuta después de cada prueba
@BeforeAll Ejecuta una vez antes de todas las pruebas (método static)
@AfterAll Ejecuta una vez después de todas las pruebas
@DisplayName Nombre descriptivo para la prueba
@Disabled Deshabilita una prueba temporalmente
@ParameterizedTest Prueba con múltiples conjuntos de datos

Ejemplo completo con JUnit 5

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

public class CalculadoraTest {

    private Calculadora calc;

    @BeforeEach
    void setUp() {
        calc = new Calculadora(); // se ejecuta antes de cada @Test
    }

    @Test
    @DisplayName("La suma de dos positivos es correcta")
    void testSuma() {
        int resultado = calc.sumar(3, 4);
        assertEquals(7, resultado, "3 + 4 debe ser 7");
    }

    @Test
    void testDivisionPorCero() {
        assertThrows(ArithmeticException.class, () -> calc.dividir(10, 0));
    }

    @Test
    void testResultadoNoNulo() {
        assertNotNull(calc.sumar(1, 1));
    }

    @AfterEach
    void tearDown() {
        calc = null; // limpieza
    }
}

Aserciones más usadas

Método Descripción
assertEquals(esperado, actual) Comprueba igualdad
assertNotEquals(a, b) Comprueba que no son iguales
assertTrue(condicion) Comprueba que la condición es true
assertFalse(condicion) Comprueba que la condición es false
assertNull(objeto) Comprueba que el objeto es null
assertNotNull(objeto) Comprueba que el objeto no es null
assertThrows(Excepcion, lambda) Comprueba que se lanza una excepción
assertAll(ejecutables...) Agrupa varias aserciones

Dependencia Maven (JUnit 5)

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>

10.2 Maven y Gradle — gestión de dependencias

Maven gestiona el ciclo de vida del proyecto mediante un fichero pom.xml. Gradle es una alternativa más moderna con DSL Groovy/Kotlin.

<!-- pom.xml básico -->
<project>
    <groupId>com.ejemplo</groupId>
    <artifactId>mi-proyecto</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>6.1.0</version>
        </dependency>
    </dependencies>
</project>

10.3 Spring Framework

El framework más popular del ecosistema Java empresarial. Proporciona:

@RestController
@RequestMapping("/api")
public class HolaController {

    @GetMapping("/saludo")
    public String saludo() {
        return "Hola desde Spring Boot";
    }
}

10.4 Hibernate

Framework ORM (Object-Relational Mapping) que mapea clases Java a tablas de una base de datos relacional, eliminando la necesidad de escribir SQL manualmente en la mayoría de casos.

@Entity
@Table(name = "empleados")
public class Empleado {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "nombre")
    private String nombre;

    @Column(name = "salario")
    private double salario;
    // getters y setters...
}

10.5 Log4j / SLF4J

Librerías de logging estándar en Java.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MiServicio {
    private static final Logger log = LoggerFactory.getLogger(MiServicio.class);

    public void procesar() {
        log.info("Iniciando procesamiento");
        log.debug("Valor calculado: {}", 42);
        log.error("Error inesperado", new RuntimeException("fallo"));
    }
}

11. Java y la JVM

El proceso de ejecución de un programa Java es:

Código fuente (.java)
        ↓  javac (compilador)
   Bytecode (.class)
        ↓  JVM (intérprete + JIT compiler)
Código máquina nativo

12. Buenas prácticas

/**
 * Calcula el área de un círculo dado su radio.
 *
 * @param radio el radio del círculo (debe ser positivo)
 * @return el área del círculo
 * @throws IllegalArgumentException si el radio es negativo
 */
public double calcularArea(double radio) {
    if (radio < 0) throw new IllegalArgumentException("El radio no puede ser negativo");
    return Math.PI * radio * radio;
}

Documento elaborado como guía de estudio para el lenguaje Java, cubriendo sintaxis, paradigmas, POO, relaciones entre clases y las librerías más utilizadas en el ecosistema Java profesional.