ListExamples.java

package fr.univtln.bruno.samples.java101.tp3.list;

import fr.univtln.bruno.samples.java101.tp3.Person;
import fr.univtln.bruno.samples.java101.tp3.functional.MappingAndSortingExamples;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * Examples demonstrating common List implementations and idioms.
 *
 * <p>Each static method illustrates characteristics, complexity or pitfalls of a
 * specific List usage (ArrayList, LinkedList, subList, immutability...).</p>
 */
@Slf4j
public class ListExamples {
  /**
   * Private constructor to prevent instantiation.
   */
  private ListExamples() {
  }

  /**
   * ArrayList: access O(1) index, append amortized O(1), remove middle O(n).
   */
  public static void arrayListExample() {
    log.info("=== ArrayList Example ===");
    List<Person> people = new ArrayList<>();
    people.add(Person.of("Alice", "Smith", 30));
    people.add(Person.of("Bob", "Jones", 25));
    people.add(Person.of("Charlie", "Brown", 35));
    log.debug("Initial people list: {}", people);
    log.info("Size: {}", people.size());
    // safe access because we just added elements above
    log.info("First: {}", people.get(0).getFullName());
    log.info("Contains Alice? {}", people.contains(Person.of("Alice", "Smith", 30)));
    people.remove(1); // remove Bob
    log.debug("After remove index=1 list: {}", people);
    log.info("After remove index=1 size: {}", people.size());
    people.remove(Person.of("Alice", "Smith", 30));
    log.debug("After remove Alice list: {}", people);
    log.info("After remove Alice size: {}", people.size());
  }

  /**
   * LinkedList: insert/remove head/tail O(1), index access O(n).
   */
  public static void linkedListExample() {
    log.info("=== LinkedList Example ===");
    LinkedList<Person> people = new LinkedList<>();
    people.addLast(Person.of("Alice", "Smith", 30));
    people.addLast(Person.of("Bob", "Jones", 25));
    people.addFirst(Person.of("Charlie", "Brown", 35));
    log.info("First: {} Last: {} Size: {}", people.getFirst().getFullName(), people.getLast().getFullName(), people.size());
    people.offer(Person.of("David", "Wilson", 40));
    Person polled = people.poll();
    if (polled != null) log.info("Poll (FIFO): {}", polled.getFullName());
    people.push(Person.of("Eve", "Davis", 28));
    Person popped = people.pop();
    if (popped != null) log.info("Pop (LIFO): {}", popped.getFullName());
  }

  /**
   * Immutability vs unmodifiable view.
   */
  public static void immutableListExample() {
    log.info("=== Immutable Lists Example ===");
    List<String> immutable = List.of("A", "B", "C");
    log.info("Immutable: {}", immutable);
    // don't attempt to modify List.of() result; demonstrate safe usage instead
    List<String> backing = new ArrayList<>(List.of("X", "Y", "Z"));
    List<String> unmodifiable = Collections.unmodifiableList(backing);
    backing.add("W");
    log.info("Backing modified reflected in view: {}", unmodifiable);
  }

  /**
   * SubList: a view backed by the original list.
   */
  public static void subListExample() {
    log.info("=== SubList Example ===");
    List<Integer> numbers = new ArrayList<>(List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
    List<Integer> slice = numbers.subList(3, 7);
    log.info("Slice [3,7): {}", slice);
    slice.set(0, 99);
    log.info("After set in slice original: {}", numbers);
    slice.clear();
    log.info("After clear slice original: {}", numbers);

    // Student note: subList is a view. If you need a detached copy, do:
    // List<Integer> detached = new ArrayList<>(numbers.subList(3,7));
  }

  /**
   * Sorting with Comparable then Comparator.
   */
  public static void sortingExample() {
    log.info("=== Sorting Example ===");
    List<Person> people = new ArrayList<>(List.of(
      Person.of("Charlie", "Brown", 35),
      Person.of("Alice", "Smith", 30),
      Person.of("Bob", "Jones", 25)
    ));
    log.debug("Before sort: {}", people);
    MappingAndSortingExamples.printFullNames(people);
    Collections.sort(people);
    log.debug("After natural sort: {}", people);
    MappingAndSortingExamples.printFullNames(people);
    people.sort(Comparator.comparing(Person::age).reversed());
    log.debug("After age-desc sort: {}", people);
    MappingAndSortingExamples.printNamesWithAges(people);
  }

  /**
   * Iteration: foreach, removeIf, ListIterator (bidirectional).
   */
  public static void iterationExample() {
    log.info("=== Iteration Example ===");
    List<String> items = new ArrayList<>(List.of("A", "B", "C", "D"));
    for (String i : items) {
      log.info("Foreach: {}", i);
    }
    // remove 'B' using removeIf instead of iterator.remove()
    // Uses a lambda expression (functional style)
    items.removeIf(s -> "B".equals(s));
    log.info("After removeIf remove B: {}", items);
    ListIterator<String> listIt = items.listIterator(items.size());
    while (listIt.hasPrevious()) {
      log.info("Backward: {}", listIt.previous());
    }

    // Student note: if you need to remove while iterating, don't use for-each; use Iterator explicitly:
    // Iterator<String> it = items.iterator();
    // while (it.hasNext()) {
    //   if (shouldRemove(it.next())) it.remove();
    // }
  }

  public static void main(String[] args) {
    arrayListExample();
    linkedListExample();
    immutableListExample();
    subListExample();
    sortingExample();
    iterationExample();
  }
}