Arrays de Objetos: Guardar y Recorrer Muchas Instancias

En la lección Arrays y Manejo de Strings en Java aprendiste a guardar muchos valores del mismo tipo en un array. En las lecciones Fundamentos de POO: Clases, Objetos y Atributos y Constructores, Modificadores de Acceso y Getters/Setters aprendiste a diseñar una clase que garantiza que cada objeto nazca válido y se mantenga consistente.

Esta lección junta las dos cosas, y ese cruce es donde Java empieza a servir para algo real:

Persona p1 = new Persona("Laura", 28);
Persona p2 = new Persona("Carlos", 35);
Persona p3 = new Persona("Ana", 41);
// ¿y si son 500?

Exactamente el mismo problema que te llevó al array en la lección Arrays y Manejo de Strings en Java, pero ahora con objetos. La solución es la misma —un array— salvo por un detalle que cambia todo: un array de objetos no guarda objetos, guarda referencias a objetos.

Ese detalle es responsable del NullPointerException más común de todo Java, de que dos casillas puedan modificar el mismo objeto sin que lo notes, y de que Arrays.sort no funcione hasta que le expliques cómo comparar. Vamos por partes.


1. Un array de objetos tiene dos niveles de memoria

Un int[] guarda los números adentro. Un Persona[] guarda direcciones que apuntan a objetos que viven en otro lado del Heap.

Un array de primitivos guarda los valores; un array de objetos guarda referencias a objetos externos int[] edades = {28, 35, 41}; 28 35 41 Un solo nivel. El valor está dentro de la casilla. Persona[] personas = {new Persona("Laura", 28), ...}; ref ref ref Persona nombre="Laura" edad=28 Persona nombre="Carlos" edad=35 Persona nombre="Ana" edad=41 Dos niveles. La casilla solo apunta.
El array de objetos es un array de flechas. Los objetos son independientes de él: el array puede desaparecer y los objetos sobrevivir, y un mismo objeto puede estar apuntado desde varias casillas.

De esa indirección salen las tres reglas que gobiernan el resto de la lección:

  • Crear el array no crea los objetos. Son dos pasos, no uno.
  • Copiar una casilla copia la flecha, no el objeto. Dos casillas pueden apuntar al mismo lugar.
  • Una casilla puede no apuntar a nada. Ese “nada” se llama null, y es la fuente de errores número uno.

2. Los dos pasos de la creación

Este es el error inaugural de todo el que empieza con arrays de objetos:

Persona[] personas = new Persona[3];

System.out.println(personas.length);          // 3   — el array existe
System.out.println(personas[0]);              // null — pero está vacío
System.out.println(personas[0].getNombre());  // NullPointerException

new Persona[3] reserva tres casillas capaces de apuntar a una Persona. No construye ninguna Persona. Es la diferencia entre comprar tres sobres y escribir tres cartas.

Crear el array y crear los objetos son dos operaciones distintas 1 new Persona[3] Reserva tres casillas. Las tres quedan en null: no hay ningún objeto todavía. null null null length = 3 2 personas[i] = new Persona(...) Recién ahora se construyen los objetos y cada casilla pasa a apuntar a uno. Laura 28 años Carlos 35 años Ana 41 años length = 3 Si te salteás el paso 2 para alguna casilla, esa casilla sigue en null y explota la primera vez que la uses.
Con primitivos el paso 2 no existe: new int[3] ya te deja tres ceros usables. Con objetos, el array nace vacío y llenarlo es trabajo tuyo.

Las tres formas de llenarlo:

// A) Literal: creás array y objetos en una sola expresión
Persona[] personas = {
    new Persona("Laura", 28),
    new Persona("Carlos", 35),
    new Persona("Ana", 41)
};

// B) Casilla por casilla
Persona[] equipo = new Persona[3];
equipo[0] = new Persona("Laura", 28);
equipo[1] = new Persona("Carlos", 35);
equipo[2] = new Persona("Ana", 41);

// C) En un bucle, que es el caso real
String[] nombres = {"Laura", "Carlos", "Ana"};
int[] edades = {28, 35, 41};

Persona[] plantel = new Persona[nombres.length];
for (int i = 0; i < plantel.length; i++) {
    plantel[i] = new Persona(nombres[i], edades[i]);
}

