java arraylist of objects: how to use it
java arraylist of objects: Learn how to create and work with an ArrayList of objects in Java, including type-safe declarations, iteration, sorting, and common pitfalls.
When you need a resizable sequence of objects in Java, ArrayList is the most common choice. Unlike a plain array, an ArrayList grows automatically as you add elements, and it integrates with the Java Collections Framework. This article focuses on the practical details of using java arraylist of objects — how to declare it with proper type safety, add and retrieve objects, iterate over them, and avoid the mistakes that lead to runtime casts or ClassCastException.
Declaring an ArrayList with a Specific Object Type
The first step is to declare an ArrayList that holds a specific class. Use generics to avoid raw types. For example, if you have a Product class with fields like name and price, you can declare:
List<Product> products = new ArrayList<>();
Using the List interface as the variable type is a common practice because it lets you swap the implementation later if needed. The diamond operator <> in Java 7 and later infers the type from the left side. If you omit generics and write ArrayList products = new ArrayList();, you get a raw type. Raw types allow any object to be added, which forces you to cast when retrieving and removes compile-time checking. For example, you could accidentally add a String to a list meant for Product objects, and the compiler would not complain. That leads to a ClassCastException at runtime when you try to use the element as a Product.
Always use a parameterized type. This is the core of type safety when working with an ArrayList of objects.
Adding Objects to the ArrayList
To add an object, use the add method. The object must be an instance of the declared type or a subtype. If Product has a constructor that takes a name and price, you can do:
products.add(new Product("Laptop", 999.99)); products.add(new Product("Mouse", 19.99));
If you try to add an incompatible type, the compiler rejects it. That is the benefit of generics. You can also insert at a specific index using add(int index, E element), which shifts subsequent elements to the right. Be aware that inserting in the middle of a large list is O(n) because elements must be shifted.
When you need to add many objects, consider using addAll with another collection. For example:
List<Product> newProducts = List.of(new Product("Keyboard", 49.99)); products.addAll(newProducts);
This is more concise than a loop when you already have a collection.
Retrieving and Modifying Objects
Retrieval is straightforward with get(int index). Because the list is typed, the returned value is already a Product, not an Object. You can call methods directly without casting:
Product first = products.get(0); System.out.println(first.getName());
To update an element at a specific position, use set(int index, E element). This replaces the existing element and returns the old one:
Product old = products.set(0, new Product("Ultrabook", 1299.99));
If you try to access an index that is out of range (negative or >= size), the get or set method throws IndexOutOfBoundsException. Always check size() before accessing an index if you are not certain about the list's contents.
Iterating Over an ArrayList of Objects
There are several ways to iterate over an ArrayList of objects. The enhanced for loop is the most readable for most cases:
for (Product p : products) { System.out.println(p.getName() + " costs " + p.getPrice()); }
If you need the index while iterating, use a traditional loop with get:
for (int i = 0; i < products.size(); i++) { Product p = products.get(i); // process p and i }
For removing elements while iterating, use an Iterator or the removeIf method. Using for with remove can cause ConcurrentModificationException if you modify the list during iteration. For example:
products.removeIf(p -> p.getPrice() > 1000);
This is clean and avoids the pitfalls of manual index management. The Iterator approach is also valid:
Iterator<Product> it = products.iterator(); while (it.hasNext()) { Product p = it.next(); if (p.getPrice() > 1000) { it.remove(); } }
Both methods are safe for structural modification.
Sorting an ArrayList of Custom Objects
To sort an ArrayList of objects, you need a way to compare them. If your class implements Comparable, you can use Collections.sort or List.sort. For example, if Product implements Comparable<Product> based on price:
public class Product implements Comparable<Product> { // fields, constructor, getters @Override public int compareTo(Product other) { return Double.compare(this.price, other.price); } }
Then you can sort with:
products.sort(null); // uses natural ordering
If you do not want to modify the class, pass a Comparator. For example, sort by name:
products.sort(Comparator.comparing(Product::getName));
You can chain comparators for secondary sort keys:
products.sort(Comparator.comparing(Product::getPrice).thenComparing(Product::getName));
The sort method is stable, meaning equal elements keep their relative order. This is useful when you sort by multiple criteria.
Performance and Memory Considerations
An ArrayList is backed by an array. When you add elements beyond its capacity, it creates a new array and copies the existing elements. This resizing operation is O(n) and happens occasionally. The amortized cost of add is still O(1), but if you know the approximate size in advance, you can specify the initial capacity to reduce reallocations:
List<Product> products = new ArrayList<>(1000);
This is a performance optimization for large lists, but it is rarely necessary for small ones. Keep in mind that ArrayList stores references to objects, not the objects themselves. The objects are on the heap, and the list holds references. This means memory usage depends on the object size and the list's capacity. If you remove many elements, the backing array does not shrink automatically. You can call trimToSize() to reduce the capacity to the current size, which can save memory if the list is large and stays that size.
When you need to frequently insert or remove elements at the beginning or middle, a LinkedList may be more efficient, but it has worse cache locality and higher per-element overhead. For most use cases, ArrayList is the better default.
Common Pitfalls with ArrayList of Objects
One common mistake is using a raw type and casting. For example:
ArrayList list = new ArrayList(); list.add(new Product("A", 1.0)); Product p = (Product) list.get(0); // works, but unsafe
If another part of the code adds a different type, the cast fails. Always use generics.
Another pitfall is forgetting that the list stores references. If you add the same object multiple times, you have multiple references to the same instance. Modifying the object affects all occurrences. If you need distinct objects, create a new instance for each add.
Also, be careful with equals and hashCode. Methods like contains, indexOf, and remove rely on equals. If your object does not override equals, it uses identity, so two objects with the same field values are not considered equal. Override equals and hashCode when you need value-based comparison. For example, to remove a specific product by value:
Product toRemove = new Product("Mouse", 19.99); products.remove(toRemove); // works only if equals is overridden
Without equals, this removes nothing unless the exact same instance is in the list.
Finally, remember that ArrayList is not thread-safe. If multiple threads modify the list concurrently, you need external synchronization or a thread-safe collection like CopyOnWriteArrayList. For read-heavy scenarios with infrequent writes, CopyOnWriteArrayList may be acceptable, but for most concurrent use cases, consider ConcurrentLinkedDeque or a synchronized wrapper.
Using a Custom Class with an ArrayList in Practice
Putting it together, here is a complete example that creates an ArrayList of Product objects, adds a few, sorts them by price, and prints the result:
import java.util.*; class Product { private String name; private double price; public Product(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; } @Override public String toString() { return name + " ($" + price + ")"; } } public class Main { public static void main(String[] args) { List<Product> products = new ArrayList<>(); products.add(new Product("Laptop", 999.99)); products.add(new Product("Mouse", 19.99)); products.add(new Product("Keyboard", 49.99)); products.sort(Comparator.comparingDouble(Product::getPrice)); for (Product p : products) { System.out.println(p); } } }
This code is type-safe, uses the List interface, and demonstrates sorting with a comparator. The output is the products sorted by price. This pattern applies to any custom object you need to manage in a list.
When you design a class that will be stored in an ArrayList, think about whether you need value equality. If you plan to use contains or remove, override equals and hashCode. If you plan to sort, implement Comparable or provide a Comparator. These decisions affect how the list behaves in practice.
An ArrayList of objects is a fundamental tool in Java. By using generics, understanding iteration and sorting, and being aware of performance and equality behavior, you can avoid the most common issues and write clean, maintainable code.