InterviewHack.ai
Start free
Blog/Preguntas de entrevista de Java — 50 con código y respuestas

Preguntas de entrevista de Java — 50 con código y respuestas

16 de septiembre de 2026

javabackend

Artículo completo "Preguntas de entrevista de Java — 50 con código y respuestas" para el blog de InterviewHack.ai. Listo para insertar en la tabla blog_posts vía el cron/admin. slug sugerido: preguntas-entrevista-java-50-con-codigo-y-respuestas, lang: es.

Preguntas de entrevista de Java — 50 con código y respuestas

Si estás preparando una entrevista de Java, esta guía es todo lo que necesitás. Armé 50 preguntas reales — las que efectivamente caen en empresas tech, desde startups hasta grandes corporaciones — con respuestas detalladas y código que podés ejecutar vos mismo. Cubrimos desde fundamentos hasta concurrencia, colecciones, JVM y design patterns.


1. Fundamentos del lenguaje

1. ¿Cuál es la diferencia entre `==` y `.equals()` en Java?

== compara referencias (si dos variables apuntan al mismo objeto en memoria). .equals() compara el contenido lógico del objeto.

java
String a = new String("hola");
String b = new String("hola");

System.out.println(a == b);       // false — objetos distintos en heap
System.out.println(a.equals(b));  // true  — mismo contenido

Trampa frecuente: con el String Pool, los literales a veces sí tienen la misma referencia:

java
String x = "hola";
String y = "hola";
System.out.println(x == y); // true — misma entrada en el pool

Por eso siempre usá .equals() para comparar cadenas.


2. ¿Qué es el String Pool?

La JVM mantiene un pool de strings internado en el heap. Cuando escribís un literal "texto", Java busca si ya existe ese valor en el pool; si existe, reutiliza el objeto. Esto ahorra memoria porque los strings son inmutables.

java
String s1 = "java";           // va al pool
String s2 = "java";           // reutiliza el mismo objeto
String s3 = new String("java"); // fuerza un nuevo objeto fuera del pool

System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1 == s3.intern()); // true — intern() lo manda al pool

3. ¿Qué significa que Java es "pass-by-value"?

Java siempre pasa argumentos por valor, no por referencia. Para tipos primitivos eso es obvio. Para objetos, el valor que se pasa es la copia de la referencia (el puntero), no el objeto en sí.

java
void cambiar(int n) { n = 99; }          // no afecta el original
void modificar(StringBuilder sb) { sb.append(" mundo"); } // sí afecta el objeto
void reasignar(StringBuilder sb) { sb = new StringBuilder("otro"); } // no afecta la var original

int x = 1;
cambiar(x);
System.out.println(x); // 1 — sin cambio

StringBuilder s = new StringBuilder("hola");
modificar(s);
System.out.println(s); // "hola mundo" — el objeto fue mutado

reasignar(s);
System.out.println(s); // "hola mundo" — la reasignación local no se propaga

4. ¿Cuál es la diferencia entre `final`, `finally` y `finalize`?

  • final: modificador. En variables → constante. En métodos → no se puede sobreescribir. En clases → no se puede heredar.
  • finally: bloque que siempre se ejecuta después de un try/catch, haya o no excepción. Ideal para cerrar recursos.
  • finalize: método de Object llamado por el GC antes de recolectar el objeto. Deprecado desde Java 9 — usá try-with-resources o Cleaner.
java
// finally
try {
    int r = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("error");
} finally {
    System.out.println("siempre se ejecuta"); // siempre
}

// final en variable
final int MAX = 100;
// MAX = 200; // error de compilación

5. ¿Qué es autoboxing y unboxing?

Autoboxing es la conversión automática de un tipo primitivo a su wrapper (int → Integer). Unboxing es la inversa.

java
List<Integer> lista = new ArrayList<>();
lista.add(5);          // autoboxing: int 5 → Integer.valueOf(5)
int n = lista.get(0);  // unboxing: Integer → int

// Trampa de rendimiento en loops intensivos:
long sum = 0;
List<Long> nums = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) nums.add((long) i);
for (Long v : nums) sum += v; // unboxing en cada iteración — costoso

6. Explicá la diferencia entre `ArrayList` y `LinkedList`

| Operación | ArrayList | LinkedList |

|---|---|---|

| Acceso por índice (get) | O(1) | O(n) |

| Inserción al final | O(1) amortizado | O(1) |

| Inserción al principio/medio | O(n) | O(1) (si tenés el nodo) |

| Memoria | Compacta (array interno) | Mayor (nodos con punteros) |

Usá ArrayList para la mayoría de los casos. LinkedList solo si hacés muchas inserciones/eliminaciones en el medio y raramente accedés por índice.