Fijate lo que hace posible la forma C: el constructor validador de la lección Constructores, Modificadores de Acceso y Getters/Setters corre una vez por cada objeto. Si alguno de los datos de entrada es inválido, el constructor lo reemplaza por un valor por defecto seguro y avisa por consola: el objeto igual nace, pero nunca con datos rotos. Sin constructores tendrías que crear los tres objetos vacíos y llenarlos después a mano, que es exactamente la ventana de tiempo con objetos rotos que esa lección se ocupó de cerrar.


3. El null en las casillas: el error más común de Java

Un array parcialmente lleno es una bomba de tiempo:

Persona[] personas = new Persona[5];
personas[0] = new Persona("Laura", 28);
personas[1] = new Persona("Carlos", 35);
// las casillas 2, 3 y 4 quedaron en null

for (Persona p : personas) {
    System.out.println(p.getNombre());   // NullPointerException en la tercera vuelta
}

Tenés tres formas de defenderte, en orden de preferencia:

// 1. La mejor: que el array no tenga casillas de más
Persona[] personas = new Persona[2];   // creá exactamente las que vas a llenar

// 2. Si no podés, verificá antes de usar
for (Persona p : personas) {
    if (p != null) {
        System.out.println(p.getNombre());
    }
}

// 3. Si el null nunca debería pasar, fallá fuerte y temprano
import java.util.Objects;

public void registrar(Persona p) {
    this.persona = Objects.requireNonNull(p, "La persona no puede ser null");
}

La opción 2 es un parche, no una solución. Un array con null adentro casi siempre significa que en realidad necesitabas capacidad y cantidad por separado — es el tema de la sección 8, y es la puerta de entrada al TAD Lista.


4. Recorrer y mostrar

Recorrer es igual que con primitivos, con la diferencia de que ahora cada elemento tiene métodos.

for (Persona p : personas) {
    System.out.println(p.getNombre() + " tiene " + p.getEdad() + " años");
}

// Con índice, cuando necesitás reemplazar la referencia de una casilla
for (int i = 0; i < personas.length; i++) {
    if (personas[i].getEdad() < 18) {
        personas[i] = new Persona(personas[i].getNombre(), 18);
    }
}

toString(): sin él, imprimir un array de objetos no sirve

System.out.println(Arrays.toString(personas));
// [Persona@1b6d3586, Persona@4554617c, Persona@74a14482]

Eso que ves es el toString() que hereda de Object: nombre de la clase, arroba y el hash en hexadecimal. Inútil. La solución es sobrescribirlo en tu clase:

public class Persona {
    private final String nombre;
    private final int edad;

    public Persona(String nombre, int edad) {
        if (nombre == null || nombre.isBlank()) {
            System.out.println("Nombre inválido, se usó \"Sin nombre\" por defecto.");
            nombre = "Sin nombre";
        }
        if (edad < 0 || edad > 130) {
            System.out.println("Edad fuera de rango, se usó 0 por defecto.");
            edad = 0;
        }
        this.nombre = nombre;
        this.edad = edad;
    }

    public String getNombre() { return nombre; }
    public int getEdad()      { return edad; }

    @Override
    public String toString() {
        return nombre + " (" + edad + ")";
    }
}
System.out.println(Arrays.toString(personas));
// [Laura (28), Carlos (35), Ana (41)]

Escribí toString() en toda clase que vayas a meter en un array. El costo son tres líneas y te ahorra horas de depuración a ciegas.


5. Buscar dentro de un array de objetos

Con primitivos buscabas un valor. Con objetos buscás el objeto cuyo atributo cumple algo, y eso siempre es un recorrido lineal escrito a mano:

public static Persona buscarPorNombre(Persona[] personas, String nombre) {
    for (Persona p : personas) {
        if (p != null && p.getNombre().equalsIgnoreCase(nombre)) {
            return p;      // encontrada: cortamos acá
        }
    }
    return null;           // recorrimos todo y no está
}

Tres decisiones de diseño que valen más que el código:

  • equalsIgnoreCase, nunca ==. Comparás el contenido de dos String, y ya viste en la lección Arrays y Manejo de Strings en Java por qué == te traiciona apenas el texto viene de afuera.
  • p != null primero. El orden importa: si evaluás p.getNombre() antes de la comprobación, explota.
  • Devolver null cuando no está. Es la opción de esta lección: quien llama recibe la responsabilidad de comprobar el resultado antes de usarlo.
