- Tipos de datos primitivos en Java
Java define ocho tipos de datos primitivos. Estos son: boolean para valores lógicos (true/false), byte (8 bits), short (16 bits), int (32 bits) y long (64 bits) para distintos rangos de enteros. Para números con decimales, existen float (32 bits, precisión simple) y double (64 bits, precisión doble). Finalmente, char (16 bits) almacena un solo carácter del conjunto Unicode.
- Propósito de los tipos envoltorio (Wrapper)
Los tipos envoltorio como Integer o Double existen por varias razones. Permiten representar valores nulos (null), algo imposible con los primitivos. Ofrecen métodos útiles para conversión y manipulación, como parseInt(). Son necesarios para trabajar con genéricos (generics), que solo operan con objetos. Java proporciona el autoboxing/unboxing para convertir automáticamente entre primitivos y sus wrappers. Finalmente, son esenciales para usar colecciones (ej. ArrayList), que solo pueden almacenar objetos.
- Diferencia entre inicialización de String
La sentencia String literal = "123"; crea una referencia al objeto String en el String Constant Pool, reutilizándolo si ya existe. En cambio, String nuevo = new String("123"); siempre crea un nuevo objeto en el heap, independientemente del pool. Esta última forma puede llegar a crear dos objetos: uno en el pool (si no existía) y otro nuevo en el heap.
- String, StringBuilder y StringBuffer
String es inmutable; cualquier modificación genera un nuevo objeto. StringBuilder es mutable y no sincronizado, ideal para operaciones en un solo hilo (mejor rendimiento). StringBuffer es mutable y sincronizado, seguro para entornos multihilo pero con menor rendimiento debido a la sobrecarga de los bloqueos. La elección depende de la necesidad de mutabilidad y del contexto de concurrencia.
- Paradigmas: Orientado a Objetos vs. Orientado a Procesos
El paradigma orientado a procesos estructura el software como una secuencia de funciones o pasos. Es claro para tareas lineales pero sufre de baja reutilización y mantenimiento difícil. El paradigma orientado a objetos modela el software como objetos que encapsulan estado (atributos) y comportamiento (métodos). Promueve la reutilización mediante la herencia y la extensibilidad mediante el polimorfismo, resultando en código más modular y mantenible.
- Características fundamentales de la POO
Los tres pilares son: Encapsulamiento (ocultar la implementación interna y exponer una interfaz pública), Herencia (reutilizar y extender funcionalidad de una clase padre) y Polimorfismo (capacidad de un objeto de tomar múltiples formas, donde un mismo método puede comportarse de diferente manera según el objeto concreto).
- Naturaleza de Java
Java es un lenguaje de programación orientado a objetos. Aunque soporta primitivos para eficiencia, su diseño y API están fundamentalmente basados en objetos y clases.
- Reflexión (Reflection)
La reflexión permite inspeccionar y manipular clases, métodos y campos en tiempo de ejecución. Es fundamental para crear frameworks genéricos, herramientas de inyección de dependencias, mapeo objeto-relacional (ORM), y para interactuar con código que no se conoce en tiempo de compilación, proporcionando gran flexibilidad.
- Precisión y tipos monetarios
Los números de punto flotante (float, double) no son adecuados para representar dinero debido a errores de redondeo inherentes a su representación binaria (ej. 0.1 no se puede representar exactamente). Esto lleva a pérdidas de precisión acumulativas en cálculos. La solución es usar java.math.BigDecimal, que proporciona aritmética decimal exacta y control sobre el redondeo.
- Almacenar cantidades monetarias como String
Almacenar dinero como String dificulta los cálculos aritméticos, requiere conversiones costosas y propensas a errores, y no aprovecha las operaciones matemáticas optimizadas del sistema. Si bien permite formatos flexibles para visualización, BigDecimal es la opción estándar y robusta para cálculos monetarios.
- Clonación de objetos y copias
Se necesita clonar objetos para crear copias independientes, evitando efectos secundarios por mutaciones. Se implementa con la interfaz Cloneable y el método clone(). Una copia superficial (shallow copy) duplica el objeto pero comparte las referencias a objetos anidados. Una copia profunda (deep copy) duplica el objeto y todos los objetos a los que este referencia recursivamente, garantizando independencia total.
- Ejecución del bloque finally con return
Sí, el bloque finally siempre se ejecuta, incluso si un return se encuentra en el bloque try o catch. La JVM guarda primero el valor a retornar, ejecuta el código en finally y luego devuelve el valor guardado. Esto garantiza la liberación de recursos (ej. cerrar archivos o conexiones).
- Inmutabilidad de la clase String
String es inmutable por razones de seguridad (es usada como parámetro en operaciones de red, seguridad y clases de sistema), eficiencia (permite cacheo en el String Pool), y simplicidad (evita problemas de concurrencia, ya que un objeto inmutable es inherentemente seguro entre hilos).
- Error vs. Exception
Los Error (ej. OutOfMemoryError) representan problemas graves del entorno de ejecución (JVM) que normalmente no deben capturarse. Las Exception representan condiciones que una aplicación puede querer capturar. Las excepciones se dividen en verificadas (Checked: deben manejarse, ej. IOException) y no verificadas (Unchecked: RuntimeException y sus subclases, como NullPointerException).
- Clases abstractas vs. Interfaces
Una clase abstracta puede tener constructores, estado (campos), métodos con implementación y métodos abstractos. Una clase solo puede heredar de una. Una interfaz define un contrato de comportamiento. Tradicionalmente, solo tenía métodos abstractos y constantes (static final). Desde Java 8, puede tener métodos default y static. Una clase puede implementar múltiples interfaces.
- Operador == vs. Método equals()
El operador == compara si dos referencias apuntan al mismo objeto en memoria (identidad). El método equals() se usa para comparar el contenido o estado lógico de dos objetos (equivalencia). Por defecto en Object, equals() se comporta como ==, pero clases como String o Integer lo sobrescriben para comparar valores.
- Uso de super y this
La palabra clave this hace referencia a la instancia actual del objeto, permitiendo acceder a sus campos y métodos. La palabra clave super se usa para acceder a miembros (campos, métodos) de la superclase inmediata, y para invocar el constructor de la superclase.
- Colecciones en Java: Tipos y características
La jerarquía incluye: List (ordenada, elementos repetidos: ArrayList, LinkedList), Set (sin duplicados: HashSet, TreeSet), Queue (FIFO: PriorityQueue, ArrayDeque), y Map (pares clave-valor: HashMap, TreeMap, ConcurrentHashMap). HashMap no es thread-safe; Hashtable (legacy) sí lo es pero es menos eficiente; ConcurrentHashMap es thread-safe con alta concurrencia.
- Ordenación en colecciones
Las listas pueden ordenarse de varias formas. El método Collections.sort() ordena una lista pasada como argumento. El método de instancia List.sort() ordena la propia lista (in-place). El stream list.stream().sorted() produce un nuevo stream ordenado sin modificar la lista original. Los dos primeros suelen ser más eficientes ya que operan directamente sobre la estructura.
- ArrayList vs. LinkedList vs. Vector
ArrayList (array dinámico) es óptimo para acceso aleatorio. LinkedList (lista doblemente enlazada) es eficiente para inserción/eliminación frecuente en medio de la lista. Vector es una versión sincronizada (thread-safe) de ArrayList, pero con rendimiento inferior y considerado legacy. ArrayList y LinkedList no son thread-safe.
- HashMap vs. Hashtable vs. ConcurrentHashMap
HashMap no es thread-safe y permite claves/valores null. Hashtable es thread-safe (métodos sincronizados) pero ineficiente y no permite null. ConcurrentHashMap es thread-safe usando bloqueos de segmentación (Java 7) o CAS + sincronización de nodos (Java 8+), ofreciendo alto rendimiento concurrente sin bloquear toda la tabla.
- Mecanismo interno de HashMap
HashMap almacena pares clave-valor en un array de nodos (Node[]). Calcula un hash de la clave para determinar su posición en el array. Si hay colisiones (mismo hash), los nodos se enlazan en una lista (o en un árbol rojo-negro si la lista supera un umbral). Al put, inserta o actualiza; al get, busca por hash y recorre la estructura colisionada. Se redimensiona (duplica el array) cuando el número de elementos excede la capacidad * factor de carga.
- Thread-safety de HashMap, HashSet y ArrayList
Ninguna de las tres implementaciones (HashMap, HashSet, ArrayList) es thread-safe por defecto. Su uso concurrente requiere sincronización externa o el uso de alternativas sincronizadas como Collections.synchronizedMap() o ConcurrentHashMap.
- Creación de hilos en Java
Existen tres formas principales: 1) Extender la clase Thread y sobrescribir run(). 2) Implementar la interfaz Runnable (preferible, ya que permite heredar de otra clase). 3) Usar el framework de ExecutorService y pools de hilos (ThreadPoolExecutor), que es la aproximación recomendada para aplicaciones de producción.
- Mecanismos de sincronización
Para controlar el acceso concurrente a recursos compartidos se pueden usar: el bloque synchronized (intrínseco, basado en monitores), ReentrantLock (más flexible), la palabra clave volatile (garantiza visibilidad pero no atomicidad compuesta), y las clases atómicas del paquete java.util.concurrent.atomic (ej. AtomicInteger), que usan CAS.
- Synchronized en métodos vs. bloques estáticos
Cuando se aplica a un método de instancia, el monitor (bloqueo) es la propia instancia del objeto (this). Cuando se aplica a un método estático, el monitor es el objeto Class de la clase. Esto significa que los hilos contendrán por la misma instancia o por la misma clase, respectivamente.
public class EjemploSincronizado { // Bloqueo sobre la instancia (this) public synchronized void metodoSincronizado() { // Operaciones sincronizadas }
// Bloqueo sobre el objeto Class de EjemploSincronizado
public static synchronized void metodoEstaticoSincronizado() {
// Operaciones sincronizadas
}
}
</div>27. Clasificación de bloqueos a nivel de JVM
--------------------------------------------
Los bloqueos se clasifican en: **Bloqueos intrínsecos** (monitores, `synchronized`) y **Bloqueos explícitos** (`ReentrantLock`, `ReentrantReadWriteLock`). También pueden clasificarse por comportamiento: **Bloqueos justos vs. injustos** (orden de adquisición), **Reentrantes** (un hilo puede adquirir el mismo bloqueo varias veces), y **Lectura/Escritura** (permiten múltiples lectores concurrentes, pero un escritor exclusivo).
28. Serialización de objetos
----------------------------
La serialización convierte el estado de un objeto en una secuencia de bytes. Es necesaria para transmitir objetos a través de la red, guardarlos en bases de datos o archivos, o para la replicación de estado en sistemas distribuidos. Las clases deben implementar `Serializable`. La deserialización reconstruye el objeto a partir de los bytes.
29. Modelos de I/O: BIO, NIO y AIO
----------------------------------
**BIO (Blocking I/O)**: Modelo síncrono y bloqueante. Un hilo por conexión, útil para pocas conexiones. **NIO (Non-blocking I/O)**: Introduce `Channels`, `Buffers` y `Selectors`. Permite que un hilo gestione múltiples conexiones de forma síncrona no bloqueante, ideal para servidores con muchas conexiones. **AIO (Asynchronous I/O)**: Introduce I/O verdaderamente asíncrona con callbacks. Las operaciones no bloquean y el sistema operativo notifica su completación.
30. Volatile y atomicidad
-------------------------
La palabra clave `volatile` garantiza la **visibilidad** (los cambios de una variable son inmediatamente visibles a otros hilos) y evita ciertas **reordenaciones de instrucciones por el compilador/CPU. Sin embargo, **no garantiza atomicidad** para operaciones compuestas como `i++`. Para ello se necesitan `synchronized` o clases atómicas.**
31. Herramientas del paquete java.util.concurrent
-------------------------------------------------
Este paquete incluye: **Bloqueos** (`ReentrantLock`, `ReadWriteLock`), **Sincronizadores** (`CountDownLatch`, `CyclicBarrier`, `Semaphore`), **Colecciones concurrentes** (`ConcurrentHashMap`, `CopyOnWriteArrayList`), **Ejecutores** (`ExecutorService`, `ThreadPoolExecutor`), y **Clases atómicas** (`AtomicInteger`, `AtomicReference`). Estas herramientas proporcionan constructores robustos y de alto rendimiento para programación concurrente.
32. SimpleDateFormat y concurrencia
-----------------------------------
`SimpleDateFormat` no es thread-safe porque su uso modifica su estado interno. Soluciones: 1) Usar una instancia por hilo (puede consumir memoria). 2) Sincronizar el acceso (cuello de botella). 3) Usar `ThreadLocal`. 4) Usar la API `java.time.format.DateTimeFormatter` de Java 8+, que es inmutable y thread-safe.
33. AQS y CAS
-------------
**AQS (AbstractQueuedSynchronizer)** es un framwork para construir bloqueos y sincronizadores (`ReentrantLock`, `Semaphore`). Maneja el estado de sincronización y una cola de hilos en espera. **CAS (Compare-And-Swap)** es una operación atómica a nivel de hardware usada por las clases atómicas (`Atomic*`) y `ConcurrentHashMap` (Java 8+). Comparan el valor actual de una memoria con un valor esperado y, si coinciden, lo actualizan atómicamente, evitando bloqueos tradicionales.
34. Resolución de colisiones en HashMap
---------------------------------------
La solución principal es el **encadenamiento separado**. Cada "bucket" del array subyacente puede contener una lista enlazada de nodos que colisionan. A partir de Java 8, si una lista enlazada supera un umbral (TREEIFY\_THRESHOLD), se convierte en un árbol rojo-negro (`TreeNode`) para mejorar el rendimiento de búsqueda de O(n) a O(log n).
35. Parámetros de un pool de hilos
----------------------------------
Al configurar un `ThreadPoolExecutor`, se deben considerar: `corePoolSize` (hilos que se mantienen vivos), `maximumPoolSize`, `workQueue` (cola para tareas esperando), `keepAliveTime` (tiempo de vida de hilos excedentes), `threadFactory` y `handler` (política de rechazo). La configuración óptima depende de si las tareas son I/O-bound (más hilos) o CPU-bound (hilos ≈ núcleos de CPU), y se ajusta mediante pruebas de carga y monitoreo.
36. Colas de espera en pools de hilos
-------------------------------------
Las implementaciones comunes son: `ArrayBlockingQueue` (limitada, basada en array), `LinkedBlockingQueue` (opcionalmente limitada, basada en lista), `SynchronousQueue` (sin capacidad, entrega directa de productor a consumidor), `PriorityBlockingQueue` (tareas con prioridad) y `DelayQueue` (tareas que solo pueden tomarse tras un retardo).
37. Tipos de referencias en Java
--------------------------------
Java tiene cuatro tipos de referencias que afectan la recolección de basura: **Fuerte** (normal, impide la recolección), **Blanda** (`SoftReference`, recolectada antes de `OutOfMemoryError`, útil para cachés), **Débil** (`WeakReference`, recolectada en el siguiente ciclo GC, útil para `WeakHashMap`) y **Fantasma** (`PhantomReference`, para acciones post-mortem del recolector).
38. CAS como bloqueo optimista
------------------------------
CAS se considera un mecanismo de **bloqueo optimista** porque asume que es improbable que otro hilo modifique el dato entre la lectura y la actualización. En lugar de bloquear, intenta la actualización; si falla (otro hilo cambió el valor), reintenta. Esto evita el overhead de los bloqueos tradicionales. En Java, la clase `Unsafe` proporciona acceso a operaciones CAS nativas del hardware.
39. ThreadLocal: Principio y consideraciones
--------------------------------------------
`ThreadLocal` almacena datos por hilo, de manera que cada hilo tiene su propia copia de la variable. Internamente, cada `Thread` tiene un `ThreadLocalMap` donde la clave es el propio `ThreadLocal` y el valor es el dato. Se debe llamar a `remove()` cuando el `ThreadLocal` ya no sea necesario para evitar fugas de memoria, especialmente en entornos con pools de hilos donde los hilos se reutilizan.
40. Modelo de Memoria Java (JMM)
--------------------------------
La JMM define reglas para el acceso a variables compartidas en un entorno multihilo. Sus tres pilares son: **Atomicidad** (las operaciones parecen indivisibles), **Visibilidad** (los cambios de un hilo son visibles a otros, garantizada por `volatile` o `synchronized`) y **Orden** (restricciones en el reordenamiento de instrucciones, definidas por la relación *happens-before*). Resuelve problemas fundamentales de concurrencia.
41. Diseño de sistemas de alta concurrencia
-------------------------------------------
Un sistema de alta concurrencia requiere una arquitectura desacoplada y escalable horizontalmente. Técnicas clave incluyen: uso de **caché** (ej. Redis) para reducir I/O, **procesamiento asíncrono** con colas de mensajes (ej. Kafka), **escalado** de servicios y bases de datos (sharding, réplicas), **balanceo de carga**, y **limitación de tasa** (*rate limiting*). También es crucial el monitoreo, los circuit breakers y la programación orientada a eventos o reactiva.
42. Relación de NIO/AIO con el sistema operativo
------------------------------------------------
Los modelos NIO y AIO de Java son abstracciones que están profundamente influenciadas y apoyadas por las capacidades del sistema operativo subyacente (por ejemplo, `epoll` en Linux, `kqueue` en BSD/macOS). Java encapsula estas llamadas al sistema para ofrecer una API portable. Por lo tanto, aunque la programación es en Java, el rendimiento y el comportamiento dependen de la implementación del SO.
43. Hilos de usuario y demonios
-------------------------------
Los **hilos de usuario** son creados por la aplicación y mantienen la JVM en ejecución hasta que finalizan. Los **hilos demonio** (daemon) son hilos de fondo (ej. recolección de basura) que no impiden que la JVM termine si todos los hilos de usuario finalizan. Se establecen como demonio con `thread.setDaemon(true)` antes de iniciarlos.
44. Estados de un hilo y su interrupción
----------------------------------------
Los estados son: `NEW`, `RUNNABLE` (incluye listo y ejecutando), `BLOCKED` (esperando monitor), `WAITING`, `TIMED_WAITING` y `TERMINATED`. Para interrumpir un hilo, se llama a `thread.interrupt()`, que establece su bandera de interrupción. El hilo debe verificar periódicamente esta bandera (`isInterrupted()`) o manejar `InterruptedException` si está en un método bloqueante como `sleep()` o `wait()`.
45. La naturaleza de los problemas de concurrencia
--------------------------------------------------
Los problemas de concurrencia (condiciones de carrera, datos inconsistentes) surgen por la falta de las tres garantías de la JMM: atomicidad, visibilidad y orden. Un acceso concurrente no sincronizado a datos compartidos viola una o más de estas garantías. La JMM, junto con primitivas como `synchronized`, `volatile` y `java.util.concurrent`, proporciona los medios para establecer estas garantías.
46. CompletableFuture para orquestación de tareas
-------------------------------------------------
`CompletableFuture` (Java 8+) es una herramienta poderosa para componer operaciones asíncronas. Permite encadenar (`thenApply`, `thenCompose`), combinar (`thenCombine`, `allOf`, `anyOf`) y manejar excepciones de tareas no bloqueantes. Facilita la programación paralela y reactiva, simplificando código complejo basado en callbacks o `Future` tradicional.