Java - Creating and Using LinkedLists

Creating and Using LinkedLists in Java

Introduction to Java LinkedList

The Java LinkedList class is one of the most widely-used data structures in the Java Collections Framework. It implements both the List interface and the Deque interface, which means it can act as a dynamic list as well as a double-ended queue. A LinkedList stores elements as a chain of nodes, where every node contains data and references to the next and previous nodes. This structural design allows efficient insertion and deletion operations compared to ArrayList, especially when working with the middle of the list. LinkedList is a powerful, flexible, and memory-dynamic data structure for performing linear operations, stack-like operations, and queue-like operations.

Unlike ArrayList, LinkedList does not store elements in contiguous memory. Instead, each element is connected to others using links, forming a bidirectional chain. Because of this, traversal might be slower, but modification operations at any position become significantly faster. This makes LinkedList ideal for applications requiring frequent add/remove operations, such as implementing queues, task schedulers, or undo mechanisms. The LinkedList class is generic and can store any type of objects, including user-defined types. Understanding its structure and behavior is essential for writing optimized and clean Java code.

Creating a LinkedList in Java

Creating a LinkedList in Java is straightforward, as the class is a part of the java.util package. You can create a LinkedList with any object type using Java Generics. The most basic way to create a LinkedList is by using the LinkedList constructor without passing any arguments. This initializes an empty list. You can also use the constructor that takes a Collection argument to initialize the LinkedList with elements from another collection. When you create a LinkedList, Java allocates memory dynamically as elements are added, allowing it to grow or shrink without the need for resizing, unlike ArrayList.


import java.util.LinkedList;

public class CreateLinkedList {
    public static void main(String[] args) {
        LinkedList list = new LinkedList<>();
        list.add("Java");
        list.add("LinkedList");
        list.add("Example");
        System.out.println(list);
    }
}

Output:


[Java, LinkedList, Example]

This example demonstrates the simplest way to create and use a LinkedList. The add() method inserts elements at the end of the list. You can also create a LinkedList of integers, objects, or even custom class types. Because LinkedList is generic, type safety is guaranteed, ensuring that only the specified data type is stored. Creating a LinkedList with initial elements is also possible by passing an ArrayList or another LinkedList to the constructor. This makes initializing large datasets easier and more flexible.

Understanding the Internal Structure of LinkedList

To appreciate how LinkedList works, it is essential to understand its internal structure. LinkedList is implemented as a doubly linked list, meaning each node contains three components: the data stored in the node, a reference to the next node, and a reference to the previous node. This structure allows bidirectional traversal and efficient insertion or deletion operations. When a new element is added, only neighboring nodes need pointer adjustments. Unlike ArrayList, LinkedList does not rely on a continuous block of memory.

This design ensures that operations such as addFirst(), addLast(), removeFirst(), and removeLast() execute in constant time, making LinkedList highly efficient for queue and deque operations. However, random access using get(index) is slower compared to ArrayList because the LinkedList must traverse nodes sequentially from either the head or the tail. Understanding this internal structure helps in making informed decisions about when to use LinkedList versus ArrayList based on the nature of your application.

Adding Elements to LinkedList

LinkedList provides multiple methods for adding elements at various positions. You can insert elements at the beginning, end, or anywhere in the middle. These operations are comparatively faster than ArrayList because LinkedList adjusts links instead of shifting elements. Common methods include add(), addFirst(), addLast(), and add(index, element). These methods give full control over how elements are organized and added. The LinkedList class also supports bulk insertion using addAll().


import java.util.LinkedList;

public class AddExample {
    public static void main(String[] args) {
        LinkedList numbers = new LinkedList<>();

        numbers.add(10);
        numbers.add(20);
        numbers.add(30);

        numbers.addFirst(5);
        numbers.addLast(40);

        numbers.add(2, 15);

        System.out.println(numbers);
    }
}

Output:


[5, 10, 15, 20, 30, 40]

The example demonstrates how adding elements at different positions impacts the structure of the LinkedList. Because the list manages node references internally, inserting elements does not require shifting existing elements. This is one of the biggest advantages of LinkedList over ArrayList. Adding a bulk collection via addAll() is also possible, making LinkedList ideal for merging datasets.

Accessing Elements in LinkedList

Accessing elements in the LinkedList is performed using a variety of methods such as get(index), getFirst(), and getLast(). Unlike ArrayList, LinkedList does not provide constant-time random access due to its non-contiguous memory structure. When you call get(index), LinkedList traverses the nodes sequentially until it reaches the desired index. Although this operation is slower than ArrayList, it is still practical for small to medium-sized lists or when frequent random access is not required.