Persona encontrada = buscarPorNombre(personas, "Ana");
if (encontrada != null) {
    System.out.println("Encontrada: " + encontrada);
} else {
    System.out.println("No está en el plantel");
}

Olvidarte de ese if (encontrada != null) es la forma más común de convertir un “no encontrado” en un NullPointerException tres líneas más abajo.


6. Ordenar: escribir el criterio a mano

Con int[] alcanzaba con Arrays.sort(numeros), porque los números tienen un orden obvio. Con objetos no lo hay: ¿dos personas se ordenan por nombre, por edad, por fecha de ingreso? Alguien tiene que decidirlo, y por ahora ese alguien sos vos: Arrays.sort(personas) compila, pero revienta en tiempo de ejecución porque Persona no sabe compararse a sí misma.

Java tiene un mecanismo para declarar ese criterio una sola vez y reutilizarlo en cualquier Arrays.sort —lo vas a ver en la lección Iteradores, Ordenamiento y Contrato equals/hashCode—. Hasta entonces, ordenás a mano: recorrés el array y comparás el campo elegido con < y >, igual que harías con un int[] si no existiera Arrays.sort.

Selección es el algoritmo más simple para escribirlo a mano: en cada vuelta buscás el elemento con el valor más chico del resto del array y lo llevás al frente.

public static void ordenarPorEdad(Persona[] personas) {
    for (int i = 0; i < personas.length - 1; i++) {
        int indiceMenor = i;
        for (int j = i + 1; j < personas.length; j++) {
            if (personas[j].getEdad() < personas[indiceMenor].getEdad()) {
                indiceMenor = j;
            }
        }
        if (indiceMenor != i) {
            Persona temporal = personas[i];
            personas[i] = personas[indiceMenor];
            personas[indiceMenor] = temporal;
        }
    }
}
ordenarPorEdad(personas);
System.out.println(Arrays.toString(personas));
// [Laura (28), Carlos (35), Ana (41)]

Fijate que el intercambio mueve referencias, no objetos: temporal guarda una flecha, no una copia de Persona. Ordenar un array de objetos nunca duplica lo que apuntan las casillas.

Para ordenar por otro campo —el nombre, por ejemplo— repetirías el mismo bucle cambiando solo la condición del if. Repetir ese bucle una vez por criterio, y no tener ninguna garantía sobre qué pasa con los empates si además querés desempatar por un segundo campo, es exactamente el problema que la lección Iteradores, Ordenamiento y Contrato equals/hashCode resuelve: vas a declarar el criterio de orden una sola vez y pasárselo a Arrays.sort, sin repetir el bucle.

Un array de objetos con null adentro rompe cualquier ordenamiento a mano con NullPointerException, porque personas[j].getEdad() explota apenas j cae en una casilla vacía. Otro motivo para no dejar huecos.


7. Aliasing: la casilla apunta, no contiene

Esta es la consecuencia menos intuitiva de los dos niveles de memoria.

Dos casillas que apuntan al mismo objeto comparten cualquier cambio equipo[2] = equipo[0]; equipo[2].cumplirAnios(); equipo [0] ref [1] ref [2] ref Persona "Laura" edad: 28 → 29 (cambió una sola vez) Persona "Carlos" equipo[0] y equipo[2] son dos flechas al MISMO objeto. Modificarlo por una se ve por la otra: equipo[0].getEdad() ahora es 29. El objeto de la casilla [2] original quedó sin referencias y se lo lleva el recolector de basura. Para tener dos objetos independientes hay que construir uno nuevo, no copiar la referencia.
Esto es lo mismo que viste con la copia superficial en la lección [Arrays y Manejo de Strings en Java](/cursos/java/04-arrays-y-strings), pero adentro de un solo array. Asignar una casilla a otra nunca duplica el objeto.
Persona[] equipo = {
    new Persona("Laura", 28),
    new Persona("Carlos", 35),
    new Persona("Ana", 41)
};

equipo[2] = equipo[0];        // ahora [0] y [2] apuntan al MISMO objeto
System.out.println(equipo[0] == equipo[2]);   // true

Lo mismo pasa al copiar el array entero:

Persona[] copia = Arrays.copyOf(equipo, equipo.length);