7. ¿Qué es la diferencia entre `HashMap`, `LinkedHashMap` y `TreeMap`?

  • HashMap: sin orden garantizado. O(1) para get/put. El más rápido para uso general.
  • LinkedHashMap: mantiene el orden de inserción. Levemente más lento que HashMap.
  • TreeMap: ordena las claves por su orden natural (o un Comparator). O(log n) para operaciones. Implementa NavigableMap.
java
Map<String, Integer> hash  = new HashMap<>();    // sin orden
Map<String, Integer> linked = new LinkedHashMap<>(); // orden inserción
Map<String, Integer> tree   = new TreeMap<>();   // orden alfabético

// TreeMap: útil para rangos
TreeMap<Integer, String> rangos = new TreeMap<>();
rangos.put(1, "uno"); rangos.put(3, "tres"); rangos.put(5, "cinco");
System.out.println(rangos.headMap(4)); // {1=uno, 3=tres}

8. ¿Qué es la diferencia entre `HashSet` y `TreeSet`?

HashSet usa una tabla de hash → O(1) para add/contains/remove, sin orden. TreeSet usa un árbol rojo-negro → O(log n), mantiene orden natural. Usá HashSet salvo que necesites el orden.


9. ¿Cómo funciona `hashCode()` y `equals()` juntos, y por qué es importante su contrato?

Contrato: si dos objetos son iguales según equals(), deben tener el mismo hashCode(). Si rompés esto, las colecciones basadas en hash (HashMap, HashSet) funcionan mal — podés insertar duplicados o no encontrar objetos.

java
class Punto {
    int x, y;
    Punto(int x, int y) { this.x = x; this.y = y; }

    @Override public boolean equals(Object o) {
        if (!(o instanceof Punto)) return false;
        Punto p = (Punto) o;
        return x == p.x && y == p.y;
    }

    @Override public int hashCode() {
        return Objects.hash(x, y); // usa los mismos campos que equals
    }
}

Set<Punto> set = new HashSet<>();
set.add(new Punto(1, 2));
System.out.println(set.contains(new Punto(1, 2))); // true — funciona

Sin sobreescribir hashCode(), set.contains(new Punto(1,2)) devuelve false aunque los valores sean iguales.


10. ¿Qué es `Comparable` vs `Comparator`?

  • Comparable: la clase implementa su propio orden natural con compareTo(). "Soy comparable."
  • Comparator: clase externa que define cómo comparar dos objetos. "Alguien externo me ordena."
java
// Comparable — orden natural por nombre
class Empleado implements Comparable<Empleado> {
    String nombre; int salario;
    Empleado(String n, int s) { nombre = n; salario = s; }

    @Override public int compareTo(Empleado otro) {
        return nombre.compareTo(otro.nombre);
    }
}

// Comparator — orden alternativo por salario
Comparator<Empleado> porSalario = Comparator.comparingInt(e -> e.salario);

List<Empleado> lista = Arrays.asList(
    new Empleado("Carla", 80000),
    new Empleado("Ana", 95000)
);
Collections.sort(lista);              // usa Comparable (por nombre)
lista.sort(porSalario);               // usa Comparator (por salario)

2. Programación Orientada a Objetos

11. Explicá los 4 pilares de la OOP en Java con ejemplos

1. Encapsulamiento: ocultar el estado interno y exponer solo lo necesario.

java
class CuentaBancaria {
    private double saldo; // privado

    public void depositar(double monto) {
        if (monto > 0) saldo += monto;
    }
    public double getSaldo() { return saldo; }
}

2. Herencia: reutilizar comportamiento de una clase padre.

java
class Vehiculo { void arrancar() { System.out.println("arrancando"); } }
class Auto extends Vehiculo {
    @Override void arrancar() { System.out.println("auto arrancando"); }
}

3. Polimorfismo: una referencia del tipo padre puede apuntar a un hijo.

java
Vehiculo v = new Auto();
v.arrancar(); // "auto arrancando" — dynamic dispatch

4. Abstracción: exponer solo la interfaz relevante, ocultar la implementación.

java
abstract class Forma { abstract double area(); }
class Circulo extends Forma {
    double radio;
    Circulo(double r) { radio = r; }
    @Override double area() { return Math.PI * radio * radio; }
}

12. ¿Cuál es la diferencia entre clase abstracta e interfaz?

| Aspecto | Clase abstracta | Interfaz |

|---|---|---|

| Herencia múltiple | No | Sí (implements múltiples) |

| Estado (campos) | Sí | Solo constantes (static final) |

| Constructores | Sí | No |

| Métodos concretos | Sí | Desde Java 8: default y static |

| Cuándo usar | Compartir código + estado | Definir un contrato |


13. ¿Qué es el principio de sustitución de Liskov (LSP)?