import java.util.LinkedList;

public class AccessExample {
    public static void main(String[] args) {
        LinkedList languages = new LinkedList<>();
        languages.add("Java");
        languages.add("Python");
        languages.add("C++");
        languages.add("JavaScript");

        System.out.println(languages.get(1));
        System.out.println(languages.getFirst());
        System.out.println(languages.getLast());
    }
}

Output:


Python
Java
JavaScript

These access methods allow retrieval of elements from specific positions, from the front, or the end of the LinkedList. Although get(index) may be slower than ArrayList, getFirst() and getLast() are extremely fast because they directly access head and tail nodes. This behavior makes LinkedList excellent for queue-based systems, where accessing ends of the list is more common than accessing random positions.

Updating Elements in LinkedList

LinkedList allows updating elements at any position using the set(index, element) method. Updating an element replaces the existing value but does not alter the structure of the linked nodes. This operation requires traversal to the specified index but executes quickly once the node is reached. Updating elements is useful when modifying object values stored in the list or when adjusting data dynamically during runtimes such as sorting or real-time computations.


import java.util.LinkedList;

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

        fruits.set(1, "Mango");

        System.out.println(fruits);
    }
}

Output:


[Apple, Mango, Orange]

Updating elements does not involve shifting or restructuring, making set() an efficient operation. LinkedList maintains the stability of node connections, and only the data stored inside a node is replaced. This behavior is ideal for updating values in large data streams or lists that are frequently modified.

Removing Elements from LinkedList

Removing elements from a LinkedList is one of the most efficient operations due to its node-based structure. Unlike ArrayList, which must shift remaining elements after removal, LinkedList adjusts node references, making removal very fast. You can remove elements using remove(), removeFirst(), removeLast(), and remove(index). Additionally, LinkedList supports removing elements based on value, making it flexible for dynamic list modifications.


import java.util.LinkedList;

public class RemoveExample {
    public static void main(String[] args) {
        LinkedList cities = new LinkedList<>();
        cities.add("Delhi");
        cities.add("Mumbai");
        cities.add("Chennai");
        cities.add("Kolkata");

        cities.remove(2);
        cities.removeFirst();
        cities.remove("Kolkata");

        System.out.println(cities);
    }
}

Output:


[Mumbai]

Each remove operation updates the links between nodes, avoiding memory shifting. This makes LinkedList highly efficient in cases where deletion happens frequently. The remove(value) method searches for the first matching element and removes it, which is useful when working with non-indexed operations.

Iterating Through a LinkedList

Iterating through a LinkedList is commonly performed using loops such as for-each, iterator, or listIterator. Although traversal is slower compared to ArrayList due to its node-based structure, LinkedList still supports efficient iteration for most real-world applications. The enhanced for-loop is the simplest way to iterate, while iterator() and descendingIterator() provide more control.


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

public class IterateExample {
    public static void main(String[] args) {
        LinkedList animals = new LinkedList<>();
        animals.add("Dog");
        animals.add("Cat");
        animals.add("Horse");

        for (String a : animals) {
            System.out.println(a);
        }

        Iterator itr = animals.iterator();
        while (itr.hasNext()) {
            System.out.println(itr.next());
        }
    }
}

Output:


Dog
Cat
Horse
Dog
Cat
Horse

Iterating is commonly used for displaying elements, processing data, or implementing algorithms. The iterator pattern provides flexibility, especially when removing elements during traversal.

LinkedList as Queue and Deque

Since LinkedList implements both the Queue and Deque interfaces, it supports methods such as offer(), poll(), peek(), push(), and pop(). This allows it to act as a queue (FIFO) or stack (LIFO). These features make LinkedList extremely versatile for simulations, scheduling algorithms, expression evaluations, and more.


import java.util.LinkedList;

public class QueueStackExample {
    public static void main(String[] args) {
        LinkedList queue = new LinkedList<>();

        queue.offer(10);
        queue.offer(20);
        queue.offer(30);

        System.out.println(queue.poll());
        System.out.println(queue.peek());

        queue.push(5);
        System.out.println(queue);
    }
}

Output:


10
20
[5, 20, 30]

These operations help implement complex workflows easily. poll() retrieves and removes the head, while peek() retrieves without removing. push() inserts at the front, making LinkedList suitable for stack operations. This dual behavior is one of the biggest advantages of LinkedList.

Advantages of LinkedList

