Java Básico

Java Básico

Historia

Línea de tiempo resumida

Java 5 (2004): Generics, anotaciones, autoboxing/unboxing, enums, varargs y for-each.

Java 6 (2006): Mejoras de rendimiento, scripting (JSR-223), API de compilación y actualizaciones en Swing.

Java 7 (2011): try-with-resources, multi-catch, diamond operator (<>), mejoras en NIO.2 y soporte para literales numéricos con guiones bajos.

Java 8 (2014): Lambdas, Streams, Optional, nueva API de fecha y hora (java.time) y métodos default/static en interfaces.

Java 9 (2017): Sistema de módulos, JShell, mejoras en colecciones y API.

Java 10 (2018): Inferencia de tipos con var (variables locales).

Java 11 (2018, LTS): Nuevo cliente HTTP, mejoras en Strings y colecciones, y actualizaciones de rendimiento.

Java 17 (2021, LTS): Sealed classes, mejoras de pattern matching y múltiples optimizaciones.

Java 21 (2023, LTS): Virtual threads, record patterns y mejoras modernas del lenguaje y la JVM.

Principales cosas a saber de Java

Conceptos base del lenguaje

  • Compilado a bytecode: El compilador transforma .java a bytecode (archivos .class) para que la JVM lo ejecute.
  • Orientado a objetos (POO): Clases y objetos como unidad central.
    • Encapsulación: Control de acceso a datos mediante modificadores y métodos.
    • Herencia: Reutilización y extensión de comportamiento.
    • Polimorfismo: Un mismo método puede comportarse distinto según el tipo del objeto.
    • Abstracción: Ocultar detalles y exponer lo esencial con interfaces y clases abstractas.
  • Fuertemente tipado: Cada variable tiene un tipo definido, y los cambios requieren conversión explícita.

Plataforma Java (JVM / JRE / JDK)

  • JVM (Java Virtual Machine): Ejecuta el bytecode. Carga clases, verifica, interpreta/compila (JIT) y gestiona memoria (GC).
  • JRE (Java Runtime Environment): Runtime para ejecutar apps (JVM + librerías). Hoy se usa menos como “descarga aparte”.
  • JDK (Java Development Kit): Para desarrollar. Incluye javac y herramientas (por ejemplo javadoc, jlink) + runtime.
  • JIT (Just-In-Time): Compila en ejecución para acelerar código “caliente”.
  • GC (Garbage Collector): Recolección automática de objetos no referenciados.

Estandarización y evolución

  • JSR (Java Specification Request): Especificaciones del Java Community Process (JCP).
  • JEP (JDK Enhancement Proposal): Propuestas de mejoras del JDK.

OpenJDK y distribuciones

  • ¿Java es open source?: La implementación más común hoy se basa en OpenJDK.
  • ¿Qué es OpenJDK?: Implementación open source de referencia (JDK + JVM + librerías).
  • ¿Por qué varias empresas tienen su versión de JDK?: Builds de OpenJDK con soporte, parches y ciclos de release. Ejemplos: Adoptium, Amazon Corretto, Azul Zulu, Oracle JDK.

Build tools y dependencias

  • Maven: Build y dependencias con pom.xml (convención, repositorios, lifecycle).
  • Gradle: Build con DSL (Groovy/Kotlin), flexible y muy usado en proyectos modernos.
  • Repositorios: Maven Central y repos privados (Nexus/Artifactory).

Instalación de Java

Primeros programas

image.png

Paquetes (package)

¿Qué es un paquete?

  • Es una forma de organizar las clases.
  • Evita conflictos de nombres.
  • Es equivalente a carpetas en el sistema de archivos.

Estructura típica:

src/
	com/
		example/
			Main.java

Buenas prácticas:

  • Siempre usar paquetes.
  • Convención: com.empresa.proyecto.
  • Todo en minúsculas.
  • Sin espacios ni caracteres especiales.

Imports

¿Qué es un import?

image.png

Si no utilizamos imports, tendríamos que invocar las clases predefinidas de Java o nuestras propias clases con el nombre completo cada vez:

image.png

Con imports:

image.png

  • El import no copia código. Solo le indica al compilador dónde encontrar clases.

Clases

¿Qué es una clase?

image.png

Una clase se puede ver como:

  • Una plantilla.
  • Un molde.
  • La unidad básica de organización en Java.

Todo en Java vive dentro de una clase.

Palabras reservadas (keywords)