Si S es un subtipo de T, entonces objetos de tipo T pueden reemplazarse por objetos de tipo S sin alterar el comportamiento correcto del programa. En la práctica: una subclase no debería sorprender al código que usa la superclase.

java
// Violación clásica: Rectángulo y Cuadrado
class Rectangulo {
    protected int ancho, alto;
    void setAncho(int a) { ancho = a; }
    void setAlto(int a) { alto = a; }
    int area() { return ancho * alto; }
}

class Cuadrado extends Rectangulo {
    @Override void setAncho(int a) { ancho = alto = a; } // viola LSP
    @Override void setAlto(int a) { ancho = alto = a; }
}

// Si el código asume que puede setear ancho y alto independientemente,
// Cuadrado lo rompe.

La solución: no hacer Cuadrado extends Rectangulo; hacerlos implementar una interfaz Forma por separado.


14. ¿Qué es el patrón Singleton y cómo se implementa de forma thread-safe?

java
// Double-checked locking — thread-safe y lazy
public class Configuracion {
    private static volatile Configuracion instancia;

    private Configuracion() {}

    public static Configuracion getInstance() {
        if (instancia == null) {
            synchronized (Configuracion.class) {
                if (instancia == null) {
                    instancia = new Configuracion();
                }
            }
        }
        return instancia;
    }
}

La mejor práctica moderna es usar un enum (garantiza thread-safety y resistencia a serialización):

java
public enum Config {
    INSTANCE;
    public void doSomething() { ... }
}

15. ¿Qué es el patrón Builder y cuándo lo usás?

Cuando un objeto tiene muchos parámetros opcionales. Evita constructores con 10 argumentos y hace el código legible.

java
class Pedido {
    private final String producto;
    private final int cantidad;
    private final boolean envioExpress;
    private final String cupon;

    private Pedido(Builder b) {
        producto = b.producto; cantidad = b.cantidad;
        envioExpress = b.envioExpress; cupon = b.cupon;
    }

    static class Builder {
        private final String producto;
        private int cantidad = 1;
        private boolean envioExpress = false;
        private String cupon = null;

        Builder(String producto) { this.producto = producto; }
        Builder cantidad(int c) { cantidad = c; return this; }
        Builder envioExpress() { envioExpress = true; return this; }
        Builder cupon(String c) { cupon = c; return this; }
        Pedido build() { return new Pedido(this); }
    }
}

// Uso:
Pedido p = new Pedido.Builder("Laptop")
    .cantidad(2)
    .envioExpress()
    .cupon("LATAM20")
    .build();

3. Excepciones

16. ¿Cuál es la diferencia entre `checked` y `unchecked` exceptions?

  • Checked: heredan de Exception (no de RuntimeException). El compilador obliga a capturarlas o declararlas en la firma del método (throws). Ej: IOException, SQLException.
  • Unchecked: heredan de RuntimeException. El compilador no obliga. Ej: NullPointerException, IllegalArgumentException, ArrayIndexOutOfBoundsException.

Regla práctica: usá checked para condiciones recuperables del negocio; unchecked para errores de programación.


17. ¿Qué es `try-with-resources` y por qué es mejor que `try-finally`?

try-with-resources cierra automáticamente cualquier objeto que implemente AutoCloseable, incluso si hay excepción. Más limpio y menos propenso a errores.

java
// Antes (try-finally — verbose y con bug potencial)
BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("archivo.txt"));
    System.out.println(br.readLine());
} finally {
    if (br != null) br.close();
}

// Con try-with-resources
try (BufferedReader br = new BufferedReader(new FileReader("archivo.txt"))) {
    System.out.println(br.readLine());
} // br.close() se llama automáticamente

4. Java 8+ y programación funcional

18. ¿Qué son las expresiones lambda y las functional interfaces?

Una functional interface tiene exactamente un método abstracto. Las lambdas las implementan de forma concisa.

java
// Functional interface custom
@FunctionalInterface
interface Transformador<T> {
    T aplicar(T valor);
}

Transformador<String> mayusculas = s -> s.toUpperCase();
System.out.println(mayusculas.aplicar("hola")); // "HOLA"

// Con las de JDK
Function<Integer, Integer> doble = n -> n * 2;
Predicate<String> noVacio = s -> !s.isEmpty();
Consumer<String> imprimir = System.out::println;
Supplier<List<String>> nuevaLista = ArrayList::new;

19. Explicá el Stream API con un ejemplo complejo

java
List<Empleado> empleados = Arrays.asList(
    new Empleado("Ana", "IT", 95000),
    new Empleado("Bob", "IT", 75000),
    new Empleado("Carla", "HR", 60000),
    new Empleado("Diego", "IT", 110000),
    new Empleado("Eva", "HR", 70000)
);