System.out.println(copia == equipo);          // false — son dos arrays distintos
System.out.println(copia[0] == equipo[0]);    // true  — comparten los objetos

Una copia profunda hay que escribirla:

Persona[] profunda = new Persona[equipo.length];
for (int i = 0; i < equipo.length; i++) {
    profunda[i] = new Persona(equipo[i].getNombre(), equipo[i].getEdad());
}

Este problema desaparece si tu clase es inmutable —atributos final y sin setters, como la Persona de arriba—. Si nadie puede modificar el objeto, compartir la referencia no puede hacer daño. Es la misma razón por la que nunca sufrís aliasing con String.


8. Capacidad contra cantidad: el array parcialmente lleno

Casi siempre no sabés de antemano cuántos objetos vas a guardar. La solución clásica es reservar de más y llevar la cuenta de cuántas casillas están realmente usadas.

Capacidad es el largo del array; cantidad es cuántas casillas están realmente ocupadas Persona[] datos = new Persona[6]; int cantidad = 3; Laura [0] Carlos [1] Ana [2] null [3] null [4] null [5] cantidad = 3 — la parte que existe de verdad datos.length = 6 — la capacidad reservada Regla de oro: recorré hasta cantidad, nunca hasta datos.length for (int i = 0; i menor que cantidad; i++) → así jamás tocás una casilla null. Una clase que envuelva este array y este contador, con agregar/eliminar/obtener, es exactamente un TAD Lista: Estáticas, Dinámicas y Enlazadas.
Separar capacidad de cantidad es el paso conceptual que convierte un array suelto en una estructura de datos. El TAD Lista de la lección [TAD Lista: Estáticas, Dinámicas y Enlazadas](/cursos/java/11-tad-listas-estaticas-y-dinamicas) hace exactamente esto por dentro.
public class Registro {
    private Persona[] datos = new Persona[6];
    private int cantidad = 0;                 // cuántas casillas están usadas

    public void agregar(Persona p) {
        if (cantidad == datos.length) {
            // se llenó: duplicamos la capacidad (ver Arrays y Manejo de Strings en Java, sección 4)
            datos = Arrays.copyOf(datos, datos.length * 2);
        }
        datos[cantidad] = p;
        cantidad++;
    }

    public boolean eliminar(int indice) {
        if (indice < 0 || indice >= cantidad) {
            System.out.println("Índice inválido: " + indice);
            return false;
        }
        // corremos todo lo que está a la derecha una posición a la izquierda
        for (int i = indice; i < cantidad - 1; i++) {
            datos[i] = datos[i + 1];
        }
        datos[cantidad - 1] = null;   // liberamos la referencia sobrante
        cantidad--;
        return true;
    }

    public int getCantidad() {
        return cantidad;
    }

    public void listar() {
        for (int i = 0; i < cantidad; i++) {    // hasta cantidad, no hasta length
            System.out.println(datos[i]);
        }
    }
}

Prestá atención a datos[cantidad - 1] = null; en eliminar. Sin esa línea el programa funciona igual, pero la última casilla sigue apuntando a un objeto que ya nadie usa y el recolector de basura no puede liberarlo. Es una fuga de memoria pequeña, silenciosa y perfectamente evitable.


9. Un array de objetos como atributo: composición y copia defensiva

El caso más frecuente no es un array suelto en el main, es un array adentro de otra clase:

public class Curso {
    private final String nombre;
    private final Persona[] alumnos;

    public Curso(String nombre, Persona[] alumnos) {
        this.nombre = nombre;
        this.alumnos = alumnos;          // MAL
    }

    public Persona[] getAlumnos() {
        return alumnos;                  // MAL
    }
}

Las dos líneas marcadas anulan el encapsulamiento de la lección Constructores, Modificadores de Acceso y Getters/Setters, por el mismo motivo de la sección 7: se está compartiendo la flecha.

Persona[] lista = { new Persona("Laura", 28) };
Curso curso = new Curso("Java", lista);

lista[0] = new Persona("Intruso", 99);   // modificamos el array desde afuera
System.out.println(curso.getAlumnos()[0]);   // Intruso (99)

curso.getAlumnos()[0] = null;            // y también desde el getter

La corrección es copiar en la entrada y en la salida:

