The LinkedList in Java is a part of the Java Collections Framework and is present in java.util package. It is a doubly-linked list implementation of the List and Deque interfaces.

Key Points

Dynamic Sizing: Like ArrayList, LinkedList is also resizable.

Ordering: Elements in a LinkedList maintain their order.

Insertion & Deletion: Generally, faster insertion and deletion compared to ArrayList as no shift is needed. However, it depends on the position of insertion/deletion.

Performance: Slower get and set methods compared to ArrayList because of the need to traverse the list.

Memory Overhead: More memory is consumed per element in a LinkedList than an ArrayList because every element in the LinkedList has two references (next and previous) in addition to the data.

Not Synchronized: Like ArrayList, LinkedList is not synchronized by default.

Bidirectional Traversal: With its ListIterator, you can traverse the list in both forward and backward directions.

1. Create a LinkedList and Adding New Elements to it

import java.util.LinkedList;

public class LinkedListDemo {
    public static void main(String[] args) {
        // 1. Creating a new LinkedList of Strings
        LinkedList<String> fruits = new LinkedList<>();

        // 2. Adding elements to the LinkedList
        fruits.add("Mango");   // Adds &apos;Mango&apos; to the end of the list
        fruits.add("Apple");   // Adds &apos;Apple&apos; to the end of the list
        fruits.addFirst("Banana"); // Adds &apos;Banana&apos; to the beginning of the list
        fruits.addLast("Orange");  // Adds &apos;Orange&apos; to the end of the list

        // 3. Printing the LinkedList
        System.out.println(fruits);
    }
}

Output:

[Banana, Mango, Apple, Orange]

Explanation:

1. An instance of LinkedList named fruits is created to store strings.

2. The add() method is used to add elements to the end of the list. So, "Mango" and then "Apple" are added.

3. The addFirst() method inserts the specified element at the beginning of the list. Here, "Banana" is added to the start.

4. The addLast() method appends the specified element to the end of the list. "Orange" is added after the other elements.

5. The entire list is then printed, displaying the order in which elements were added.

2. Retrieve Elements from a LinkedList

import java.util.LinkedList;

public class LinkedListRetrieveDemo {
    public static void main(String[] args) {
        // 1. Create a LinkedList and add some elements
        LinkedList<String> fruits = new LinkedList<>();
        fruits.add("Banana");
        fruits.add("Mango");
        fruits.add("Apple");
        fruits.add("Orange");

        // 2. Retrieve elements from the LinkedList
        String firstElement = fruits.getFirst();  // Get the first element
        String lastElement = fruits.getLast();    // Get the last element
        String secondElement = fruits.get(1);     // Get the element at index 1

        // 3. Print the retrieved elements
        System.out.println("First Element: " + firstElement);
        System.out.println("Last Element: " + lastElement);
        System.out.println("Element at Index 1: " + secondElement);
    }
}

Output:

First Element: Banana
Last Element: Orange
Element at Index 1: Mango

Explanation:

1. A LinkedList named fruits is initialized and populated with four fruit names.

2. The method getFirst() is used to retrieve the first element from the list, getLast() to get the last element, and get(index) to get an element at a specified index.

3. These retrieved elements are then printed to the console to display their values.

3. Remove Elements from a LinkedList

import java.util.LinkedList;
import java.util.function.Predicate;

public class LinkedListRemoveDemo {
    public static void main(String[] args) {
        // 1. Initialize a LinkedList with some elements
        LinkedList<String> fruits = new LinkedList<>();
        fruits.add("Banana");
        fruits.add("Mango");
        fruits.add("Apple");
        fruits.add("Orange");
        fruits.add("Mango");

        // 2. Remove elements from the LinkedList
        fruits.removeFirst();                             // Remove the first element
        fruits.removeLast();                              // Remove the last element
        fruits.remove("Mango");                           // Remove the first occurrence of "Mango"

        Predicate<String> condition = fruit -> fruit.contains("Apple");
        fruits.removeIf(condition);                       // Remove all elements that satisfy the predicate

        System.out.println(fruits);

        // 3. Clear the entire LinkedList
        fruits.clear();
        System.out.println("After clearing: " + fruits);
    }
}

Output:

[Apple]
After clearing: []

Explanation:

1. A LinkedList named fruits is initialized and populated with several fruit names.

2. The method removeFirst() is used to remove the first element, and removeLast() to remove the last element from the list. The remove(Object) method is used to remove the first occurrence of the specified object, in this case, "Mango". The removeIf(Predicate) method removes all of the elements of this collection that satisfy the given predicate.

3. The clear() method is called to remove all the elements in the LinkedList, making it empty.

4. Search Elements in a LinkedList

import java.util.LinkedList;

public class LinkedListSearchDemo {
    public static void main(String[] args) {
        // 1. Initialize a LinkedList with some elements
        LinkedList<String> fruits = new LinkedList<>();
        fruits.add("Banana");
        fruits.add("Mango");
        fruits.add("Apple");
        fruits.add("Orange");
        fruits.add("Mango");

        // 2. Check for existence of an element
        boolean exists = fruits.contains("Mango");
        System.out.println("Mango exists in list: " + exists);

        // 3. Find index of first and last occurrence
        int firstIndex = fruits.indexOf("Mango");
        System.out.println("First occurrence of Mango: " + firstIndex);

        int lastIndex = fruits.lastIndexOf("Mango");
        System.out.println("Last occurrence of Mango: " + lastIndex);
    }
}

Output:

Mango exists in list: true
First occurrence of Mango: 1
Last occurrence of Mango: 4

Explanation:

1. A LinkedList named fruits is initialized and populated with several fruit names.

2. The contains(Object) method is used to check if an element, in this case "Mango", exists in the LinkedList.