// Promedio de salario por departamento, solo mayores a 65000
Map<String, Double> resultado = empleados.stream()
    .filter(e -> e.salario > 65000)
    .collect(Collectors.groupingBy(
        e -> e.departamento,
        Collectors.averagingInt(e -> e.salario)
    ));

// {IT=93333.33, HR=70000.0}

20. ¿Qué es `Optional` y cuándo lo usás?

Optional es un contenedor que puede o no tener un valor. Comunica explícitamente que un resultado puede ser nulo, sin lanzar NullPointerException.

java
Optional<String> nombre = Optional.ofNullable(obtenerNombreDeDB()); // puede ser null

// Mal uso — lo usa como if (!) 
if (nombre.isPresent()) {
    System.out.println(nombre.get());
}

// Buen uso — fluido
nombre
    .map(String::toUpperCase)
    .filter(n -> n.length() > 3)
    .ifPresent(System.out::println);

String fallback = nombre.orElse("Desconocido");
String calculado = nombre.orElseGet(() -> calcularDefault());

Usalo como tipo de retorno de métodos, no como parámetro de método ni en campos de clase.


21. ¿Cuál es la diferencia entre `map()` y `flatMap()` en streams?

  • map(): transforma cada elemento → devuelve Stream.
  • flatMap(): cada elemento se convierte en un stream que se "aplana" → devuelve Stream.
java
List<List<Integer>> listas = Arrays.asList(
    Arrays.asList(1, 2, 3),
    Arrays.asList(4, 5),
    Arrays.asList(6)
);

// map: Stream<List<Integer>>
listas.stream().map(l -> l).forEach(System.out::println); // [1,2,3] [4,5] [6]

// flatMap: Stream<Integer>
listas.stream().flatMap(Collection::stream).forEach(System.out::println); // 1 2 3 4 5 6

// Con Optional
Optional<String> opt = Optional.of("hola");
Optional<Integer> len = opt.flatMap(s -> Optional.of(s.length())); // Optional[4]

22. ¿Qué son los method references?

Atajo sintáctico para lambdas que solo invocan un método existente.

java
// Lambda equivalente vs method reference
list.stream().map(s -> s.toUpperCase());   // lambda
list.stream().map(String::toUpperCase);   // method ref a instancia

list.stream().forEach(s -> System.out.println(s));
list.stream().forEach(System.out::println);

// Referencia a constructor
Stream<StringBuilder> sbs = Stream.of("a", "b").map(StringBuilder::new);

// Referencia a método estático
Stream<Integer> nums = Stream.of("-1", "2", "-3").map(Integer::parseInt);

23. ¿Qué es la interfaz `Predicate` y cómo se compone?

java
Predicate<Integer> positivo = n -> n > 0;
Predicate<Integer> par = n -> n % 2 == 0;

Predicate<Integer> positivoPar = positivo.and(par);
Predicate<Integer> positivoOImpar = positivo.or(par.negate());

List<Integer> nums = Arrays.asList(-3, -2, 0, 1, 2, 4, 5);
nums.stream().filter(positivoPar).forEach(System.out::println); // 2, 4

5. Concurrencia

24. ¿Cuál es la diferencia entre `Thread` y `Runnable`?

java
// Extendiendo Thread — limita herencia múltiple
class MiThread extends Thread {
    @Override public void run() { System.out.println("corriendo"); }
}
new MiThread().start();

// Implementando Runnable — preferido
Runnable tarea = () -> System.out.println("corriendo");
new Thread(tarea).start();

// Mejor aún — usar ExecutorService
ExecutorService exec = Executors.newFixedThreadPool(4);
exec.submit(() -> System.out.println("pool"));
exec.shutdown();

25. ¿Qué es `synchronized` y cuáles son sus limitaciones?

synchronized asegura que solo un thread a la vez ejecute un bloque o método sobre el mismo monitor (lock).

java
class Contador {
    private int valor = 0;

    public synchronized void incrementar() { valor++; }
    public synchronized int getValor() { return valor; }
}

Limitaciones: no permite lock con timeout, ni intentar adquirirlo sin bloquear, ni múltiples condiciones. Para eso existe ReentrantLock.


26. Explicá `volatile` en Java

volatile garantiza visibilidad: los cambios de un thread son inmediatamente visibles a otros. No garantiza atomicidad.

java
class Worker {
    private volatile boolean corriendo = true;

    public void stop() { corriendo = false; }

    public void run() {
        while (corriendo) {
            // sin volatile, este loop podría no ver el cambio de otro thread
        }
    }
}

Para operaciones compuestas (leer-modificar-escribir) usá AtomicInteger o synchronized.


27. ¿Qué es un `deadlock` y cómo lo prevenís?

Un deadlock ocurre cuando dos o más threads esperan mutuamente que el otro libere un lock.