LinkedList offers many benefits: Fast insertion and deletion, dynamic memory allocation, efficient queue and deque implementation, and flexible node-based structure. It is ideal for applications that require frequent element modifications. LinkedList also avoids resizing overhead and supports both forward and backward traversal.

Disadvantages of LinkedList

Despite advantages, LinkedList is slower in random access operations. It consumes more memory per element due to node references. Traversal takes more time than ArrayList. It is not ideal when fast index-based access is required.


LinkedList in Java is a powerful and versatile data structure suitable for dynamic data operations. It excels in insertion, deletion, and queue-based workflows. Understanding when and where to use LinkedList helps developers write optimized, efficient, and production-ready code.

logo

Java

Beginner 5 Hours

Creating and Using LinkedLists in Java

Introduction to Java LinkedList

The Java LinkedList class is one of the most widely-used data structures in the Java Collections Framework. It implements both the List interface and the Deque interface, which means it can act as a dynamic list as well as a double-ended queue. A LinkedList stores elements as a chain of nodes, where every node contains data and references to the next and previous nodes. This structural design allows efficient insertion and deletion operations compared to ArrayList, especially when working with the middle of the list. LinkedList is a powerful, flexible, and memory-dynamic data structure for performing linear operations, stack-like operations, and queue-like operations.

Unlike ArrayList, LinkedList does not store elements in contiguous memory. Instead, each element is connected to others using links, forming a bidirectional chain. Because of this, traversal might be slower, but modification operations at any position become significantly faster. This makes LinkedList ideal for applications requiring frequent add/remove operations, such as implementing queues, task schedulers, or undo mechanisms. The LinkedList class is generic and can store any type of objects, including user-defined types. Understanding its structure and behavior is essential for writing optimized and clean Java code.

Creating a LinkedList in Java

Creating a LinkedList in Java is straightforward, as the class is a part of the java.util package. You can create a LinkedList with any object type using Java Generics. The most basic way to create a LinkedList is by using the LinkedList constructor without passing any arguments. This initializes an empty list. You can also use the constructor that takes a Collection argument to initialize the LinkedList with elements from another collection. When you create a LinkedList, Java allocates memory dynamically as elements are added, allowing it to grow or shrink without the need for resizing, unlike ArrayList.

import java.util.LinkedList; public class CreateLinkedList { public static void main(String[] args) { LinkedList list = new LinkedList<>(); list.add("Java"); list.add("LinkedList"); list.add("Example"); System.out.println(list); } }

Output:

[Java, LinkedList, Example]

This example demonstrates the simplest way to create and use a LinkedList. The add() method inserts elements at the end of the list. You can also create a LinkedList of integers, objects, or even custom class types. Because LinkedList is generic, type safety is guaranteed, ensuring that only the specified data type is stored. Creating a LinkedList with initial elements is also possible by passing an ArrayList or another LinkedList to the constructor. This makes initializing large datasets easier and more flexible.

Understanding the Internal Structure of LinkedList

To appreciate how LinkedList works, it is essential to understand its internal structure. LinkedList is implemented as a doubly linked list, meaning each node contains three components: the data stored in the node, a reference to the next node, and a reference to the previous node. This structure allows bidirectional traversal and efficient insertion or deletion operations. When a new element is added, only neighboring nodes need pointer adjustments. Unlike ArrayList, LinkedList does not rely on a continuous block of memory.

This design ensures that operations such as addFirst(), addLast(), removeFirst(), and removeLast() execute in constant time, making LinkedList highly efficient for queue and deque operations. However, random access using get(index) is slower compared to ArrayList because the LinkedList must traverse nodes sequentially from either the head or the tail. Understanding this internal structure helps in making informed decisions about when to use LinkedList versus ArrayList based on the nature of your application.

Adding Elements to LinkedList

LinkedList provides multiple methods for adding elements at various positions. You can insert elements at the beginning, end, or anywhere in the middle. These operations are comparatively faster than ArrayList because LinkedList adjusts links instead of shifting elements. Common methods include add(), addFirst(), addLast(), and add(index, element). These methods give full control over how elements are organized and added. The LinkedList class also supports bulk insertion using addAll().

import java.util.LinkedList; public class AddExample { public static void main(String[] args) { LinkedList numbers = new LinkedList<>(); numbers.add(10); numbers.add(20); numbers.add(30); numbers.addFirst(5); numbers.addLast(40); numbers.add(2, 15); System.out.println(numbers); } }

Output:

[5, 10, 15, 20, 30, 40]