3. The indexOf(Object) method returns the index of the first occurrence of the specified element in the list, or -1 if the list does not contain the element. Similarly, the lastIndexOf(Object) method is used to find the index of the last occurrence of the specified element.

5. Iterate Over a LinkedList – Different Ways

import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

public class LinkedListIterationDemo {
    public static void main(String[] args) {
        LinkedList<String> fruits = new LinkedList<>();
        fruits.add("Banana");
        fruits.add("Mango");
        fruits.add("Apple");
        fruits.add("Orange");

        // 1. Using Java 8 forEach() with lambda
        System.out.println("Using Java 8 forEach with lambda:");
        fruits.forEach(fruit -> System.out.println(fruit));

        // 2. Using iterator
        System.out.println("\nUsing iterator:");
        Iterator<String> iterator = fruits.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }

        // 3. Using iterator() and Java 8 forEachRemaining() method
        System.out.println("\nUsing iterator with forEachRemaining method:");
        iterator = fruits.iterator();
        iterator.forEachRemaining(fruit -> System.out.println(fruit));

        // 4. Using descendingIterator
        System.out.println("\nUsing descendingIterator:");
        Iterator<String> descendingIterator = fruits.descendingIterator();
        while (descendingIterator.hasNext()) {
            System.out.println(descendingIterator.next());
        }

        // 5. Using listIterator
        System.out.println("\nUsing listIterator:");
        ListIterator<String> listIterator = fruits.listIterator();
        while (listIterator.hasNext()) {
            System.out.println(listIterator.next());
        }

        // 6. Using simple for-each loop
        System.out.println("\nUsing simple for-each loop:");
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
    }
}

Output:

Using Java 8 forEach with lambda:
Banana
Mango
Apple
Orange

Using iterator:
Banana
Mango
Apple
Orange

Using iterator with forEachRemaining method:
Banana
Mango
Apple
Orange

Using descendingIterator:
Orange
Apple
Mango
Banana

Using listIterator:
Banana
Mango
Apple
Orange

Using simple for-each loop:
Banana
Mango
Apple
Orange

Explanation:

1. The forEach() method from Java 8 allows us to iterate over the LinkedList using lambda expressions.

2. An Iterator is an object-oriented design pattern that provides a way to access elements of a collection sequentially without exposing the underlying structure.

3. The forEachRemaining() method from Java 8 with an Iterator behaves similarly to forEach(), but it starts from the current position of the iterator.

4. The descendingIterator() returns an iterator over the elements in this deque in reverse sequential order.

5. ListIterator is an iterator for lists that allows the programmer to traverse the list in either direction and modify the list during iteration.

6. A simple for-each loop is a traditional and straightforward method for iterating over collections.

6. Real-World Example – Student Management using LinkedList

import java.util.LinkedList;

class Student {
    private int id;
    private String name;
    private double grade;

    public Student(int id, String name, double grade) {
        this.id = id;
        this.name = name;
        this.grade = grade;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public double getGrade() {
        return grade;
    }

    @Override
    public String toString() {
        return "ID: " + id + ", Name: " + name + ", Grade: " + grade;
    }
}

public class StudentManagement {
    public static void main(String[] args) {
        LinkedList<Student> students = new LinkedList<>();

        // Adding students
        students.add(new Student(1, "Ravi", 85.5));
        students.add(new Student(2, "Priya", 78.4));
        students.add(new Student(3, "Vijay", 90.2));

        // Displaying students
        System.out.println("Students List:");
        for (Student student : students) {
            System.out.println(student);
        }

        // Removing a student
        Student priya = null;
        for (Student student : students) {
            if (student.getName().equals("Priya")) {
                priya = student;
                break;
            }
        }
        if (priya != null) {
            students.remove(priya);
            System.out.println("\nAfter removing Priya:");
            for (Student student : students) {
                System.out.println(student);
            }
        }
    }
}

Output:

Students List:
ID: 1, Name: Ravi, Grade: 85.5
ID: 2, Name: Priya, Grade: 78.4
ID: 3, Name: Vijay, Grade: 90.2

After removing Priya:
ID: 1, Name: Ravi, Grade: 85.5
ID: 3, Name: Vijay, Grade: 90.2

Explanation:

1. We defined a Student class with attributes id, name, and grade.

2. We created a LinkedList named students to store Student objects.

3. We added three student objects to the list using the add() method.

4. We displayed the students using a simple for-each loop.

5. We then showed how to find and remove a student named "Priya" from the list.

7. ArrayList vs LinkedList

Property ArrayList LinkedList
Implementation Backed by a dynamic array. Implemented as a doubly-linked list.
Resizing Automatically resized, but resizing involves array copying which can be costly. Nodes can be easily inserted or removed without resizing or reorganization.
Memory Consumption Uses less memory as it stores only data. Uses more memory due to storage of data and two pointers (next & previous) per node.
Access Time Constant-time (O(1)) for indexed retrieval due to array implementation. Linear-time (O(n)) for access as it might need to traverse the list.
Insertions/Deletions O(n) in the worst case as elements might need to be shifted. O(1) for insertion/deletion once the node is accessed, but finding the node takes O(n) in the worst case.
Performance Generally faster for indexed operations due to cache coherence and array implementation. Performance can suffer due to non-contiguous memory allocation, especially during iteration.
Use Cases Best for scenarios with more frequent get/set operations and less frequent insertions/deletions. Preferred for frequent insertions and deletions with relatively fewer get operations.

Conclusion

In this tutorial, you learned what is a LinkedList, what are the differences between a LinkedList and an ArrayList, how to create a LinkedList, how to add, remove, and search for elements in a LinkedList, how to iterate over a LinkedList, and a simple real-world example using LinkedList.