Java tiene palabras que no pueden usarse como nombres de variables o clases. Son instrucciones especiales que el compilador reconoce.

Algunos ejemplos:

  • public, private, protected
  • class, interface, enum
  • static, final, void, new
  • int, double, boolean, char
  • if, else, switch, case, for, while, return
  • package, import
  • yield (en switch modernos)

Punto de entrada del programa (main)

image.png

Leyendo el código (de forma general):

  • public: accesible desde cualquier lugar.
  • static: pertenece a la clase, no a un objeto.
  • void: el método no retorna valor.
  • String[] args: arreglo de strings con argumentos de la línea de comandos.

¿Cómo sabe la JVM dónde empezar?

  • Cuando ejecuta java Main, la JVM busca la clase Main y el método public static void main(String[] args).
  • Si no lo encuentra, verás un error indicando que falta el método main.

Tipos de datos primitivos

  • Enteros: byte, short, int, long
  • Decimales: float, double
  • Booleano: boolean
  • Carácter: char
TipoTamañoRango aproximadoEjemplo
byte8 bits-128 a 127byte b = 10;
short16 bits-32,768 a 32,767short s = 3000;
int32 bits-2.1B a 2.1Bint n = 42;
long64 bitsMuy grandelong l = 9_000_000_000L;
float32 bitsPrecisión ~7 dígitosfloat f = 3.14f;
double64 bitsPrecisión ~15-16 dígitosdouble d = 3.14159;
booleanDepende de la JVMtrue o falseboolean ok = true;
char16 bits0 a 65,535 (UTF-16)char c = 'A';

Basicos

Variables y operadores

  • Declaración e inicialización: int edad = 20;, var nombre = "Alejandro";.
  • Scope (alcance): dónde “vive” una variable y desde qué partes del código se puede usar.
    • 1) Variables locales (dentro de un método o bloque)
      • Solo existen dentro del bloque { ... } donde se declaran.

      • Se crean al entrar al bloque y “mueren” al salir.

      • Ejemplo (bloque if):

        public class Demo {
        	public static void main(String[] args) {
        		int x = 10; // local de main
         
        		if (x > 5) {
        			int y = 20; // local del bloque if
        			System.out.println(x + y); // OK
        		}
         
        		// System.out.println(y); // ERROR: y no existe aquí
        	}
        }
      • Ejemplo (for):

        for (int i = 0; i < 3; i++) {
        	System.out.println(i);
        }
        // System.out.println(i); // ERROR: i era del for
      • Nota: var (Java 10+) solo se puede usar en variables locales:

        var nombre = "Alejandro"; // OK (local)
        // var edad;              // ERROR: debe inicializarse
    • 2) Parámetros de método (también son locales al método)
      • Solo se pueden usar dentro del método.

      • Ejemplo:

        static int sumar(int a, int b) {
        	return a + b;
        	// a y b no existen fuera de sumar
        }
    • 3) Campos de instancia (variables de clase, pero NO static)
      • Pertenecen a cada objeto.

      • Se acceden desde cualquier método de la clase (según el modificador: private, public, etc.).

      • Tienen valor por defecto si no se inicializan (por ejemplo 0, false, null).

      • Ejemplo:

        public class Cuenta {
        	private int saldo; // campo de instancia
         
        	public void depositar(int monto) {
        		saldo += monto; // usa el campo
        	}
         
        	public int verSaldo() {
        		return saldo;
        	}
        }
    • 4) Campos estáticos (de clase, static)
      • Pertenecen a la clase, no a un objeto.

      • Hay un solo valor compartido.

      • Ejemplo:

        public class Contador {
        	static int total = 0; // compartido
        	int id;               // por objeto
         
        	public Contador() {
        		total++;
        		id = total;
        	}
        }
    • 5) Variables “sombreadas” (shadowing): local vs campo
      • Si una variable local tiene el mismo nombre que un campo, la local “tapa” al campo.

      • Se usa this. para referirse al campo.

      • Ejemplo:

        public class Persona {
        	private String nombre;
         
        	public void setNombre(String nombre) {
        		this.nombre = nombre; // this.nombre = campo, nombre = parámetro
        	}
        }
    • Tip rápido: si se necesita que una variable se recuerde entre llamadas a métodos, normalmente debe ser un campo (instancia o static). Si solo sirve para un cálculo puntual, debe ser local.
  • Conversión de tipos (casting): implícito y explícito
    • Objetivo: pasar un valor de un tipo a otro (por ejemplo de int a double).

    • Regla general:

      • Widening (implícita / segura): de un tipo “más pequeño” a uno “más grande” (no se pierde información).
      • Narrowing (explícita / con riesgo): de un tipo “más grande” a uno “más pequeño” (puede haber pérdida de información).
    • Casting implícito (widening): el compilador lo hace automáticamente.

      int n = 10;
      double d = n;     // 10.0
      long l = n;       // 10
      float f = l;      // 10.0f
    • Casting explícito (narrowing): debes indicar el tipo con (tipo).

      double d = 9.99;
      int n = (int) d;          // 9  (pierde decimales)
       
      int x = 130;
      byte b = (byte) x;        // -126 (overflow)
    • Cuidado con:

      • Pérdida de decimales: double/float -> int/long trunca, no redondea.

      • Overflow/underflow: al convertir a byte/short/int puede “dar la vuelta”.

      • char y números: char es numérico (UTF-16), se puede convertir a int.

        char c = 'A';
        int code = c;   // 65
    • Tabla rápida de conversiones (primitivos numéricos):

      De Abyteshortcharintlongfloatdouble
      byteImplícitaExplícitaImplícitaImplícitaImplícitaImplícita
      shortExplícitaExplícitaImplícitaImplícitaImplícitaImplícita
      charExplícitaExplícitaImplícitaImplícitaImplícitaImplícita
      intExplícitaExplícitaExplícitaImplícitaImplícitaImplícita
      longExplícitaExplícitaExplícitaExplícitaImplícitaImplícita
      floatExplícitaExplícitaExplícitaExplícitaExplícitaImplícita
      doubleExplícitaExplícitaExplícitaExplícitaExplícitaExplícita
    • Extra (autoboxing/unboxing): entre primitivos y wrappers.

      Integer a = 10;      // autoboxing (int -> Integer)
      int b = a;           // unboxing (Integer -> int)
  • Operadores: aritméticos (+ - * / %), comparación (== != < > <= >=), lógicos (&& || !), asignación (= +=), ternario (cond ? a : b).

