Back to Blog
Java

Java ArrayList contains: Usage, Performance, and Alternatives

java arraylist contains: Learn how ArrayList contains works, its linear scan behavior, null handling, and when to switch to HashSet for faster lookups.

ArrayListJava CollectionscontainsHashSetequals
Illustration of a magnifying glass scanning a list of elements, representing the linear search behavior of ArrayList contains.

java arraylist contains requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you call contains on a java.util.ArrayList, the method performs a linear scan of the list and compares each element using Objects.equals. This behavior is simple and predictable, but it has performance implications that matter when the list grows or when contains is called frequently. This article explains the mechanics of ArrayList.contains, how equality is determined, and when a different collection is a better fit.

How ArrayList.contains Works

The contains(Object o) method is defined in the List interface and implemented in ArrayList by iterating over the internal array and comparing each element with the argument. The comparison uses o.equals(element) for non-null arguments and o == null for null checks. This means the method relies on the equals contract of the elements stored in the list.

List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); if (names.contains("Alice")) { System.out.println("Found Alice"); }

In this example, contains iterates through the list and calls "Alice".equals("Alice") on the first element, returning true immediately. If the element is not found, the iteration completes without a match and false is returned.

The Role of equals and hashCode

ArrayList.contains uses equals to determine equality, but it does not use hashCode. This is different from HashSet or HashMap, which rely on hashCode to locate buckets. For ArrayList, the only requirement is that the element's equals method is correctly implemented. If you store custom objects, you must override equals (and ideally hashCode) to define meaningful equality.

class Product { String sku; Product(String sku) { this.sku = sku; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Product)) return false; Product other = (Product) obj; return sku.equals(other.sku); } @Override public int hashCode() { return sku.hashCode(); } }

Without overriding equals, two Product instances with the same sku are not considered equal because Object.equals compares references. In that case, contains only returns true for the exact same object instance, which is rarely the intended behavior.

Null Handling in contains

ArrayList allows null elements, and contains handles them correctly. When you call contains(null), the implementation checks for a null element using == rather than equals. This is consistent with the Objects.equals semantics.

List<String> list = new ArrayList<>(); list.add(null); System.out.println(list.contains(null)); // true

This behavior is useful when a list may contain null values and you need to check for their presence. However, be aware that if your list contains null and you call contains with a non-null object, the iteration will skip null elements without calling equals on them, avoiding a NullPointerException.

Time Complexity and Performance

ArrayList.contains runs in O(n) time because it may need to examine every element. For small lists, this overhead is negligible. But if you call contains repeatedly on a large list, the cumulative cost becomes significant. For example, checking membership for each of m elements against a list of size n results in O(m * n) time.

List<String> largeList = // ... for (String item : manyItems) { if (largeList.contains(item)) { // process match } }

In such scenarios, a HashSet provides O(1) average lookup time because it uses hashCode to locate the element directly. The tradeoff is that HashSet does not preserve insertion order and requires proper hashCode implementations.

Comparing ArrayList and HashSet for contains

Collectioncontains Time ComplexityOrderingNull ElementsRequires hashCode
ArrayListO(n)Insertion orderYesNo
HashSetO(1) averageNo guaranteed orderYes (one null)Yes

Use ArrayList when you need to preserve order and the list is small, or when contains is called rarely. Use HashSet when membership testing is a primary operation and the collection size is large. If you need both order and fast lookups, consider LinkedHashSet, which maintains insertion order while providing O(1) contains.

Using containsAll for Bulk Checks

ArrayList also inherits the containsAll(Collection<?> c) method from List. This method returns true if all elements of the specified collection are present in the list. It is implemented by iterating over the argument collection and calling contains for each element, so its time complexity is O(n * m) where n is the list size and m is the argument size.

List<String> required = Arrays.asList("A", "B"); List<String> actual = Arrays.asList("A", "B", "C"); boolean hasAll = actual.containsAll(required); // true

If you frequently need to check whether a list contains all elements of another collection, consider using a HashSet for the larger collection to reduce the overall cost.

When to Avoid ArrayList.contains

Avoid using ArrayList.contains in tight loops or with large datasets where membership checks dominate. A common pattern is deduplication: building a list of unique items by checking contains before adding. This is O(n^2) for n items. Instead, use a HashSet to track seen items and then optionally convert to a list if order matters.

List<String> unique = new ArrayList<>(); Set<String> seen = new HashSet<>(); for (String item : input) { if (seen.add(item)) { unique.add(item); } }

Here, the HashSet.add returns false if the item already exists, providing O(1) membership testing. This approach scales much better than using ArrayList.contains in a loop.

Compatibility and Version Notes

The behavior of ArrayList.contains has been stable since the early versions of the Java Collections Framework. The implementation relies on equals and does not depend on hashCode. This means that even if you have a class with a broken hashCode (e.g., always returning 0), ArrayList.contains still works correctly as long as equals is correct. This is a subtle advantage over HashSet, which would degrade to O(n) behavior in such cases.

In Java 8 and later, Objects.equals is used internally, but the observable behavior is identical. No special configuration is required to use contains; it is available on any ArrayList instance.

For most applications, the decision between ArrayList and HashSet for membership tests comes down to the tradeoff between order preservation and lookup speed. Measure your actual usage patterns rather than assuming one is always better. If you need to maintain order and the list is small, ArrayList.contains is perfectly acceptable. If you are building a lookup index or performing frequent membership checks on a large dataset, switch to a HashSet or LinkedHashSet.

java arraylist contains: Practical Usage and Code Examples | RYUSLOG DEV