Don’t Be Fooled by Appearances: The Truth Behind List.contains() in Java

venkat subbaraju
2 min readApr 10, 2024

--

Weird contains() method behaviour in Java util List

Have you ever encountered a situation where you expect the contains method in a Java List to return true for a seemingly obvious match, but it inexplicably returns false? You're not alone. This behavior can be counterintuitive, especially when dealing with String objects. Let's delve into the reasons behind this and explore how to ensure accurate comparisons in your code.

The Misconception: Comparing Addresses vs. Values

At first glance, it might seem like the contains method simply checks if the memory address of an object in the list matches the address of the object you're searching for. However, this is not entirely true. In most cases, contains focuses on comparing the values (content) of objects, not their memory locations.

The Plot Twist: String Internals and Object Equality

Java uses built-in logic to compare objects for equality. When dealing with standard objects like Strings and Integers, this usually works seamlessly. But things can get a little tricky with Strings, especially when you create them in specific ways.

For instance, consider this code snippet:

String color1 = "green";
String color2 = new String("green");
List<String> colors = Arrays.asList("red", color1);
System.out.println(colors.contains(color2)); // Might print false!

Here, color1 and color2 have the same value ("green"), but they might be different String objects in memory. This can happen due to Java's String pool optimization. As a result, the contains method might not recognize them as equal, leading to the unexpected false output.

Solutions to the “Mystery”

There are a couple of ways to ensure accurate value comparison with contains despite potential String object differences:

  • Explicit Comparison with equals():
if (colorList.contains(targetColor)) {     
// Might not work due to separate String objects
}
else {
for (String color : colorList) {
if (color.equals(targetColor)) {
// Explicit comparison using equals method
}
}
}

This approach iterates through the colorList and explicitly compares each element's value with targetColor using the equals method. This guarantees a value-based comparison.

  • Case-Insensitive Search with toLowerCase():
List<String> colorListLowerCase = colorList.stream().map(String::toLowerCase).collect(Collectors.toList());
String targetColorLowerCase = targetColor.toLowerCase();
if (colorListLowerCase.contains(targetColorLowerCase)) {
// Now the comparison is case-insensitive
} else {
// Handle not found case
}

This approach converts both colorList elements and targetColor to lowercase (or uppercase) before comparison. This ensures case sensitivity won't affect the search.

Key Takeaways:

  • List.contains compares object values, not memory addresses (in most cases).
  • String object creation can lead to seemingly identical String objects with different memory locations.
  • Use equals for explicit value comparison or toLowerCase/toUpperCase for case-insensitive searches.

By understanding these concepts, you can avoid unexpected behavior with List.contains and ensure your code accurately identifies elements within your lists. Remember, even seemingly simple methods in Java can have hidden depths!

--

--