public Curso(String nombre, Persona[] alumnos) {
    this.nombre = nombre;
    this.alumnos = Arrays.copyOf(alumnos, alumnos.length);   // copia al entrar
}

public Persona[] getAlumnos() {
    return Arrays.copyOf(alumnos, alumnos.length);           // copia al salir
}

Con una Persona inmutable, la copia superficial alcanza: nadie puede modificar los objetos apuntados. Si Persona tuviera setters, harían falta copias profundas para blindar el Curso de verdad.


10. Errores frecuentes

  • Creer que new Persona[3] crea tres personas. Crea tres null.
  • Recorrer hasta length teniendo un contador. Recorré hasta cantidad.
  • No sobrescribir toString(). Imprimís hashes en hexadecimal y depurás a ciegas.
  • Tratar un array de objetos como si Arrays.sort supiera ordenarlo solo. Con objetos necesitás decidir el campo de comparación y escribir el bucle vos mismo.
  • Ordenar un array con null adentro. NullPointerException apenas la comparación toca una casilla vacía.
  • Comparar objetos con ==. Compara identidad. Para contenido hace falta equals() — su contrato completo llega en la lección Iteradores, Ordenamiento y Contrato equals/hashCode.
  • Asignar a[i] = a[j] creyendo que copia. Copia la referencia; quedan dos flechas al mismo objeto.
  • Exponer el array interno de una clase. Un private no sirve de nada si el getter devuelve la referencia.
  • Olvidar datos[cantidad - 1] = null al eliminar. Deja viva una referencia que el GC no puede liberar.

11. Ejercicios prácticos guiados

Ejercicio 1 — Estadísticas del plantel

Dado un Persona[] sin null, mostrá la persona más joven, la más grande y el promedio de edad en un solo recorrido.

Ver solución sugerida
public class Estadisticas {

    public static void informe(Persona[] personas) {
        if (personas == null || personas.length == 0) {
            System.out.println("No hay personas para analizar");
            return;
        }

        // Arrancamos con el primer elemento, no con valores inventados
        Persona masJoven = personas[0];
        Persona masGrande = personas[0];
        long sumaEdades = 0;

        for (Persona p : personas) {
            if (p.getEdad() < masJoven.getEdad())  masJoven = p;
            if (p.getEdad() > masGrande.getEdad()) masGrande = p;
            sumaEdades += p.getEdad();
        }

        double promedio = (double) sumaEdades / personas.length;

        System.out.println("Más joven: " + masJoven);
        System.out.println("Más grande: " + masGrande);
        System.out.println("Promedio: %.1f años".formatted(promedio));
    }

    public static void main(String[] args) {
        Persona[] plantel = {
            new Persona("Laura", 28),
            new Persona("Carlos", 35),
            new Persona("Ana", 41)
        };
        informe(plantel);
    }
}

Fijate que masJoven guarda la referencia al objeto, no la edad. Así, cuando terminás el recorrido, tenés la persona entera y no solo su número: podés imprimir su nombre sin volver a buscarla.

Ejercicio 2 — Filtrar en un array nuevo

Escribí un método que reciba un Persona[] y devuelva otro array solo con las personas mayores de una edad dada. El array resultante no debe tener casillas sobrantes.

Ver solución sugerida
import java.util.Arrays;

public static Persona[] mayoresDe(Persona[] personas, int edadMinima) {
    // 1. Reservamos con la capacidad máxima posible
    Persona[] resultado = new Persona[personas.length];
    int cantidad = 0;

    // 2. Llenamos solo con los que cumplen
    for (Persona p : personas) {
        if (p != null && p.getEdad() >= edadMinima) {
            resultado[cantidad] = p;
            cantidad++;
        }
    }

    // 3. Recortamos a la cantidad real: sin este paso quedan null al final
    return Arrays.copyOf(resultado, cantidad);
}

Los tres pasos son el patrón estándar de filtrado con arrays: reservar de más, contar, recortar. El paso 3 es el que suele faltar, y es el que evita que el array devuelto tenga null que exploten después.

Notá también que el array resultante comparte los objetos con el original (aliasing, sección 7). Para este caso está bien: filtrar no debería duplicar personas.

Ejercicio 3 — Ordenar por edad descendente

Ordená un Persona[] de mayor a menor edad, sin usar Arrays.sort.

Ver solución sugerida
import java.util.Arrays;