java
// Deadlock clásico
Object lockA = new Object();
Object lockB = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lockA) {
        synchronized (lockB) { /* ... */ }
    }
});
Thread t2 = new Thread(() -> {
    synchronized (lockB) {   // orden invertido — deadlock
        synchronized (lockA) { /* ... */ }
    }
});

Prevención: adquirir siempre los locks en el mismo orden en todos los threads. También podés usar tryLock() con timeout con ReentrantLock.


28. ¿Qué es `ExecutorService` y cuándo lo preferís a `new Thread()`?

ExecutorService es un pool de threads que reutiliza hilos, limita concurrencia y gestiona el ciclo de vida. Mucho más eficiente que crear un Thread por tarea.

java
ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

Future<Integer> futuro = pool.submit(() -> {
    Thread.sleep(1000);
    return 42;
});

System.out.println(futuro.get()); // bloquea hasta que termina → 42

pool.shutdown();
pool.awaitTermination(5, TimeUnit.SECONDS);

29. ¿Qué son `CompletableFuture` y la programación asíncrona?

java
CompletableFuture<String> cf = CompletableFuture
    .supplyAsync(() -> llamarAPI())          // en otro thread
    .thenApply(resultado -> resultado.toUpperCase())
    .thenCompose(r -> CompletableFuture.supplyAsync(() -> procesar(r)))
    .exceptionally(ex -> "error: " + ex.getMessage());

// Combinar dos futuros
CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> "hola");
CompletableFuture<String> b = CompletableFuture.supplyAsync(() -> " mundo");
CompletableFuture<String> combinado = a.thenCombine(b, (x, y) -> x + y);
System.out.println(combinado.get()); // "hola mundo"

30. ¿Qué es `ConcurrentHashMap` y cómo difiere de `HashMap` sincronizado?

ConcurrentHashMap usa segmentación: divide el mapa en segmentos y lockea solo el segmento necesario. Collections.synchronizedMap(map) lockea el mapa entero en cada operación → mucho menos throughput.

java
ConcurrentHashMap<String, Integer> mapa = new ConcurrentHashMap<>();
mapa.put("a", 1);
mapa.computeIfAbsent("b", k -> calcular(k)); // atómico
mapa.merge("a", 1, Integer::sum);             // atómico — incremento seguro

6. JVM, memoria y rendimiento

31. ¿Cómo funciona la gestión de memoria en Java (heap, stack, GC)?

  • Stack: almacena variables locales y referencias. Uno por thread. LIFO. Se libera automáticamente al salir del método.
  • Heap: donde viven los objetos. Compartido por todos los threads. Gestionado por el Garbage Collector.
  • Metaspace (Java 8+): reemplaza PermGen. Almacena metadata de clases.

El GC usa generational collection: la mayoría de los objetos mueren jóvenes. Hay un Young Generation (Eden + Survivor) y un Old Generation. G1GC es el default desde Java 9.


32. ¿Qué es el problema de memory leak en Java y cómo lo detectás?

Java no es inmune a memory leaks. Ocurren cuando objetos que ya no se necesitan siguen teniendo referencias (el GC no los puede recolectar).

Causas comunes:

  • Static collections que crecen sin límite
  • Listeners/observers que no se desregistran
  • Cachés sin política de evicción
  • ThreadLocal sin llamar remove()

Herramientas: VisualVM, JProfiler, Eclipse MAT, jmap -histo.


33. ¿Qué hace `System.gc()`?

Le "sugiere" al GC que corra, pero la JVM puede ignorarlo. No es una garantía. En producción, nunca lo llamés manualmente — confiá en el GC.


34. ¿Qué es el JIT compiler?

La JVM interpreta bytecode, pero el Just-In-Time compiler identifica hot paths (código muy ejecutado) y los compila a código nativo para mayor velocidad. Tiered compilation: primero interpreta, luego compila con optimizaciones crecientes (C1 → C2).


7. Generics y tipos

35. Explicá `<? extends T>` vs `<? super T>` (PECS)

PECS: Producer Extends, Consumer Super.

  • : podés leer T (el wildcard produce T). No podés escribir (no sabés el tipo exacto).
  • : podés escribir T (el wildcard consume T). Leer devuelve Object.
java
// Leer de una colección — extends
public double suma(List<? extends Number> lista) {
    return lista.stream().mapToDouble(Number::doubleValue).sum();
}
suma(List.of(1, 2, 3));       // List<Integer> — ok
suma(List.of(1.5, 2.5));      // List<Double>  — ok

// Escribir en una colección — super
public void agregar(List<? super Integer> lista) {
    lista.add(1); lista.add(2);
}
agregar(new ArrayList<Number>());  // ok
agregar(new ArrayList<Object>());  // ok

36. ¿Qué es type erasure?

Los generics existen solo en tiempo de compilación. En runtime, List y List son ambos simplemente List. El compilador inserta casts automáticos. Por eso no podés hacer new T[] o instanceof List.

