When working with collections in Java, the ArrayList
class is one of the most frequently used data structures due to its flexibility and powerful features. However, there may be instances where you need a customized version to suit specific requirements. This blog post will guide you through creating a simple, custom ArrayList
implementation in Java, explaining each step to understand the underlying mechanics.
Introduction to ArrayList
An ArrayList
in Java is a resizable array, which can grow as needed to accommodate adding items. Unlike a standard array, it provides methods to manipulate the size, and elements, and perform various operations such as adding, removing, and accessing elements.
Why Create a Custom ArrayList?
Creating a custom ArrayList
allows you to:
- Implement specific behaviors or constraints.
- Optimize performance for particular use cases.
- Understand the internal workings of data structures.
Step-by-Step Implementation
Our custom ArrayList
, named MyArrayList
, will implement basic functionalities: adding, removing, and accessing elements, along with checking the size and if it’s empty.
1. Defining the Class and Constructor
First, we define our class with a parameterized type <E>
to make it generic:
package datastructure.array;
public class MyArrayList <E>{
private Object [] elements;
private int size;
public MyArrayList(){
elements = new Object[10];
}
}
The elements
array will store the list items. Initially, it has a capacity of 10 elements, but this will increase as needed. The size
keeps track of the actual number of elements in the list.
2. Ensuring Capacity
Before adding new elements, we must ensure that there is enough space in the underlying array. If the array is full, we create a new array with double the capacity and copy the existing elements.
private void ensureCapacity(){
if(size == elements.length){
Object[] newElements = new Object[elements.length * 2];
for(int i = 0; i < elements.length; i++){
newElements[i] = elements[i];
}
elements = newElements;
}
}
3. Adding Elements
To add an element, we first ensure capacity, then add the new element at the next available position, indicated by size
, and increment size
.
public void add(E e){
ensureCapacity();
elements[size++] = e;
}
4. Accessing Elements
To access an element, we provide its index. If the index is out of bounds, we throw an IndexOutOfBoundsException
.
@SuppressWarnings("unchecked")
public E get(int index){
if(index >= size || index < 0){
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
return (E) elements[index];
}
In this code:
- If
@SuppressWarnings("unchecked")
annotation is not added then getting warnings likeUnchecked cast: 'java.lang.Object' to 'E'
- The
@SuppressWarnings("unchecked")
annotation is used to suppress the unchecked cast warning. This tells the compiler that the programmer believes the cast to be safe and does not want the warning to be shown.
Remember, suppressing warnings should be done with caution. In this case, it’s relatively safe because the internal array (elements
) is controlled and only E
type elements are added to it. Therefore, the cast should be safe. However, in more complex cases or public APIs, it might be better to find a design that avoids the need for unchecked casts altogether, though that can be challenging with Java’s type system.
5. Removing Elements
Removing an element involves shifting all subsequent elements one position to the left to fill the gap. We also nullify the last element to avoid memory leaks.
public void remove(int index){
if(index >= size || index < 0){
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
for(int i = index; i < size - 1; i++){
elements[i] = elements[i + 1];
}
elements[--size] = null;
}
6. Size and IsEmpty
We provide two utility methods to check the number of elements and if the list is empty:
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
7. Testing Our MyArrayList
Finally, we test our implementation with a simple main method:
public static void main(String[] args) {
MyArrayList<Integer> myArrayList = new MyArrayList<>();
myArrayList.add(12);
myArrayList.add(13);
myArrayList.add(11);
for(int i = 0; i < myArrayList.size(); i++){
System.out.println(myArrayList.get(i) + ", ");
}
}
Conclusion
Through this guide, we’ve created a basic yet functional custom ArrayList
in Java. While this implementation covers fundamental operations, Java’s standard ArrayList
offers much more. However, understanding and building your own data structures is invaluable for grasping the concepts behind them and can be a rewarding experience.
Remember, this is a simplified version meant for educational purposes. Real-world applications would require more comprehensive methods and considerations for efficiency and safety.