I'm always excited to take on new projects and collaborate with innovative minds.
contact@niteshsynergy.com
https://www.niteshsynergy.com/
Java Collections Framework is a unified architecture for managing groups of objects. It simplifies programming by providing data structures and algorithms for collections, making them easier to use and manipulate.
Java Collections Framework provides a set of interfaces and classes for working with collections.
add()
, remove()
, size()
, iterator()
, etc.ArrayList
, LinkedList
, Vector
HashSet
, LinkedHashSet
, TreeSet
PriorityQueue
, ArrayDeque
HashMap
, LinkedHashMap
, TreeMap
, Hashtable
Collection Type | Characteristics | Common Classes |
---|---|---|
List | Ordered, duplicates allowed | ArrayList , LinkedList , Vector |
Set | No duplicates, unordered | HashSet , LinkedHashSet , TreeSet |
Queue | FIFO or priority order | PriorityQueue , ArrayDeque |
Map | Key-value pairs | HashMap , TreeMap , LinkedHashMap |
add(element)
, addAll(collection)
remove(element)
, clear()
get(index)
get(key)
Iterator
forEach
loopstream().forEach()
)get()
method).add()
/remove()
operations in the middle.HashMap
.HashSet
due to sorting.HashMap
.List<String> list = new ArrayList<>();
Collections.synchronizedList()
or ConcurrentHashMap
.list.stream().filter(e -> e.startsWith("A")).collect(Collectors.toList());
Feature | ArrayList | LinkedList | HashSet | TreeSet |
---|---|---|---|---|
Ordering | Yes | Yes | No | Yes (sorted) |
Duplicates | Yes | Yes | No | No |
Access Speed | Fast (index) | Slow (sequential) | Fast (hash) | Slow (tree-based) |
ConcurrentModificationException
if the collection is modified while iterating.CopyOnWriteArrayList
allow modification during iteration.ArrayList
when frequent random access is needed.HashSet
for unique, unordered collections.ConcurrentHashMap
over Hashtable
.TreeMap
or TreeSet
for sorted data.
Lets start each one
Collection
InterfaceThe Collection
interface in Java is part of the Java Collections Framework and represents a group of objects known as elements. It is the root interface of the framework, and all the collection types (e.g., List
, Set
, Queue
) extend this interface, allowing them to share common methods and behaviors.
Collection
Interface:Collection
interface uses generics to define the type of elements it can store, e.g., Collection<T>
, where T
is the type of the elements.ArrayList
, HashSet
, etc.Set
, List
, Queue
, etc.Collection
InterfaceHere are the core methods provided by the Collection
interface, along with their use cases:
boolean add(E e)
List<String> names = new ArrayList<>(); names.add("John");
boolean remove(Object o)
names.remove("John");
boolean contains(Object o)
true
if the collection contains the specified element.boolean exists = names.contains("John");
boolean isEmpty()
true
if the collection is empty (contains no elements).boolean empty = names.isEmpty();
int size()
int count = names.size();
void clear()
names.clear();
Object[] toArray()
Object[] arr = names.toArray();
boolean containsAll(Collection<?> c)
true
if the collection contains all elements of the specified collection.boolean hasAll = names.containsAll(otherList);
boolean addAll(Collection<? extends E> c)
names.addAll(otherList);
boolean removeAll(Collection<?> c)
names.removeAll(otherList);
boolean retainAll(Collection<?> c)
names.retainAll(otherList);
Iterator<E> iterator()
Iterator<String> it = names.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
Collection
is Given as an Interface:Collection
as an interface, Java allows different types of collections (like List
, Set
, and Queue
) to implement the same set of methods. This makes it possible to write generic code that works with any collection type.ArrayList
or HashSet
). You can change the underlying implementation without changing the code that uses the collection, making it more maintainable and flexible.Checking size and emptiness:
System.out.println("Size: " + names.size());
System.out.println("Is empty: " + names.isEmpty());
Removing elements:
names.remove("Bob");
Clearing all elements:
names.clear();
Iterating over the collection:
for (String name : names) {
System.out.println(name);
}
What is output of this program ?
See & tell me why this CE error. As we return type of toArray() method is Object[] right.
Next →
ArrayList
in Java (Short Definition)ArrayList
is a part of the Java Collections Framework and implements the List
interface. It is a resizable array implementation, meaning the size of the array can grow as needed to accommodate new elements. It allows duplicate elements and maintains the insertion order.
ArrayList
ArrayList
is best suited when you need:
ArrayList
is efficient.
ArrayList
(Without Generics & With Generics)import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// Creating ArrayList without generics
ArrayList list = new ArrayList();
list.add("Apple");
list.add(100);
list.add(5.5);
// Retrieving elements
for (Object element : list) {
System.out.println(element);
}
}
}
import java.util.ArrayList;
public class ArrayListExample {
public static void main(String[] args) {
// Creating ArrayList with generics
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// Retrieving elements
for (String element : list) {
System.out.println(element);
}
}
}
ArrayList
OperationsArrayList
internally, so it takes constant time to retrieve.ArrayList
package com.niteshsynergy.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
public class Demo02AL {
public static void main(String[] args) {
// Using generics to avoid raw type warnings
ArrayList<String> list = new ArrayList<>();
// Adding elements to the ArrayList
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.add("F");
list.add("G");
list.add("H");
list.add("I");
// Printing the ArrayList
System.out.println(list);
// 8 ways to retrieve elements from an ArrayList:
// 1. Using the get() method:
System.out.println("Using get method...");
System.out.println(list.get(1)); // Access element at index 1
// 2. Using an iterator:
System.out.println("Using Iterator...");
Iterator<String> iterator = list.iterator(); // Using generics
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// Time Complexity: O(n) - iterating through all elements
// 3. Using enhanced for loop (foreach):
System.out.println("Using enhanced for loop (foreach)...");
for (String element : list) { // No need to cast as it's a generic list
System.out.println(element);
}
// Time Complexity: O(n)
// 4. Using a ListIterator (for bi-directional iteration):
System.out.println("Using ListIterator...");
ListIterator<String> listIterator = list.listIterator(); // Using generics
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}
// Time Complexity: O(n)
// 5. Using the stream() method:
System.out.println("Using stream() method...");
list.stream().forEach(System.out::println);
// Time Complexity: O(n)
// 6. Using the forEach() method (Java 8+):
System.out.println("Using forEach() method...");
list.forEach(System.out::println);
// Time Complexity: O(n)
// 7. Using the toArray() method (convert to array):
System.out.println("Using toArray() method...");
Object[] array = list.toArray();
for (Object element : array) {
System.out.println(element);
}
// Time Complexity: O(n) for conversion and iteration
// 8. Using the for loop with index:
System.out.println("Using for loop with index...");
for (int i = 0; i < list.size(); i++) { // Corrected index range
System.out.println(list.get(i));
}
// Time Complexity: O(n) for iteration
}
}
The various ways to access elements from an ArrayList
in Java offer different trade-offs in terms of flexibility, performance, and convenience. Below, I’ll explain why each of these methods exists and when they might be preferable:
get()
methodget()
method provides direct access to an element by its index. It’s efficient when you need to retrieve an element at a specific position quickly.Iterator
interface)Iterator
provides a way to iterate over the ArrayList
without directly using indices. It allows you to traverse the collection in a more abstract way, particularly useful when you don't need to know the index of the current element.Iterator.remove()
).for
loop (foreach)for
loop provides a simple and readable way to iterate through all elements in the ArrayList
. It’s cleaner than using an explicit iterator in simple use cases.ListIterator
extends Iterator
and allows traversal of the list in both forward and backward directions. It also allows modification of the list during iteration, making it more flexible than a standard iterator.ListIterator
provides extra functionality compared to a basic iterator.stream()
stream()
method in Java 8+ provides a functional programming approach to handle elements in a collection. It allows you to chain operations like filtering, mapping, or collecting elements in a fluent style.for
loop.forEach()
method (Java 8+)forEach()
is a default method in Iterable
that simplifies iteration. It allows you to pass a lambda expression or method reference to perform an action on each element of the ArrayList
.stream()
, it's a modern approach to iteration but involves lambda expressions, which can have a slight performance overhead compared to using for
loops or Iterator
.toArray()
methodtoArray()
converts the ArrayList
to a plain Java array, which can be useful if you need to work with a standard array or perform operations that are easier with arrays (like accessing elements via indices in a non-List
context).for
loop with indexfor
loop with an index is the traditional way of iterating over a collection by directly accessing elements via their index. It's very familiar and useful when you need to perform specific operations based on the index, such as comparing elements at different positions.ListIterator
allows backward iteration, while toArray()
allows you to convert to an array for external processing.forEach()
and stream()
provide a functional, declarative approach that may be easier to read, especially in complex transformations.get()
, for
loop) or processing elements functionally (stream()
, forEach()
).get()
) is faster than iterating through the collection when you know the position of the element.stream()
), which can make maintenance easier in complex applications.Fail-Safe vs. Fail-Fast Iterators
Fail-Fast:
ConcurrentModificationException
if the collection is modified while it is being iterated over (except through the iterator itself).ArrayList
uses fail-fast iterators.Example of Fail-Fast:
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
Iterator<String> it = list.iterator();
list.add("Banana");
it.next(); // Throws ConcurrentModificationException
Fail-Safe:
CopyOnWriteArrayList
, which create a copy of the collection for iteration, ensuring that the original collection remains unchanged during iteration.Example of Fail-Safe
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Apple");
Iterator<String> it = list.iterator();
list.add("Banana"); // No ConcurrentModificationException
it.next();
If you want to avoid ConcurrentModificationException
, you have two options:
Use an explicit synchronization mechanism like synchronized
blocks to ensure no modification happens while iterating.
Use a CopyOnWriteArrayList
if you expect modifications while iterating, as it supports fail-safe iterators.
Example of Synchronization:
synchronized(list) {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
Here’s a concise, tabular format explaining the insertion order, duplicates, and time complexities for ArrayList
operations in Java:
Property | Description | Time Complexity |
---|---|---|
Insertion Order | ArrayList maintains the order of insertion. The elements are retrieved in the same sequence as they were added. | O(1) for adding an element at the end of the list (amortized). |
Duplicates | ArrayList allows duplicate elements. You can add the same element multiple times. | O(1) for adding a duplicate at the end of the list. |
Accessing by Index | You can access any element by its index. This operation is fast because it is backed by an array. | O(1) (constant time). |
Adding an Element | Adds an element at the end of the list. If capacity is exceeded, a new array is created. | O(1) amortized for most cases, but O(n) if resizing occurs (rare). |
Inserting at Index | Inserts an element at a specific index. All subsequent elements are shifted to the right. | O(n) in the worst case (when inserting at the beginning). |
Removing by Index | Removes an element at a specific index, and shifts subsequent elements to the left. | O(n) in the worst case (when removing from the beginning). |
Removing by Object | Removes the first occurrence of the specified element, shifting subsequent elements. | O(n) because it needs to search for the element. |
Iterating (Looping) | Iterating through the list with an iterator or enhanced for loop preserves the insertion order. | O(n) for iterating over all elements. |
ArrayList
is maintained when accessed or iterated over.ArrayList
allows duplicates. The same element can appear multiple times in the list.add()
: Most of the time, adding an element to the end of the list is O(1). However, if the underlying array is full and needs resizing, it takes O(n) because a new array is created and all elements are copied over.get()
: Accessing an element by index is a constant-time operation, i.e., O(1).
For a real-time project, using an ArrayList
is common in scenarios that require maintaining insertion order and where you can have duplicates. A complex example could be a task management system that handles the following:
Imagine you're building a task management system for a project. In this system:
ArrayList
to store tasks.import java.util.Date;
public class Task {
private int taskId;
private String taskName;
private Date deadline;
private int priority; // 1 = High, 2 = Medium, 3 = Low
private String assignee;
public Task(int taskId, String taskName, Date deadline, int priority, String assignee) {
this.taskId = taskId;
this.taskName = taskName;
this.deadline = deadline;
this.priority = priority;
this.assignee = assignee;
}
// Getters and Setters
public int getTaskId() { return taskId; }
public String getTaskName() { return taskName; }
public Date getDeadline() { return deadline; }
public int getPriority() { return priority; }
public String getAssignee() { return assignee; }
@Override
public String toString() {
return "Task{" +
"taskId=" + taskId +
", taskName='" + taskName + '\'' +
", deadline=" + deadline +
", priority=" + priority +
", assignee='" + assignee + '\'' +
'}';
}
}
Task Manager Class (Using ArrayList
):
import java.util.ArrayList;
import java.util.List;
import java.util.Date;
public class TaskManager {
private List<Task> taskList;
public TaskManager() {
taskList = new ArrayList<>();
}
// Add a task
public void addTask(Task task) {
taskList.add(task);
}
// Remove a task by taskId
public boolean removeTaskById(int taskId) {
for (Task task : taskList) {
if (task.getTaskId() == taskId) {
taskList.remove(task);
return true;
}
}
return false;
}
// Search task by priority
public List<Task> getTasksByPriority(int priority) {
List<Task> result = new ArrayList<>();
for (Task task : taskList) {
if (task.getPriority() == priority) {
result.add(task);
}
}
return result;
}
// Update task deadline
public void updateTaskDeadline(int taskId, Date newDeadline) {
for (Task task : taskList) {
if (task.getTaskId() == taskId) {
task.setDeadline(newDeadline);
break;
}
}
}
// Get all tasks assigned to a specific assignee
public List<Task> getTasksForAssignee(String assignee) {
List<Task> result = new ArrayList<>();
for (Task task : taskList) {
if (task.getAssignee().equals(assignee)) {
result.add(task);
}
}
return result;
}
// Display all tasks
public void displayTasks() {
for (Task task : taskList) {
System.out.println(task);
}
}
}
Main Class (Running the Task Manager):
import java.util.Date;
public class TaskManagerApp {
public static void main(String[] args) {
// Creating TaskManager instance
TaskManager taskManager = new TaskManager();
// Adding tasks to the manager
taskManager.addTask(new Task(1, "Design Database", new Date(), 1, "John"));
taskManager.addTask(new Task(2, "Develop API", new Date(), 2, "Sarah"));
taskManager.addTask(new Task(3, "Create UI", new Date(), 3, "Michael"));
taskManager.addTask(new Task(4, "Test Application", new Date(), 1, "John"));
// Display all tasks
System.out.println("All Tasks:");
taskManager.displayTasks();
// Remove a task
taskManager.removeTaskById(2);
// Display tasks after removal
System.out.println("\nTasks After Removal:");
taskManager.displayTasks();
// Get tasks with high priority
System.out.println("\nHigh Priority Tasks:");
taskManager.getTasksByPriority(1).forEach(System.out::println);
// Update task deadline
taskManager.updateTaskDeadline(3, new Date(System.currentTimeMillis() + 86400000)); // 1 day later
// Display updated task
System.out.println("\nUpdated Task 3:");
taskManager.displayTasks();
// Get tasks for a specific assignee
System.out.println("\nTasks assigned to John:");
taskManager.getTasksForAssignee("John").forEach(System.out::println);
}
}
The ArrayList<E>
class in Java is an implementation of the List<E>
interface, and it extends AbstractList<E>
, implementing several other interfaces like RandomAccess
, Cloneable
, and Serializable
. Let’s break down the purpose of each of these interfaces and how they benefit the ArrayList
class.
List<E>
InterfaceArrayList
implements the List<E>
interface, which defines methods to manage a collection of elements in a specific order.ArrayList
follows the List
contract.RandomAccess
InterfaceRandomAccess
interface is a marker interface that indicates that the list supports fast, constant-time access to elements by index.ArrayList
:ArrayList
provides fast O(1) time complexity for element access via the get(int index)
and set(int index, E element)
methods. This is because it is backed by a dynamic array.RandomAccess
, algorithms or utilities can take advantage of this feature to optimize performance. For example, methods like Collections.sort()
can optimize their sorting algorithms for RandomAccess
collections.ArrayList
didn’t implement RandomAccess
, performance might degrade when iterating over large lists or when trying to access elements directly by index, compared to an implementation like LinkedList
, which doesn't offer fast random access.Cloneable
InterfaceCloneable
interface allows for creating a copy of the ArrayList
using the clone()
method.ArrayList
:Cloneable
, the ArrayList
can provide a shallow copy of the list (i.e., copying the array but not the individual objects themselves).ArrayList<E> clone = (ArrayList<E>) originalList.clone();
ArrayList
: It makes it easier to duplicate the list if needed for parallel processing, backup purposes, or when working with independent collections that share the same elements initially.Serializable
InterfaceSerializable
interface marks the ArrayList
as serializable, meaning instances of ArrayList
can be converted into a byte stream, and this byte stream can be stored (e.g., in a file) or transmitted over a network.ArrayList
:ArrayList
and save its state to a file or send it over a network. When deserialized, the list can be restored to its original form.FileOutputStream fos = new FileOutputStream("list.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(myArrayList); // Serializing
Why it helps ArrayList
: Serialization is key in systems where objects need to be saved or transmitted. By implementing Serializable
, ArrayList
can be part of these systems.
ArrayList
Implement These Interfaces?Efficiency and Versatility:
RandomAccess
ensures that ArrayList
can handle indexing efficiently, which is a key operation in many algorithms.Cloneable
makes it easier to duplicate the list when needed.Serializable
enables storing and transferring the list's state.These features are meant to make ArrayList
a general-purpose, efficient, and flexible data structure, capable of performing a wide variety of tasks while being easy to use in complex systems where performance, duplication, or communication across JVMs is necessary.
ArrayList
not only provides the core functionality expected from a list (such as element management) but also improves its utility in real-world applications, offering performance optimizations (via RandomAccess
), making it easy to duplicate (via Cloneable
), and ensuring compatibility with systems that need to store or transmit objects (via Serializable
). These features align with common use cases like object persistence, network communication, and performance-critical applications.Here’s a real-time code example demonstrating how Cloneable
helps ArrayList
by allowing you to create a shallow copy of an ArrayList
. This can be useful when you want to create a new list that has the same elements as the original but without modifying the original list.
Imagine you're building a shopping cart system for an e-commerce website. You want to clone the items in a cart before applying a discount or modifying some of the items, so you don't affect the original cart.
import java.util.ArrayList;
class Product {
String name;
double price;
// Constructor
public Product(String name, double price) {
this.name = name;
this.price = price;
}
// Method to display product info
public void display() {
System.out.println("Product: " + name + ", Price: " + price);
}
}
public class ShoppingCart {
public static void main(String[] args) {
// Create an ArrayList to hold products in a shopping cart
ArrayList<Product> cart = new ArrayList<>();
cart.add(new Product("Laptop", 1000.00));
cart.add(new Product("Phone", 500.00));
cart.add(new Product("Headphones", 150.00));
// Display the original cart
System.out.println("Original Cart:");
for (Product product : cart) {
product.display();
}
// Clone the cart using the clone() method
ArrayList<Product> clonedCart = (ArrayList<Product>) cart.clone();
// Modify the cloned cart (apply a discount on the cloned cart)
clonedCart.get(0).price = 900.00; // Discount applied on the Laptop
// Display the modified cloned cart
System.out.println("\nCloned Cart (after applying discount):");
for (Product product : clonedCart) {
product.display();
}
// Display the original cart again to show that it was not affected
System.out.println("\nOriginal Cart after modification:");
for (Product product : cart) {
product.display();
}
}
}
Product
class representing an item in the cart with a name and price.ShoppingCart
class, we create an ArrayList<Product>
to represent the cart.cart.clone()
method is used to clone the original shopping cart. This creates a new list with the same elements.clonedCart.get(0).price = 900.00
), but since it's a shallow copy, the original cart remains unaffected.clone()
creates a shallow copy, meaning the Product
objects themselves are not duplicated; the references to the same Product
objects are copied. Therefore, changes to the properties of the objects inside the clonedCart
will not affect the original cart
object.
Vector
is a legacy class in Java that implements the List interface, just like ArrayList
. It is part of the java.util
package and was introduced before ArrayList
.ArrayList
is not synchronized by default, Vector
is synchronized, making it thread-safe (but at the cost of performance).Vector
grows dynamically as needed. By default, it increases its size by doubling the capacity when it runs out of space.ArrayList
.Vector
are synchronized by default, which ensures thread safety but can cause performance overhead in single-threaded scenarios.Operation | Time Complexity |
---|---|
Add an element (at the end) | O(1) (amortized), but O(n) when resizing occurs. |
Add an element (at any index) | O(n) (due to shifting elements) |
Get an element by index | O(1) |
Remove an element by index | O(n) (due to shifting elements) |
Remove an element by object | O(n) (due to searching) |
Iterating | O(n) |
9 ways to retrieve elements from a Vector:
package com.niteshsynergy.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Vector;
import java.util.Enumeration;
public class Demo02Vector {
public static void main(String[] args) {
// Using generics for type safety
Vector<String> vector = new Vector<>();
// Adding elements to the Vector
vector.add("A");
vector.add("B");
vector.add("C");
vector.add("D");
vector.add("E");
vector.add("F");
vector.add("G");
vector.add("H");
vector.add("I");
// Printing the Vector
System.out.println("Vector elements: " + vector);
// 9 ways to retrieve elements from a Vector:
// 1. Using the get() method:
System.out.println("Using get method...");
System.out.println(vector.get(1)); // Access element at index 1
// 2. Using an iterator:
System.out.println("Using Iterator...");
Iterator<String> iterator = vector.iterator(); // Using generics
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// Time Complexity: O(n)
// 3. Using enhanced for loop (foreach):
System.out.println("Using enhanced for loop (foreach)...");
for (String element : vector) { // No need to cast as it's a generic list
System.out.println(element);
}
// Time Complexity: O(n)
// 4. Using a ListIterator (for bi-directional iteration):
System.out.println("Using ListIterator...");
ListIterator<String> listIterator = vector.listIterator(); // Using generics
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}
// Time Complexity: O(n)
// 5. Using the stream() method:
System.out.println("Using stream() method...");
vector.stream().forEach(System.out::println);
// Time Complexity: O(n)
// 6. Using the forEach() method (Java 8+):
System.out.println("Using forEach() method...");
vector.forEach(System.out::println);
// Time Complexity: O(n)
// 7. Using the toArray() method (convert to array):
System.out.println("Using toArray() method...");
Object[] array = vector.toArray();
for (Object element : array) {
System.out.println(element);
}
// Time Complexity: O(n) for conversion and iteration
// 8. Using the for loop with index:
System.out.println("Using for loop with index...");
for (int i = 0; i < vector.size(); i++) {
System.out.println(vector.get(i));
}
// Time Complexity: O(n)
// 9. Using Enumeration (specific to Vector):
System.out.println("Using Enumeration...");
Enumeration<String> enumeration = vector.elements(); // Vector's specific method to get Enumeration
while (enumeration.hasMoreElements()) {
System.out.println(enumeration.nextElement());
}
// Time Complexity: O(n)
}
}
ArrayList
and Vector
is that Vector
is synchronized by default, which makes it thread-safe but slower than ArrayList
for single-threaded applications.Vector
doubles its size when it reaches capacity, while ArrayList
increases its size by 50% by default.Let’s create a real-world example similar to the task manager we did for ArrayList
, but this time using Vector
. Since Vector
is thread-safe, it might be useful in scenarios where multiple threads need to interact with the same list of tasks concurrently.
Vector
(Thread-Safe)
import java.util.*;
class Task {
private int taskId;
private String taskName;
private String assignee;
public Task(int taskId, String taskName, String assignee) {
this.taskId = taskId;
this.taskName = taskName;
this.assignee = assignee;
}
public int getTaskId() { return taskId; }
public String getTaskName() { return taskName; }
public String getAssignee() { return assignee; }
@Override
public String toString() {
return "Task{" + "taskId=" + taskId + ", taskName='" + taskName + '\'' + ", assignee='" + assignee + '\'' + '}';
}
}
public class TaskManagerWithVector {
private Vector<Task> taskList;
public TaskManagerWithVector() {
taskList = new Vector<>();
}
// Add a task
public void addTask(Task task) {
taskList.add(task);
}
// Remove a task by taskId
public boolean removeTaskById(int taskId) {
for (Task task : taskList) {
if (task.getTaskId() == taskId) {
taskList.remove(task);
return true;
}
}
return false;
}
// Search task by assignee
public List<Task> getTasksForAssignee(String assignee) {
List<Task> result = new ArrayList<>();
for (Task task : taskList) {
if (task.getAssignee().equals(assignee)) {
result.add(task);
}
}
return result;
}
// Display all tasks
public void displayTasks() {
for (Task task : taskList) {
System.out.println(task);
}
}
}
class Main {
public static void main(String[] args) {
TaskManagerWithVector taskManager = new TaskManagerWithVector();
// Adding tasks to the manager
taskManager.addTask(new Task(1, "Design Database", "John"));
taskManager.addTask(new Task(2, "Develop API", "Sarah"));
taskManager.addTask(new Task(3, "Create UI", "Michael"));
taskManager.addTask(new Task(4, "Test Application", "John"));
// Display all tasks
System.out.println("All Tasks:");
taskManager.displayTasks();
// Remove a task by ID
taskManager.removeTaskById(2);
System.out.println("\nAfter Removing Task with ID 2:");
taskManager.displayTasks();
// Get tasks for assignee "John"
System.out.println("\nTasks assigned to John:");
taskManager.getTasksForAssignee("John").forEach(System.out::println);
}
}
taskId
, taskName
, and assignee
.Vector<Task>
to store tasks.Vector
is synchronized, it ensures that tasks are added and removed safely when accessed by different threads, but it may incur performance overhead in a single-threaded application due to synchronization.Vector
provides a quick solution.Vector
is a legacy class, it might be used in legacy systems where refactoring to newer collections like CopyOnWriteArrayList
is impractical.
Vector
is thread-safe and preserves the insertion order, making it a good choice in multi-threaded applications where synchronization is required.ArrayList
might be a better choice because it offers better performance.ArrayList
, Vector
allows duplicates and can dynamically resize, but at the cost of performance due to synchronization.If you are building a high-performance application where thread-safety is not a concern, it’s often better to use ArrayList
instead of Vector
. If you need thread-safety and don’t want to manually manage synchronization, Vector
can be a good choice, but modern alternatives like CopyOnWriteArrayList
or Collections.synchronizedList()
are often preferred.
Stack
is a legacy class in Java that represents a last-in-first-out (LIFO) stack of objects. It extends Vector
and implements the List
interface, so it has similar properties to Vector
, but with additional stack-specific methods like push
, pop
, peek
, etc.Vector
, Stack
is synchronized by default, making it thread-safe, but also potentially slower in single-threaded contexts due to the overhead of synchronization.Stack
is often used when you need to keep track of method calls, undo operations, or parsing expressions (like in calculators or compilers).Operation | Time Complexity |
---|---|
Push (Add an element) | O(1) |
Pop (Remove an element) | O(1) |
Peek (View top element) | O(1) |
Search | O(n) (linear search for element) |
9 ways to retrieve elements from a Stack:
package com.niteshsynergy.collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Stack;
import java.util.Enumeration;
public class Demo02Stack {
public static void main(String[] args) {
// Using generics for type safety
Stack<String> stack = new Stack<>();
// Adding elements to the Stack
stack.push("A");
stack.push("B");
stack.push("C");
stack.push("D");
stack.push("E");
stack.push("F");
stack.push("G");
stack.push("H");
stack.push("I");
// Printing the Stack
System.out.println("Stack elements: " + stack);
// 9 ways to retrieve elements from a Stack:
// 1. Using the peek() method:
System.out.println("Using peek method...");
System.out.println(stack.peek()); // Access top element without removing it
// 2. Using pop() method:
System.out.println("Using pop method...");
System.out.println(stack.pop()); // Access and remove top element
// 3. Using an iterator:
System.out.println("Using Iterator...");
Iterator<String> iterator = stack.iterator(); // Using generics
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// Time Complexity: O(n)
// 4. Using enhanced for loop (foreach):
System.out.println("Using enhanced for loop (foreach)...");
for (String element : stack) { // No need to cast as it's a generic list
System.out.println(element);
}
// Time Complexity: O(n)
// 5. Using a ListIterator (for bi-directional iteration):
System.out.println("Using ListIterator...");
ListIterator<String> listIterator = stack.listIterator(); // Using generics
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}
// Time Complexity: O(n)
// 6. Using the stream() method:
System.out.println("Using stream() method...");
stack.stream().forEach(System.out::println);
// Time Complexity: O(n)
// 7. Using the forEach() method (Java 8+):
System.out.println("Using forEach() method...");
stack.forEach(System.out::println);
// Time Complexity: O(n)
// 8. Using the toArray() method (convert to array):
System.out.println("Using toArray() method...");
Object[] array = stack.toArray();
for (Object element : array) {
System.out.println(element);
}
// Time Complexity: O(n) for conversion and iteration
// 9. Using Enumeration (specific to Stack):
System.out.println("Using Enumeration...");
Enumeration<String> enumeration = stack.elements(); // Stack's specific method to get Enumeration
while (enumeration.hasMoreElements()) {
System.out.println(enumeration.nextElement());
}
// Time Complexity: O(n)
}
}
We can implement a real-world example of a Undo Mechanism using a Stack
. This is a typical use case for a Stack
, where each action performed (e.g., editing text) is pushed onto the stack, and you can pop the last action to undo it.
import java.util.*;
class Action {
private String actionName;
private String data;
public Action(String actionName, String data) {
this.actionName = actionName;
this.data = data;
}
@Override
public String toString() {
return "Action{" + "actionName='" + actionName + '\'' + ", data='" + data + '\'' + '}';
}
}
public class UndoManager {
private Stack<Action> undoStack;
private Stack<Action> redoStack;
public UndoManager() {
undoStack = new Stack<>();
redoStack = new Stack<>();
}
// Perform an action and push it onto the undo stack
public void performAction(String actionName, String data) {
Action action = new Action(actionName, data);
undoStack.push(action);
System.out.println("Performed: " + action);
// Clear redo stack after a new action
redoStack.clear();
}
// Undo the last action
public void undo() {
if (!undoStack.isEmpty()) {
Action lastAction = undoStack.pop();
redoStack.push(lastAction);
System.out.println("Undone: " + lastAction);
} else {
System.out.println("No actions to undo.");
}
}
// Redo the last undone action
public void redo() {
if (!redoStack.isEmpty()) {
Action lastUndoneAction = redoStack.pop();
undoStack.push(lastUndoneAction);
System.out.println("Redone: " + lastUndoneAction);
} else {
System.out.println("No actions to redo.");
}
}
// Display the current actions in the undo stack
public void displayActions() {
System.out.println("\nCurrent Actions (Undo Stack):");
for (Action action : undoStack) {
System.out.println(action);
}
}
}
class Main {
public static void main(String[] args) {
UndoManager undoManager = new UndoManager();
// Perform some actions
undoManager.performAction("Edit Text", "Added a paragraph.");
undoManager.performAction("Delete Text", "Deleted a paragraph.");
undoManager.performAction("Format Text", "Bolded a paragraph.");
// Display actions in the undo stack
undoManager.displayActions();
// Undo an action
undoManager.undo();
// Display actions after undo
undoManager.displayActions();
// Redo an action
undoManager.redo();
// Display actions after redo
undoManager.displayActions();
}
}
actionName
and data
to describe what was done (e.g., editing text).undoStack
for actions that can be undone and redoStack
for actions that have been undone but can be redone.performAction
method adds an action to the undoStack
.undo
method pops the most recent action from the undoStack
and pushes it onto the redoStack
.redo
method pops the most recent undone action from the redoStack
and pushes it back onto the undoStack
.Stack
(due to its synchronization) may introduce unnecessary performance overhead. For single-threaded environments, a simple ArrayList
or LinkedList
may be more efficient.Stack
, and you are building a modern application, Deque
(Double-ended Queue) or ArrayDeque
is often recommended for stack-like operations because they provide better performance.
Stack
is useful in scenarios where LIFO (Last In First Out) behavior is needed, such as undo/redo functionality, parsing, and recursive method calls.ArrayDeque
or LinkedList
in non-concurrent contexts.Stack
can efficiently handle operations that need to be undone or redone, maintaining a history of actions and providing simple management of them.
LinkedList
in Java is part of the java.util
package and implements the List
interface. Unlike an ArrayList
, it stores elements in nodes where each node points to the next node, creating a linked structure. It supports operations such as insertion and deletion at both ends of the list.LinkedList
is not synchronized, meaning it is not thread-safe by default. You would need to use external synchronization if multiple threads are involved.LinkedList
is useful when you need to frequently add or remove elements, particularly when these operations are expected to happen at the beginning or end of the list, as it performs better than ArrayList
in these cases.
Operation | Time Complexity |
---|---|
Add First (Insert at beginning) | O(1) |
Add Last (Insert at end) | O(1) |
Remove First | O(1) |
Remove Last | O(1) |
Get (Access element) | O(n) |
Remove (Access and remove an element) | O(n) |
8 ways to retrieve elements from a LinkedList:
package com.niteshsynergy.collection;
import java.util.LinkedList;
import java.util.Iterator;
public class Demo03LinkedList {
public static void main(String[] args) {
// Create a LinkedList of Strings
LinkedList<String> list = new LinkedList<>();
// Add elements to the LinkedList
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
// Printing the LinkedList
System.out.println("LinkedList elements: " + list);
// 8 ways to retrieve elements from a LinkedList:
// 1. Using the get() method:
System.out.println("Using get() method...");
System.out.println(list.get(2)); // Access the element at index 2
// 2. Using iterator():
System.out.println("Using Iterator...");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 3. Using for-each loop:
System.out.println("Using enhanced for loop...");
for (String element : list) {
System.out.println(element);
}
// 4. Using listIterator():
System.out.println("Using ListIterator...");
var listIterator = list.listIterator();
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}
// 5. Using stream() method:
System.out.println("Using stream() method...");
list.stream().forEach(System.out::println);
// 6. Using forEach() method:
System.out.println("Using forEach() method...");
list.forEach(System.out::println);
// 7. Using toArray():
System.out.println("Using toArray() method...");
Object[] array = list.toArray();
for (Object element : array) {
System.out.println(element);
}
// 8. Using descendingIterator() for reverse order:
System.out.println("Using descendingIterator() for reverse order...");
var descendingIterator = list.descendingIterator();
while (descendingIterator.hasNext()) {
System.out.println(descendingIterator.next());
}
}
}
In this case, a LinkedList
can be used to track changes in an editor, where each change is added to the list, and actions can be undone by removing elements from the list.
import java.util.*;
class Action {
private String actionName;
private String data;
public Action(String actionName, String data) {
this.actionName = actionName;
this.data = data;
}
@Override
public String toString() {
return "Action{" + "actionName='" + actionName + '\'' + ", data='" + data + '\'' + '}';
}
}
public class UndoManager {
private LinkedList<Action> actionHistory;
public UndoManager() {
actionHistory = new LinkedList<>();
}
// Perform an action
public void performAction(String actionName, String data) {
Action action = new Action(actionName, data);
actionHistory.add(action);
System.out.println("Performed: " + action);
}
// Undo the last action
public void undo() {
if (!actionHistory.isEmpty()) {
Action lastAction = actionHistory.removeLast();
System.out.println("Undone: " + lastAction);
} else {
System.out.println("No actions to undo.");
}
}
// Display actions
public void displayActions() {
System.out.println("\nCurrent Actions:");
for (Action action : actionHistory) {
System.out.println(action);
}
}
}
class Main {
public static void main(String[] args) {
UndoManager undoManager = new UndoManager();
// Perform some actions
undoManager.performAction("Edit Text", "Added a paragraph.");
undoManager.performAction("Delete Text", "Deleted a paragraph.");
undoManager.performAction("Format Text", "Bolded a paragraph.");
// Display actions
undoManager.displayActions();
// Undo an action
undoManager.undo();
// Display actions after undo
undoManager.displayActions();
}
}
In this example, the LinkedList
allows us to perform actions (like editing text) and keep a history. The removeLast()
method efficiently removes the most recent action for the undo functionality.
list.get(index)
), an ArrayList
is often more efficient due to its random access capability (O(1) time complexity).ArrayList
.