Java Core Junior a Mid-Level
Este simulacro reúne 40 preguntas de Java Core con su respuesta correcta y el análisis de cada una. El objetivo es practicar con preguntas típicas de una evaluación técnica de nivel junior a mid-level, no memorizar de memoria.
Los bloques cubren: OOP, herencia y polimorfismo; String, wrappers y tipos; Collections; excepciones; generics; lambda y Streams; y concurrencia. Al final hay una guía de lectura de resultados y la lista de temas que abarca.
Bloque 1: OOP, herencia y polimorfismo
1. Sobrecarga: qué método se ejecuta
class Demo {
static void print(int x) {
System.out.println("int");
}
static void print(Integer x) {
System.out.println("Integer");
}
public static void main(String[] args) {
print(10);
}
}
Pregunta: ¿Qué imprime?
Respuesta correcta: int
Análisis: El literal 10 tiene tipo primitivo int. En la resolución de sobrecarga, Java prefiere una coincidencia directa con int antes que realizar boxing hacia Integer.
2. Overriding y polimorfismo
class Animal {
void sonido() {
System.out.println("Animal");
}
}
class Perro extends Animal {
@Override
void sonido() {
System.out.println("Perro");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Perro();
a.sonido();
}
}
Pregunta: ¿Qué imprime?
Respuesta correcta: Perro
Análisis: La referencia es de tipo Animal, pero el objeto real es Perro. Los métodos de instancia sobrescritos se resuelven dinámicamente según el tipo real del objeto.
3. Campos y herencia
class Padre {
int x = 1;
}
class Hijo extends Padre {
int x = 2;
}
public class Test {
public static void main(String[] args) {
Padre p = new Hijo();
System.out.println(p.x);
}
}
Pregunta: ¿Qué imprime?
Respuesta correcta: 1
Análisis: Los campos no utilizan despacho dinámico como los métodos sobrescritos. El acceso a p.x se determina por el tipo de referencia, que es Padre.
4. Orden de constructores
class Padre {
Padre() {
System.out.println("Padre");
}
}
class Hijo extends Padre {
Hijo() {
System.out.println("Hijo");
}
}
public class Test {
public static void main(String[] args) {
new Hijo();
}
}
Pregunta: ¿Qué imprime y en qué orden?
Respuesta correcta:
Padre
Hijo
Análisis: Antes de ejecutar el cuerpo del constructor de Hijo, se ejecuta el constructor de la superclase. Si no se escribe explícitamente super(), el compilador intenta insertar la llamada al constructor sin argumentos de la superclase.
5. Métodos static y ocultamiento
class A {
static void test() {
System.out.println("A");
}
}
class B extends A {
static void test() {
System.out.println("B");
}
}
public class Test {
public static void main(String[] args) {
A obj = new B();
obj.test();
}
}
Pregunta: ¿Qué imprime?
Respuesta correcta: A
Análisis: Los métodos static no son sobrescritos mediante polimorfismo de instancia. Se ocultan (method hiding) y la llamada queda determinada por el tipo de referencia: A.
6. final en referencias
final List<String> nombres = new ArrayList<>();
nombres.add("Ana");
Pregunta: ¿El código anterior es válido? ¿Qué impide realmente final?
Respuesta correcta: Es válido. final impide reasignar la referencia, pero no hace inmutable al objeto apuntado.
Por ejemplo, esto es válido:
nombres.add("Luis");
Pero esto no:
nombres = new ArrayList<>();
Análisis: final aplicado a una variable de referencia fija qué objeto puede referenciar esa variable; no convierte automáticamente el objeto en inmutable.
7. equals y hashCode
Pregunta: Una clase sobrescribe equals() pero no sobrescribe hashCode(). ¿Cuál es el problema principal al utilizar objetos de esa clase como claves de HashMap o elementos de HashSet?
Respuesta correcta: Puede romperse el comportamiento esperado de las colecciones basadas en hashing, porque dos objetos considerados iguales por equals() deben producir el mismo hashCode().
Análisis: Contrato fundamental: si a.equals(b) es true, entonces a.hashCode() debe ser igual a b.hashCode(). La relación inversa no es obligatoria: dos objetos pueden tener el mismo hash y no ser iguales.
8. Default methods en interfaces
interface A {
default void mostrar() {
System.out.println("A");
}
}
interface B {
default void mostrar() {
System.out.println("B");
}
}
class C implements A, B {
}
Pregunta: ¿Compila la clase C tal como está?
Respuesta correcta: No compila.
Análisis: C hereda dos implementaciones default para el mismo método. La clase debe resolver explícitamente el conflicto sobrescribiendo mostrar(), por ejemplo:
@Override
public void mostrar() {
A.super.mostrar();
}
Bloque 2: String, wrappers y tipos
9. String pool y ==
String a = "Java";
String b = "Java";
System.out.println(a == b);
System.out.println(a.equals(b));
Pregunta: ¿Qué imprime?
Respuesta correcta:
true
true
Análisis: Los literales de String pueden reutilizar la misma instancia del pool de Strings. == compara referencias; equals() compara contenido.
10. String con new
String a = new String("Java");
String b = new String("Java");
System.out.println(a == b);
System.out.println(a.equals(b));
Pregunta: ¿Qué imprime?
Respuesta correcta:
false
true
Análisis: Cada new String crea una nueva instancia. Las referencias son distintas, pero equals() devuelve true porque el contenido coincide.
11. Integer y boxing
Integer a = 127;
Integer b = 127;
System.out.println(a == b);
Pregunta: ¿Qué imprime?
Respuesta correcta: true
Análisis: El boxing de valores enteros dentro del rango de caché garantizado por la especificación puede reutilizar instancias; para 127, la comparación mediante == resulta true.
Importante: == entre wrappers no debe utilizarse para comparar valores numéricos de forma general. Para eso se usa equals() o se realiza comparación con primitivos.
12. Integer contra int
Integer a = 100;
int b = 100;
System.out.println(a == b);
Pregunta: ¿Qué imprime?
Respuesta correcta: true
Análisis: Como uno de los operandos es primitivo int, Java realiza unboxing del Integer y compara valores int.
13. Integer y null
Integer x = null;
int y = x;
Pregunta: ¿Qué ocurre?
Respuesta correcta: Se lanza NullPointerException durante el unboxing.
Análisis: Java necesita convertir x de Integer a int. Como x contiene null, el unboxing no puede producir un valor primitivo.
14. String no cambia
String s = "Hola";
s.concat(" mundo");
System.out.println(s);
Pregunta: ¿Qué imprime?
Respuesta correcta: Hola
Análisis: String es inmutable. concat() devuelve un nuevo String; no modifica la instancia original.
Bloque 3: Collections
15. ArrayList.remove(): índice vs objeto
List<Integer> lista = new ArrayList<>(Arrays.asList(10, 20, 30));
lista.remove(1);
System.out.println(lista);
Pregunta: ¿Qué imprime?
Respuesta correcta: [10, 30]
Análisis: Con List<Integer>, remove(1) selecciona la sobrecarga remove(int index), por lo que elimina el elemento situado en el índice 1.
Para eliminar el valor 1 como objeto se usaría:
lista.remove(Integer.valueOf(1));
16. Arrays.asList()
List<String> lista = Arrays.asList("A", "B", "C");
lista.set(1, "X");
System.out.println(lista);
Respuesta correcta: [A, X, C]
Análisis: Arrays.asList() produce una lista de tamaño fijo respaldada por un arreglo. Permite reemplazar posiciones con set(), pero no permite cambiar el tamaño.
Por ejemplo, add() o remove() provocan UnsupportedOperationException.
17. HashMap y clave duplicada
Map<String, Integer> edades = new HashMap<>();
edades.put("Ana", 20);
edades.put("Ana", 25);
System.out.println(edades.size());
System.out.println(edades.get("Ana"));
Pregunta: ¿Qué imprime?
Respuesta correcta:
1
25
Análisis: Una clave no puede representar dos valores simultáneamente dentro del mismo Map. La segunda llamada a put() reemplaza el valor asociado con "Ana".
18. HashSet y duplicados
Set<String> set = new HashSet<>();
set.add("Java");
set.add("Java");
set.add("Spring");
System.out.println(set.size());
Pregunta: ¿Qué imprime?
Respuesta correcta: 2
Análisis: Set representa una colección sin elementos duplicados de acuerdo con las reglas de igualdad de la implementación.
19. LinkedHashMap
Map<Integer, String> mapa = new LinkedHashMap<>();
mapa.put(3, "C");
mapa.put(1, "A");
mapa.put(2, "B");
System.out.println(mapa.keySet());
Pregunta: ¿Qué imprime?
Respuesta correcta: [3, 1, 2]
Análisis: LinkedHashMap mantiene un orden de iteración basado por defecto en el orden de inserción.
20. Queue: poll() vs remove()
Queue<String> cola = new ArrayDeque<>();
System.out.println(cola.poll());
Pregunta: ¿Qué imprime?
Respuesta correcta: null
Análisis: poll() devuelve null cuando la cola está vacía.
En cambio, remove() sobre una cola vacía lanza NoSuchElementException.
21. Iterator.remove()
List<Integer> lista = new ArrayList<>(Arrays.asList(1, 2, 3));
Iterator<Integer> it = lista.iterator();
while (it.hasNext()) {
int n = it.next();
if (n == 2) {
it.remove();
}
}
System.out.println(lista);
Pregunta: ¿Qué imprime?
Respuesta correcta: [1, 3]
Análisis: Iterator.remove() elimina correctamente el elemento devuelto por la última llamada a next() y mantiene coherente el estado del iterador.
22. Modificar una ArrayList durante for-each
List<Integer> lista = new ArrayList<>(Arrays.asList(1, 2, 3));
for (Integer n : lista) {
if (n == 2) {
lista.remove(n);
}
}
Pregunta: ¿Qué ocurre?
Respuesta correcta: Se lanza ConcurrentModificationException durante la iteración.
Análisis: El for-each utiliza internamente un Iterator. Modificar directamente la lista mientras ese iterador está recorriendo la estructura puede provocar ConcurrentModificationException.
La forma correcta, en este caso, es utilizar Iterator.remove() o métodos apropiados como removeIf().
Bloque 4: Excepciones
23. Checked exception
Pregunta: Supón que un método llama a una API que declara throws IOException. ¿Qué debe hacer un método que la invoque?
Respuesta correcta: Debe capturar la excepción con try/catch o declarar que también puede lanzarla mediante throws IOException (o una superclase adecuada).
Análisis: IOException es una checked exception. El compilador exige que el código maneje o propague este tipo de excepción.
24. finally y return
static int obtener() {
try {
return 1;
} finally {
return 2;
}
}
Pregunta: ¿Qué devuelve obtener()?
Respuesta correcta: 2
Análisis: finally se ejecuta antes de abandonar el método. Un return dentro de finally reemplaza el valor que iba a retornar el try.
Es una práctica que debe evitarse porque puede ocultar retornos y excepciones.
25. finally con excepción
try {
int x = 10 / 0;
} finally {
System.out.println("finally");
}
Pregunta: ¿Qué ocurre?
Respuesta correcta: Primero imprime "finally" y después se propaga ArithmeticException.
Análisis: finally se ejecuta aunque el try termine abruptamente por una excepción, salvo situaciones extremas como terminación forzada de la JVM.
26. Orden de catch
try {
// ...
} catch (Exception e) {
// ...
} catch (IOException e) {
// ...
}
Pregunta: ¿Compila?
Respuesta correcta: No compila.
Análisis: IOException es subclase de Exception. El primer catch ya captura IOException, por lo que el segundo catch sería inalcanzable.
Los catch deben colocarse de la excepción más específica a la más general.
27. try-with-resources
Pregunta: ¿Qué ventaja principal ofrece try-with-resources?
Respuesta correcta: Cierra automáticamente los recursos que implementan AutoCloseable al terminar el bloque, incluso cuando ocurre una excepción.
Análisis: Evita gran parte del código manual de cierre y reduce el riesgo de fugas de recursos.
28. throw vs throws
Pregunta: ¿Cuál es la diferencia entre throw y throws?
Respuesta correcta: throw se utiliza para lanzar una excepción concreta en un punto de ejecución. throws se utiliza en la firma de un método para declarar que puede propagar determinadas excepciones.
Ejemplo de throw:
throw new IllegalArgumentException("Dato inválido");
Ejemplo de throws:
void leer() throws IOException {
// ...
}
Análisis: throw es una instrucción. throws forma parte de la declaración del método.
Bloque 5: Generics
29. Invarianza de generics
List<Integer> enteros = new ArrayList<>();
List<Number> numeros = enteros;
Pregunta: ¿Compila?
Respuesta correcta: No compila.
Análisis: Aunque Integer es subtipo de Number, List<Integer> NO es subtipo de List<Number>.
Los tipos genéricos son invariantes. Si se permitiese esa asignación, podrías introducir un Double en una lista que realmente es de Integer.
30. Wildcard extends
List<Integer> enteros = Arrays.asList(1, 2, 3);
List<? extends Number> numeros = enteros;
Number n = numeros.get(0);
Pregunta: ¿Es válido?
Respuesta correcta: Sí.
Análisis: Con ? extends Number sabemos que cada elemento es algún subtipo de Number, por lo que leerlo como Number es seguro.
No podemos agregar arbitrariamente Integer, Double u otros Number porque el tipo exacto desconocido podría ser diferente.
31. Wildcard super
List<? super Integer> lista = new ArrayList<Number>();
lista.add(10);
Pregunta: ¿Es válido?
Respuesta correcta: Sí.
Análisis: ? super Integer significa que el tipo concreto es Integer o alguno de sus supertipos. Un Integer puede almacenarse de forma segura.
Al leer, el tipo seguro conocido es Object.
32. Raw types
List lista = new ArrayList();
lista.add("Java");
Integer x = (Integer) lista.get(0);
Pregunta: ¿Qué ocurre?
Respuesta correcta: Se lanza ClassCastException en tiempo de ejecución.
Análisis: La colección raw perdió seguridad de tipos. En tiempo de ejecución el objeto recuperado es String, y no puede convertirse a Integer.
33. Type erasure
Pregunta: En Java, ¿en tiempo de ejecución se distinguen como clases diferentes ArrayList<String> y ArrayList<Integer>?
Respuesta correcta: No. Ambas corresponden a la misma clase de ejecución: ArrayList.
Análisis: Java implementa generics mediante type erasure. La información específica del parámetro genérico no permanece de la misma manera en el tipo runtime.
Por eso tampoco puedes escribir directamente:
if (obj instanceof List<String>) { ... }
porque el parámetro String no está disponible para esa comprobación runtime.
34. Método genérico
static <T> T primero(List<T> lista) {
return lista.get(0);
}
List<String> datos = Arrays.asList("A", "B");
String resultado = primero(datos);
Pregunta: ¿Qué valor queda en resultado?
Respuesta correcta: "A"
Análisis: El compilador infiere T como String a partir del argumento List<String>. Por ello el resultado se trata como String sin cast explícito.
Bloque 6: Lambda y Streams
35. Lazy evaluation en Streams
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
Stream<Integer> stream = nums.stream()
.filter(n -> {
System.out.println("filter " + n);
return n % 2 == 0;
});
System.out.println("antes");
long cantidad = stream.count();
System.out.println(cantidad);
Pregunta: ¿Qué ocurre con los mensajes de filter?
Respuesta correcta: No se ejecuta ningún filter al crear el Stream. Primero se imprime "antes". Después, al ejecutar count(), se procesan los elementos y se imprimen los mensajes del filter. Finalmente se imprime 2.
Análisis: Las operaciones intermedias de Stream son lazy. Necesitan una operación terminal como count(), collect(), reduce(), forEach(), etc.
36. Un stream no se recicla
Stream<String> s = Stream.of("A", "B", "C");
long n1 = s.count();
long n2 = s.count();
Pregunta: ¿Qué ocurre?
Respuesta correcta: La segunda operación provoca IllegalStateException porque el stream ya fue consumido o cerrado.
Análisis: Un Stream está diseñado para una sola secuencia de procesamiento. Si necesitas volver a procesar los datos, crea otro Stream desde la fuente.
37. filter + map + reduce
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
int resultado = nums.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * 10)
.reduce(0, Integer::sum);
Pregunta: ¿Qué valor produce resultado?
Respuesta correcta: 60
Análisis: Primero filter deja 2 y 4. Después map los convierte en 20 y 40. Por último reduce suma: 20 + 40 = 60.
38. Optional: orElse vs orElseGet
static String fallback() {
System.out.println("fallback ejecutado");
return "por defecto";
}
Optional<String> valor = Optional.of("Java");
String resultado = valor.orElse(fallback());
Pregunta: ¿Se ejecuta fallback()?
Respuesta correcta: Sí.
Análisis: orElse recibe un valor ya evaluado, por lo que fallback() se ejecuta aunque Optional tenga un valor.
Con orElseGet(() -> fallback()) la evaluación del proveedor es lazy y solo ocurre cuando el Optional está vacío.
Bloque 7: Concurrencia
39. count++ no es atómico
class Contador {
private int count = 0;
void incrementar() {
count++;
}
int getCount() {
return count;
}
}
Pregunta: Si varios threads llaman incrementar() simultáneamente miles de veces, ¿puedes asumir que el resultado final será exactamente el número total de incrementos?
Respuesta correcta: No.
Análisis: count++ implica varias acciones conceptuales: leer, sumar y escribir. Dos threads pueden leer el mismo valor y sobrescribirse entre sí.
Para resolver el problema pueden utilizarse mecanismos como synchronized, Lock o AtomicInteger, dependiendo del diseño requerido.
40. volatile vs operación atómica
Pregunta: ¿Declarar count como volatile hace que count++ sea seguro entre múltiples threads?
Respuesta correcta: No.
Análisis: volatile proporciona garantías de visibilidad y ordenamiento relacionadas con la variable, pero no convierte una operación compuesta como count++ en una operación atómica.
Si necesitas incrementar de forma segura concurrentemente, una alternativa habitual es:
AtomicInteger count = new AtomicInteger();
count.incrementAndGet();
o utilizar sincronización apropiada.
Mini guía de evaluación
| Puntuación | Lectura |
|---|---|
| 40/40 | Nivel muy sólido para una evaluación técnica Java Core JR-Mid |
| 35 a 39 | Muy buen dominio. Conviene revisar los errores concretos antes de una entrevista |
| 30 a 34 | Base buena, pero hay temas importantes que todavía pueden aparecer como trampas |
| 25 a 29 | Nivel funcional, pero conviene reforzar Collections, excepciones, generics y concurrencia |
| Menos de 25 | Recomendable repasar fundamentos antes de intentar una evaluación Java Core de dificultad intermedia |
Temas que cubre este simulacro
- OOP, encapsulación conceptual, herencia, overloading, overriding y polimorfismo
- static y method hiding, final, interfaces y default methods
- equals y hashCode
- String pool, inmutabilidad, boxing y unboxing, Integer caching
- ArrayList, Arrays.asList, HashMap, HashSet, LinkedHashMap, Queue, Iterator y ConcurrentModificationException
- Checked exceptions, finally, try-with-resources, throw y throws
- Generics, wildcards, PECS, raw types, type erasure y métodos genéricos
- Streams, lazy evaluation, reduce, Optional, orElse y orElseGet
- Multithreading, atomicidad, volatile y AtomicInteger
Fuentes consultadas
La selección de temas fue contrastada con documentación oficial de Oracle/Java y bancos de entrevistas que publican preguntas de Core Java y experiencias técnicas.
- Oracle, Java SE y Core Libraries: https://docs.oracle.com/en/java/javase/26/core/index.html
- Oracle, Java Collections Framework: https://docs.oracle.com/javase/tutorial/collections/
- Oracle, Interfaces e Inheritance: https://docs.oracle.com/javase/tutorial/java/IandI/
- Oracle, Generics: https://docs.oracle.com/javase/tutorial/java/generics/
- Oracle, Exceptions: https://docs.oracle.com/javase/tutorial/essential/exceptions/
- Oracle, java.lang y wrappers: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/package-summary.html
- Baeldung, Java Interview Questions: https://www.baeldung.com/java-interview-questions
- Baeldung, Java Collections Interview Questions: https://www.baeldung.com/java-collections-interview-questions
- Baeldung, Java Generics Interview Questions: https://www.baeldung.com/java-generics-interview-questions
- Baeldung, Java Concurrency Interview Questions: https://www.baeldung.com/java-concurrency-interview-questions
- InterviewBit, Core Java Interview Questions: https://www.interviewbit.com/java-interview-questions/
- GeeksforGeeks, Java Interview Questions: https://www.geeksforgeeks.org/java/commonly-asked-java-programming-interview-questions-set-1/
Las 40 preguntas y explicaciones de este simulacro fueron redactadas de forma original para practicar; no son una copia literal de las páginas consultadas.
© Arturo Juárez Monroy