Control de flujo (if / switch / loops)

El control de flujo define qué se ejecuta, cuándo, y cuántas veces.

  • Selección: if y switch
  • Iteración: for, while, do-while, for-each
  • Control: break, continue, return

if / else if / else

  • Se usa cuando la decisión depende de condiciones booleanas.
  • Puedes encadenar varias condiciones con else if.
  • Consejo: si hay muchos else if, a veces un switch o una estructura de datos (por ejemplo Map) es más clara.
int edad = 20;
 
if (edad < 18) {
	System.out.println("Menor de edad");
} else if (edad < 65) {
	System.out.println("Adulto");
} else {
	System.out.println("Adulto mayor");
}

Detalles útiles:

  • Usar llaves { } siempre. Evita errores al agregar líneas después.
  • && y || son cortocircuito: si ya se sabe el resultado, no evalúa lo demás.
if (user != null && user.isActive()) {
	// isActive() solo se evalúa si user != null
}

switch (clásico)

  • Se usa cuando se elige entre casos discretos (valores específicos).
  • Funciona con int, char, String, enum (y más tipos según versión).
  • En el switch clásico es común usar break para evitar el fall-through
int dia = 3;
 
switch (dia) {
	case 1:
		System.out.println("Lunes");
		break;
	case 2:
		System.out.println("Martes");
		break;
	case 3:
		System.out.println("Miércoles");
		break;
	default:
		System.out.println("Día inválido");
}

switch moderno (Java 14+)

  • Usa -> y no necesita break en cada caso.
  • Puede ser expresión, es decir, puede devolver un valor.
  • Para devolver un valor en un bloque con varias líneas, se usa yield.
String tipo = "A";
 
String mensaje = switch (tipo) {
	case "A" -> "Excelente";
	case "B" -> "Bien";
	case "C" -> {
		String base = "Regular";
		yield base + " (puede mejorar)";
	}
	default -> "Desconocido";
};
 
System.out.println(mensaje);

Bucles

for (clásico): cuando sabes cuántas iteraciones necesitas.

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

while: cuando repites mientras se cumpla una condición (no sabes el número exacto).

int n = 3;
while (n > 0) {
	System.out.println(n);
	n--;
}

do-while: igual que while, pero garantiza al menos una ejecución.

int intento = 0;
do {
	intento++;
	System.out.println("Intento " + intento);
} while (intento < 3);