The example demonstrates how adding elements at different positions impacts the structure of the LinkedList. Because the list manages node references internally, inserting elements does not require shifting existing elements. This is one of the biggest advantages of LinkedList over ArrayList. Adding a bulk collection via addAll() is also possible, making LinkedList ideal for merging datasets.

Accessing Elements in LinkedList

Accessing elements in the LinkedList is performed using a variety of methods such as get(index), getFirst(), and getLast(). Unlike ArrayList, LinkedList does not provide constant-time random access due to its non-contiguous memory structure. When you call get(index), LinkedList traverses the nodes sequentially until it reaches the desired index. Although this operation is slower than ArrayList, it is still practical for small to medium-sized lists or when frequent random access is not required.

import java.util.LinkedList; public class AccessExample { public static void main(String[] args) { LinkedList languages = new LinkedList<>(); languages.add("Java"); languages.add("Python"); languages.add("C++"); languages.add("JavaScript"); System.out.println(languages.get(1)); System.out.println(languages.getFirst()); System.out.println(languages.getLast()); } }

Output:

Python Java JavaScript

These access methods allow retrieval of elements from specific positions, from the front, or the end of the LinkedList. Although get(index) may be slower than ArrayList, getFirst() and getLast() are extremely fast because they directly access head and tail nodes. This behavior makes LinkedList excellent for queue-based systems, where accessing ends of the list is more common than accessing random positions.

Updating Elements in LinkedList

LinkedList allows updating elements at any position using the set(index, element) method. Updating an element replaces the existing value but does not alter the structure of the linked nodes. This operation requires traversal to the specified index but executes quickly once the node is reached. Updating elements is useful when modifying object values stored in the list or when adjusting data dynamically during runtimes such as sorting or real-time computations.

import java.util.LinkedList; public class UpdateExample { public static void main(String[] args) { LinkedList fruits = new LinkedList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.add("Orange"); fruits.set(1, "Mango"); System.out.println(fruits); } }

Output:

[Apple, Mango, Orange]

Updating elements does not involve shifting or restructuring, making set() an efficient operation. LinkedList maintains the stability of node connections, and only the data stored inside a node is replaced. This behavior is ideal for updating values in large data streams or lists that are frequently modified.

Removing Elements from LinkedList

Removing elements from a LinkedList is one of the most efficient operations due to its node-based structure. Unlike ArrayList, which must shift remaining elements after removal, LinkedList adjusts node references, making removal very fast. You can remove elements using remove(), removeFirst(), removeLast(), and remove(index). Additionally, LinkedList supports removing elements based on value, making it flexible for dynamic list modifications.

import java.util.LinkedList; public class RemoveExample { public static void main(String[] args) { LinkedList cities = new LinkedList<>(); cities.add("Delhi"); cities.add("Mumbai"); cities.add("Chennai"); cities.add("Kolkata"); cities.remove(2); cities.removeFirst(); cities.remove("Kolkata"); System.out.println(cities); } }

Output:

[Mumbai]

Each remove operation updates the links between nodes, avoiding memory shifting. This makes LinkedList highly efficient in cases where deletion happens frequently. The remove(value) method searches for the first matching element and removes it, which is useful when working with non-indexed operations.

Iterating Through a LinkedList

Iterating through a LinkedList is commonly performed using loops such as for-each, iterator, or listIterator. Although traversal is slower compared to ArrayList due to its node-based structure, LinkedList still supports efficient iteration for most real-world applications. The enhanced for-loop is the simplest way to iterate, while iterator() and descendingIterator() provide more control.

import java.util.LinkedList; import java.util.Iterator; public class IterateExample { public static void main(String[] args) { LinkedList animals = new LinkedList<>(); animals.add("Dog"); animals.add("Cat"); animals.add("Horse"); for (String a : animals) { System.out.println(a); } Iterator itr = animals.iterator(); while (itr.hasNext()) { System.out.println(itr.next()); } } }

Output:

Dog Cat Horse Dog Cat Horse

Iterating is commonly used for displaying elements, processing data, or implementing algorithms. The iterator pattern provides flexibility, especially when removing elements during traversal.

LinkedList as Queue and Deque

Since LinkedList implements both the Queue and Deque interfaces, it supports methods such as offer(), poll(), peek(), push(), and pop(). This allows it to act as a queue (FIFO) or stack (LIFO). These features make LinkedList extremely versatile for simulations, scheduling algorithms, expression evaluations, and more.