java
List<String> strings = new ArrayList<>();
List<Integer> ints    = new ArrayList<>();
System.out.println(strings.getClass() == ints.getClass()); // true — ambos son ArrayList

8. Design Patterns

37. ¿Qué es el patrón Observer?

Permite que múltiples objetos (observers) se suscriban a eventos de un sujeto y sean notificados automáticamente.

java
interface Observer { void actualizar(String evento); }

class EventBus {
    private List<Observer> subs = new ArrayList<>();

    void suscribir(Observer o) { subs.add(o); }
    void publicar(String evento) { subs.forEach(o -> o.actualizar(evento)); }
}

EventBus bus = new EventBus();
bus.suscribir(e -> System.out.println("Logger: " + e));
bus.suscribir(e -> System.out.println("Mailer: " + e));
bus.publicar("usuario.creado"); // ambos reciben el evento

38. ¿Qué es el patrón Strategy?

Permite cambiar el algoritmo de un objeto en runtime encapsulando cada algoritmo en su propia clase.

java
interface EstrategiaPago { void pagar(double monto); }

class PagoTarjeta implements EstrategiaPago {
    public void pagar(double m) { System.out.println("Pagando $" + m + " con tarjeta"); }
}
class PagoTransferencia implements EstrategiaPago {
    public void pagar(double m) { System.out.println("Transfiriendo $" + m); }
}

class Checkout {
    private EstrategiaPago estrategia;
    Checkout(EstrategiaPago e) { estrategia = e; }
    void procesar(double monto) { estrategia.pagar(monto); }
}

new Checkout(new PagoTarjeta()).procesar(99.99);
new Checkout(new PagoTransferencia()).procesar(200.0);

9. Java moderno (Records, Sealed, Pattern Matching)

39. ¿Qué son los `record` en Java 16+?

Los records son clases inmutables de datos. El compilador genera automáticamente constructor, getters, equals, hashCode y toString.

java
record Punto(double x, double y) {
    // Compact constructor para validación
    Punto {
        if (x < 0 || y < 0) throw new IllegalArgumentException("coordenadas negativas");
    }

    double distanciaAlOrigen() {
        return Math.sqrt(x * x + y * y);
    }
}

Punto p = new Punto(3, 4);
System.out.println(p.x());              // 3.0
System.out.println(p.distanciaAlOrigen()); // 5.0
System.out.println(p); // Punto[x=3.0, y=4.0]

40. ¿Qué es pattern matching con `instanceof` (Java 16+)?

Elimina el cast explícito después de un instanceof.

java
// Antes
Object obj = "hola";
if (obj instanceof String) {
    String s = (String) obj; // cast manual
    System.out.println(s.length());
}

// Con pattern matching
if (obj instanceof String s) {
    System.out.println(s.length()); // s ya es String aquí
}

// Con switch (Java 21+)
String descripcion = switch (obj) {
    case Integer i -> "entero: " + i;
    case String s  -> "cadena de largo " + s.length();
    case null      -> "nulo";
    default        -> "otro tipo";
};

41. ¿Qué son las `sealed classes`?

Permiten controlar qué clases pueden extender/implementar una jerarquía. Útil para modelar dominios cerrados como ASTs o resultados de operaciones.

java
sealed interface Resultado<T> permits Exito, Falla {}
record Exito<T>(T valor) implements Resultado<T> {}
record Falla<T>(String error) implements Resultado<T> {}

Resultado<Integer> resultado = new Exito<>(42);

String msg = switch (resultado) {
    case Exito<Integer> e -> "ok: " + e.valor();
    case Falla<Integer> f -> "error: " + f.error();
};

10. Preguntas de sistema y diseño

42. ¿Cómo implementarías una caché simple en Java?

java
import java.util.LinkedHashMap;
import java.util.Map;

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacidad;

    LRUCache(int capacidad) {
        super(capacidad, 0.75f, true); // accessOrder=true
        this.capacidad = capacidad;
    }

    @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacidad;
    }
}

LRUCache<String, String> cache = new LRUCache<>(3);
cache.put("a", "1"); cache.put("b", "2"); cache.put("c", "3");
cache.get("a"); // accede a "a" → se vuelve el más reciente
cache.put("d", "4"); // expulsa "b" (el menos reciente)
System.out.println(cache.containsKey("b")); // false

43. ¿Cómo diseñarías un sistema de rate limiting en Java?

java
class RateLimiter {
    private final int maxRequests;
    private final long ventanaMs;
    private final Map<String, Deque<Long>> historial = new ConcurrentHashMap<>();

    RateLimiter(int max, long ventanaMs) {
        maxRequests = max; this.ventanaMs = ventanaMs;
    }

