1. Memoria Contigua: Por qué los Arrays inician en 0
Un array en Java almacena sus casillas pegadas una tras otra en memoria. El índice no es un "número de casilla ordinal", sino un desplazamiento (offset). Por eso acceder a un índice cualquiera toma exactamente el mismo tiempo: O(1).
Al ser índice 0, el desplazamiento es 0 * 4 = 0 bytes. Por eso se lee instantáneamente el inicio del array.
2. Matrices en Java: Arrays de Arrays (Jagged)
En Java no existe una tabla rectangular continua en RAM. Una matriz int[][] es en realidad un array principal que contiene punteros a otros arrays independientes, cada uno con su propia longitud y ubicación en el Heap.
La fila 0 tiene 2 casillas. Podés reasignarla con matriz[0] = new int[500]; sin alterar las demás filas.
3. Inmutabilidad de String: Grabado en Piedra
Un objeto String en Java NUNCA puede ser modificado. Métodos como .toUpperCase(), .trim() o .replace() no alteran el String original: instancian un NUEVO objeto en el Heap. Si no guardás la referencia, se pierde en el Garbage Collector.
¡El error clásico del principiante! Llamar al método sin guardar el retorno deja a la variable apuntando al texto viejo.
4. El String Constant Pool: == vs .equals()
La JVM optimiza la memoria guardando los literales entre comillas en una piscina especial (String Pool). Si dos variables usan "java", apuntan al mismo objeto. Pero si usás new String("java"), forzás un objeto duplicado afuera. ¡Por eso JAMÁS se compara con ==!
Ambos literales reutilizan la misma celda de memoria. == compara punteros y devuelve true por casualidad.
5. StringBuilder: El Buffer Mutable de Texto
Concatenar texto en un bucle con + ("s += i") crea un nuevo String por cada vuelta, colapsando el Heap con miles de objetos basura. StringBuilder mantiene un único array interno de caracteres (char[]) que se expande dinámicamente.
Cada concatenación copia todos los caracteres anteriores a un nuevo array. Para 1.000 vueltas, el costo es exponencial.