import java.util.LinkedList; public class QueueStackExample { public static void main(String[] args) { LinkedList queue = new LinkedList<>(); queue.offer(10); queue.offer(20); queue.offer(30); System.out.println(queue.poll()); System.out.println(queue.peek()); queue.push(5); System.out.println(queue); } }

Output:

10 20 [5, 20, 30]

These operations help implement complex workflows easily. poll() retrieves and removes the head, while peek() retrieves without removing. push() inserts at the front, making LinkedList suitable for stack operations. This dual behavior is one of the biggest advantages of LinkedList.

Advantages of LinkedList

LinkedList offers many benefits: Fast insertion and deletion, dynamic memory allocation, efficient queue and deque implementation, and flexible node-based structure. It is ideal for applications that require frequent element modifications. LinkedList also avoids resizing overhead and supports both forward and backward traversal.

Disadvantages of LinkedList

Despite advantages, LinkedList is slower in random access operations. It consumes more memory per element due to node references. Traversal takes more time than ArrayList. It is not ideal when fast index-based access is required.


LinkedList in Java is a powerful and versatile data structure suitable for dynamic data operations. It excels in insertion, deletion, and queue-based workflows. Understanding when and where to use LinkedList helps developers write optimized, efficient, and production-ready code.

Related Tutorials

Frequently Asked Questions for Java

Java is known for its key features such as object-oriented programming, platform independence, robust exception handling, multithreading capabilities, and automatic garbage collection.

The Java Development Kit (JDK) is a software development kit used to develop Java applications. The Java Runtime Environment (JRE) provides libraries and other resources to run Java applications, while the Java Virtual Machine (JVM) executes Java bytecode.

Java is a high-level, object-oriented programming language known for its platform independence. This means that Java programs can run on any device that has a Java Virtual Machine (JVM) installed, making it versatile across different operating systems.

Deadlock is a situation in multithreading where two or more threads are blocked forever, waiting for each other to release resources.

Functional programming in Java involves writing code using functions, immutability, and higher-order functions, often utilizing features introduced in Java 8.

A process is an independent program in execution, while a thread is a lightweight subprocess that shares resources with other threads within the same process.

The Comparable interface defines a natural ordering for objects, while the Comparator interface defines an external ordering.

The List interface allows duplicate elements and maintains the order of insertion, while the Set interface does not allow duplicates and does not guarantee any specific order.

String is immutable, meaning its value cannot be changed after creation. StringBuffer and StringBuilder are mutable, allowing modifications to their contents. The main difference between them is that StringBuffer is synchronized, making it thread-safe, while StringBuilder is not.

Checked exceptions are exceptions that must be either caught or declared in the method signature, while unchecked exceptions do not require explicit handling.

ArrayList is backed by a dynamic array, providing fast random access but slower insertions and deletions. LinkedList is backed by a doubly-linked list, offering faster insertions and deletions but slower random access.

Autoboxing is the automatic conversion between primitive types and their corresponding wrapper classes. For example, converting an int to Integer.

The 'synchronized' keyword in Java is used to control access to a method or block of code by multiple threads, ensuring that only one thread can execute it at a time.

Multithreading in Java allows concurrent execution of two or more threads, enabling efficient CPU utilization and improved application performance.

A HashMap is a collection class that implements the Map interface, storing key-value pairs. It allows null values and keys and provides constant-time performance for basic operations.

Java achieves platform independence by compiling source code into bytecode, which is executed by the JVM. This allows Java programs to run on any platform that has a compatible JVM.

The Serializable interface provides a default mechanism for serialization, while the Externalizable interface allows for custom serialization behavior.

The 'volatile' keyword in Java indicates that a variable's value will be modified by multiple threads, ensuring that the most up-to-date value is always visible.

Serialization is the process of converting an object into a byte stream, enabling it to be saved to a file or transmitted over a network.

The finalize() method is called by the garbage collector before an object is destroyed, allowing for cleanup operations.

The 'final' keyword in Java is used to define constants, prevent method overriding, and prevent inheritance of classes, ensuring that certain elements remain unchanged.

Garbage collection is the process by which the JVM automatically deletes objects that are no longer reachable, freeing up memory resources.

'throw' is used to explicitly throw an exception, while 'throws' is used in method declarations to specify that a method can throw one or more exceptions.

The 'super' keyword in Java refers to the immediate parent class and is used to access parent class methods, constructors, and variables.

The JVM is responsible for loading, verifying, and executing Java bytecode. It provides an abstraction between the compiled Java program and the underlying hardware, enabling platform independence.

line

Copyrights © 2024 letsupdateskills All rights reserved