    synchronized boolean permitir(String userId) {
        long ahora = System.currentTimeMillis();
        historial.computeIfAbsent(userId, k -> new ArrayDeque<>());
        Deque<Long> timestamps = historial.get(userId);

        // Eliminar timestamps fuera de la ventana
        while (!timestamps.isEmpty() && ahora - timestamps.peekFirst() > ventanaMs) {
            timestamps.pollFirst();
        }

        if (timestamps.size() < maxRequests) {
            timestamps.addLast(ahora);
            return true;
        }
        return false;
    }
}

44. ¿Cómo implementás un productor-consumidor con `BlockingQueue`?

java
BlockingQueue<String> cola = new LinkedBlockingQueue<>(10);

// Productor
Thread productor = new Thread(() -> {
    try {
        for (int i = 0; i < 20; i++) {
            cola.put("tarea-" + i); // bloquea si la cola está llena
            System.out.println("Producido: tarea-" + i);
        }
    } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});

// Consumidor
Thread consumidor = new Thread(() -> {
    try {
        while (true) {
            String tarea = cola.take(); // bloquea si la cola está vacía
            System.out.println("Consumido: " + tarea);
        }
    } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
});

productor.start(); consumidor.start();

45. ¿Qué es la inmutabilidad y por qué importa en código concurrente?

Un objeto inmutable no puede cambiar de estado después de crearse. En concurrencia, es intrínsecamente thread-safe sin necesidad de sincronización.

java
final class Dinero {
    private final double monto;
    private final String moneda;

    Dinero(double monto, String moneda) {
        this.monto = monto; this.moneda = moneda;
    }

    Dinero sumar(Dinero otro) {
        if (!moneda.equals(otro.moneda)) throw new IllegalArgumentException();
        return new Dinero(monto + otro.monto, moneda); // nuevo objeto, no muta
    }

    @Override public String toString() { return monto + " " + moneda; }
}

46. ¿Qué es la diferencia entre `Serializable` y `Externalizable`?

  • Serializable: el mecanismo por default de Java serializa todos los campos no-transient. Simple pero lento y sin control.
  • Externalizable: implementás writeExternal() y readExternal() vos mismo. Control total y mejor rendimiento.

47. ¿Cómo funciona la reflexión en Java y cuáles son sus riesgos?

La reflexión permite inspeccionar y modificar el comportamiento de clases, métodos y campos en runtime.

java
Class<?> clazz = Class.forName("java.util.ArrayList");
Method metodo = clazz.getMethod("size");
Object lista = clazz.getDeclaredConstructor().newInstance();
System.out.println(metodo.invoke(lista)); // 0

// Acceder a campo privado
Field campo = String.class.getDeclaredField("value");
campo.setAccessible(true); // ⚠ bypasea encapsulamiento

Riesgos: rompe encapsulamiento, dificulta refactoring, genera overhead de rendimiento, puede fallar con módulos en Java 9+ (module system).


48. ¿Qué es la diferencia entre `String`, `StringBuilder` y `StringBuffer`?

  • String: inmutable. Cada concatenación crea un nuevo objeto.
  • StringBuilder: mutable, no thread-safe. Usalo para concatenaciones en un solo thread.
  • StringBuffer: mutable, thread-safe (sincronizado). Más lento que StringBuilder. Hoy raramente necesario.
java
// Ineficiente en loops
String resultado = "";
for (int i = 0; i < 10000; i++) resultado += i; // crea 10000 objetos

// Eficiente
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.append(i);
String resultado2 = sb.toString(); // un solo objeto final

49. ¿Qué son los módulos en Java 9+ (JPMS)?

El Java Platform Module System permite declarar explícitamente qué paquetes expone un módulo y de qué módulos depende.

java
// module-info.java
module com.miapp.servicio {
    requires com.miapp.dominio;        // dependencia
    exports com.miapp.servicio.api;    // expone este paquete
    opens com.miapp.servicio.internal; // abre para reflexión (Spring, etc.)
}

Beneficios: encapsulamiento a nivel de módulo, JVMs más pequeñas con jlink, mejor gestión de classpath.


50. ¿Cómo depurás un problema de alto uso de CPU en producción?

  1. 1Identificar el proceso Java con alto CPU: top o ps aux
  2. 2Obtener el PID y listar threads: jstack > thread-dump.txt
  3. 3Ver qué threads están consumiendo CPU: top -H -p (muestra threads)
  4. 4Convertir el TID de decimal a hex: printf '%x\n'
  5. 5Buscar ese hex en el thread dump para identificar el stack trace problemático

Causas comunes: loops infinitos, GC en modo "stop the world" continuo (heap lleno), deadlock que genera spin-wait, deserialización masiva.

bash
# Script rápido de diagnóstico
jstack $(pgrep -f MiApp) | grep -A 20 "RUNNABLE" | head -60