public class OrdenarPersonas {
    public static void main(String[] args) {
        Persona[] personas = {
            new Persona("Laura", 35),
            new Persona("Ana", 41),
            new Persona("Bruno", 35),
            new Persona("Carlos", 28)
        };

        for (int i = 0; i < personas.length - 1; i++) {
            int indiceMayor = i;
            for (int j = i + 1; j < personas.length; j++) {
                if (personas[j].getEdad() > personas[indiceMayor].getEdad()) {
                    indiceMayor = j;
                }
            }
            if (indiceMayor != i) {
                Persona temporal = personas[i];
                personas[i] = personas[indiceMayor];
                personas[indiceMayor] = temporal;
            }
        }

        System.out.println(Arrays.toString(personas));
        // [Ana (41), Laura (35), Bruno (35), Carlos (28)]
    }
}

Laura y Bruno empatan en edad (35). Este bucle, tal como está escrito, no da ninguna garantía sobre el orden relativo de los empates —en esta corrida particular Laura queda antes que Bruno, pero eso es una consecuencia de los intercambios, no una regla del algoritmo—. Desempatar de forma predecible por un segundo criterio, como el nombre, sin reescribir el bucle entero cada vez, es exactamente lo que vas a aprender a hacer en la lección Iteradores, Ordenamiento y Contrato equals/hashCode.

Ejercicio 4 — Agenda con capacidad dinámica

Implementá una clase Agenda que guarde Contacto en un array interno, crezca sola cuando se llena, y permita agregar, buscar por nombre y eliminar por índice.

Ver solución sugerida
import java.util.Arrays;

public class Agenda {
    private Contacto[] contactos = new Contacto[4];
    private int cantidad = 0;

    public boolean agregar(Contacto c) {
        if (c == null) {
            System.out.println("El contacto no puede ser null, no se agregó.");
            return false;
        }
        if (cantidad == contactos.length) {
            contactos = Arrays.copyOf(contactos, contactos.length * 2);
        }
        contactos[cantidad++] = c;
        return true;
    }

    public Contacto buscar(String nombre) {
        for (int i = 0; i < cantidad; i++) {
            if (contactos[i].getNombre().equalsIgnoreCase(nombre)) {
                return contactos[i];
            }
        }
        return null;
    }

    public boolean eliminar(int indice) {
        if (indice < 0 || indice >= cantidad) {
            System.out.println("Índice " + indice + " fuera de rango [0, " + (cantidad - 1) + "].");
            return false;
        }
        for (int i = indice; i < cantidad - 1; i++) {
            contactos[i] = contactos[i + 1];
        }
        contactos[--cantidad] = null;
        return true;
    }

    public int getCantidad() {
        return cantidad;
    }

    public Contacto[] listar() {
        // copia defensiva y recortada: ni referencias internas ni null sobrantes
        return Arrays.copyOf(contactos, cantidad);
    }
}

Esta clase ya es, conceptualmente, una lista dinámica en miniatura: array interno, capacidad que se duplica, tamaño lógico separado del físico y desplazamiento al eliminar. En la lección TAD Lista: Estáticas, Dinámicas y Enlazadas vas a formalizarla como TAD Lista y a compararla con la versión enlazada.


Para llevarte

  • Un array de objetos guarda referencias, no objetos. Todo lo raro que hace sale de ahí.
  • new Persona[3] crea tres null. Crear el array y crear los objetos son dos pasos.
  • Un array con casillas vacías es la causa del NullPointerException más frecuente de Java.
  • Sobrescribí toString() en toda clase que vayas a guardar en un array: sin él, imprimir no informa nada.
  • Buscar es siempre un recorrido lineal escrito a mano; si devolvés null cuando no hay resultado, quien llama debe comprobarlo con != null antes de usarlo.
  • Ordenar objetos exige elegir un criterio y compararlo campo a campo con tu propio bucle; la lección Iteradores, Ordenamiento y Contrato equals/hashCode muestra cómo declarar ese criterio una sola vez y reutilizarlo.
  • Asignar una casilla a otra no copia el objeto: quedan dos flechas al mismo lugar.
  • Un array interno de una clase se copia al entrar y al salir, o el private no protege nada.
  • Separar capacidad de cantidad es lo que convierte un array en una estructura de datos. Ese es el punto de partida del TAD Lista.