for-each: para recorrer colecciones o arrays de forma simple.

int[] nums = {1, 2, 3};
for (int x : nums) {
	System.out.println(x);
}

break y continue

  • break: sale del bucle (o del switch).
  • continue: salta a la siguiente iteración del bucle.
for (int i = 1; i <= 10; i++) {
	if (i == 5) {
		continue; // se salta el 5
	}
	if (i == 9) {
		break; // termina en 8
	}
	System.out.println(i);
}

Métodos

Un método es una unidad de código reutilizable que recibe datos (parámetros), hace una tarea, y opcionalmente retorna un resultado.

Modificadores y palabras clave muy comunes (static, final, etc.)

  • static: pertenece a la clase, no al objeto.
    • En campos: un solo valor compartido por todas las instancias.
    • En métodos: se puede llamar sin crear un objeto.
  • final: “no se puede cambiar” (según el contexto).
    • Variable/atributo final: no se puede reasignar.
    • Método final: no se puede sobreescribir.
    • Clase final: no se puede heredar.
  • Modificadores de acceso:
    • public: accesible desde cualquier lugar.
    • protected: accesible desde el paquete y desde subclases.
    • (sin keyword) (package-private): accesible solo dentro del paquete.
    • private: accesible solo dentro de la clase.
  • this: referencia al objeto actual.
  • super: referencia al “padre” (clase base), útil para llamar constructor o métodos del padre.
  • null: ausencia de referencia. Acceder a miembros sobre null produce NullPointerException.

Firma de un método

La firma incluye:

  • Nombre del método
  • Lista de parámetros (tipo + orden)
  • El tipo de retorno no distingue una sobrecarga por sí solo, pero sí hace parte de la declaración.
static int sumar(int a, int b) {
	return a + b;
}

return y void

  • Si el retorno no es void, se debe retornar un valor en todos los caminos posibles.
  • return; también sirve para salir temprano en métodos void.
static void imprimirSiPositivo(int n) {
	if (n <= 0) {
		return;
	}
	System.out.println(n);
}

Parámetros por valor (pass-by-value)

En Java siempre se pasa por valor:

  • Para primitivos, se copia el valor.
  • Para objetos, se copia la referencia (la dirección), no el objeto.

Esto significa que:

  • Puedes modificar el estado interno del objeto recibido.
  • No puedes “reemplazar” el objeto del llamador reasignando el parámetro.
static void cambiarNumero(int x) {
	x = 99; // no afecta al original
}
 
static void cambiarNombre(StringBuilder sb) {
	sb.append("!" ); // sí afecta al mismo objeto
	sb = new StringBuilder("nuevo"); // no cambia la referencia afuera
}

Sobrecarga (overloading)

Puedes tener varios métodos con el mismo nombre si cambian los parámetros (cantidad o tipos).

static int max(int a, int b) { return a > b ? a : b; }
static double max(double a, double b) { return a > b ? a : b; }

Buenas prácticas

  • Nombres claros: calcularTotal, validarEmail.
  • Métodos pequeños, una responsabilidad.
  • Evita demasiados parámetros. Si hay muchos, considera un objeto de configuración.

Arrays y Strings

Arrays

  • Tamaño fijo.
  • Índices desde 0 hasta length - 1.
int[] a = new int[3];
a[0] = 10;
a[1] = 20;
a[2] = 30;
 
System.out.println(a.length); // 3

Iteración:

for (int i = 0; i < a.length; i++) {
	System.out.println("a[" + i + "] = " + a[i]);
}
 
for (int x : a) {
	System.out.println(x);
}

String

  • Es inmutable: cualquier “cambio” crea un nuevo String.
  • Concatenar con + está bien para pocas uniones.
String s = "Hola";
s = s + " mundo"; // crea un nuevo String

StringBuilder (concatenación eficiente)

En bucles o muchas concatenaciones, usa StringBuilder.

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 3; i++) {
	sb.append(i).append(",");
}
String out = sb.toString();

Comparación: equals() vs ==

  • == compara referencias en objetos (si es el mismo objeto).
  • equals() compara contenido (según la implementación).
String a1 = new String("hola");
String a2 = new String("hola");
 
System.out.println(a1 == a2);        // false
System.out.println(a1.equals(a2));   // true

Tip: para evitar NullPointerException:

String rol = null;
if ("ADMIN".equals(rol)) {
	// seguro incluso si rol es null
}