Tips finales antes de la entrevista

Estas 50 preguntas cubren el 90% de lo que cae en entrevistas Java de nivel junior a senior. Pero el conocimiento solo no alcanza: practicá explicarlas en voz alta, como si le estuvieras enseñando a alguien. Los entrevistadores evalúan claridad de comunicación tanto como profundidad técnica.

Temas que complementan esta guía: Spring Boot (IoC, AOP, auto-configuration), Hibernate/JPA (lazy loading, N+1), testing con JUnit 5 y Mockito, y system design orientado a microservicios con Java.

FAQ

¿Qué preguntas de Java caen más en entrevistas técnicas?+

Las más frecuentes son: diferencias entre == y .equals(), cómo funciona HashMap internamente, qué es el contrato hashCode/equals, diferencias entre clase abstracta e interfaz, cómo funciona synchronized y volatile, y preguntas sobre el Stream API de Java 8. En niveles senior se suman concurrencia avanzada, GC y JVM tuning.

¿Cuál es la diferencia entre HashMap y ConcurrentHashMap?+

HashMap no es thread-safe — accesos concurrentes pueden corromperlo. ConcurrentHashMap usa segmentación interna: lockea solo el segmento afectado en cada operación, lo que permite alta concurrencia sin bloquear el mapa entero. Para uso en un solo thread, HashMap es más rápido; en entornos multi-thread, siempre usá ConcurrentHashMap.

¿Cómo funciona el Garbage Collector en Java?+

La JVM divide el heap en generaciones: Young Generation (Eden + Survivor spaces) y Old Generation. La mayoría de los objetos mueren jóvenes (hipótesis generacional), así que el GC menor (Minor GC) es frecuente y rápido. El GC mayor (Major/Full GC) limpia el Old Generation y es más costoso. Desde Java 9, G1GC es el colector por default.

¿Cuál es la diferencia entre interface y clase abstracta en Java?+

Una clase abstracta puede tener estado (campos), constructores y métodos concretos; solo permite herencia simple. Una interfaz define un contrato, permite herencia múltiple (implements), y desde Java 8 puede tener métodos default y static. Usá clase abstracta cuando compartís código y estado; interfaz cuando definís un comportamiento que múltiples clases no relacionadas deben cumplir.

¿Qué es autoboxing en Java y cuándo causa problemas de rendimiento?+

Autoboxing es la conversión automática entre tipos primitivos (int, long) y sus wrappers (Integer, Long). Causa problemas de rendimiento en loops intensivos que operan sobre colecciones de wrappers: cada iteración crea o destruye objetos, presionando el GC. Para cómputo numérico intensivo usá arrays primitivos o streams primitivos (IntStream, LongStream) en lugar de Stream<Integer>.

¿Qué son los records en Java y cuándo conviene usarlos?+

Los records (Java 16+) son clases inmutables de datos donde el compilador genera automáticamente constructor, getters, equals, hashCode y toString. Convienen para DTOs, value objects, resultados de queries y cualquier clase cuya única responsabilidad sea transportar datos. No reemplazan a las clases normales cuando necesitás mutabilidad, herencia o lógica de negocio compleja.

Related articles

Cómo usar el método STAR en entrevistas (con ejemplos reales)

Aprende a responder preguntas difíciles usando el método STAR en entrevistas. Consejos y ejemplos concretos para roles remotos tech de LATAM.

Cómo conseguir trabajo remoto en dólares desde LATAM: guía real

Descubre consejos concretos para conseguir trabajo remoto en dólares desde LATAM: estrategias de búsqueda, preparación y entrevista para roles tecnológicos.

Las mejores preguntas para hacerle al entrevistador al final

Descubre las mejores preguntas para hacerle al entrevistador al final, útiles para entrevistas tech remotas, diferenciándote y logrando roles en dólares.

Cómo preparar entrevistas de desarrollo sin experiencia previa

Consejos prácticos para enfrentar entrevistas de tu primer trabajo como desarrollador, incluso sin experiencia. Técnicas para destacar y convencer en cada etapa.

Prepare for your real interview

Paste your job link: we research who's interviewing you and rehearse you live.

Start free →

Have an interview coming up? Install the live copilot →

InterviewHack.ai

Prepare for the exact interview: who's interviewing you, a tailored CV, and a real coach.

Product

JobsFree ATS checkerInterview-English checkSalary checkLATAM salary reportFree coursesBlogTailored CVSpoken practiceIt's free

Remote jobs

ReactPythonFull-StackLATAMArgentinaMexicoSee all →

Prepare

Spoken practiceFrontendBackendAI EngineerBy companySell with your CV

Company

For employersAboutContactPrivacyTerms

© 2026 InterviewHack.ai · Your CV is yours. Never used to train anything. · A product of IA-PTY