contains and remove in the Collection use depth -- why override equals()?

introduction

In the Collection:
The contains method determines whether a collection contains a specified element. If so, it returns true;
The remove method deletes a single instance of a specified element from the collection;
These two methods look very simple, are also very simple to use, and are also very commonly used; But how exactly do they match to the corresponding elements?

Source code analysis

Taking ArrayList as an example, let's analyze the source code of contains and remove in ArrayList;

First look at the contents:

The Object to be compared here is an Object class, the variable name is O (i.e. whether it contains o), and an indexOf method is called. Next, let's take a further look at the indexOf source code:

You can see that indexOf further calls indexOfRange method. We need to take a deep look at this method:

It can be found that the equals method (blue part) is o called in indexOfRange!
We know that the equals method judges whether two objects are equal, but by default, the address of the object is compared. If you want to compare the content of the object, you need to override the equals method;
Then, the contents calls the equals method. Therefore, the contents determines whether a collection contains an element, which is actually compared through the object address;
This is not the result we want, so almost all types placed in the collection need to override the equals method!

Why almost all?
Because there is still a special case: SUN has rewritten the equals method of String class and wrapper class, so we don't need to rewrite equals for these two types!

Also look at the remove method:

Similarly, the remove method compares elements through the equals method and then removes them;

Therefore, a conclusion can be drawn here:
Both the remove method and the contain method in the Collection will call equals at the bottom, so the equals method should be overridden as long as the type placed in the Collection;
Because the comparison of object addresses is meaningless, what we actually need is the comparison between object contents;

Example test

Knowing the conclusion, let's write a few codes to test:

Special cases of String class and wrapper class

For String type and wrapper class, SUN company rewrites the equals method, so let's test these two cases first:

import java.util.ArrayList;
import java.util.Collection;

// Conclusion: the bottom layer of the remove method and the contains method in the Collection interface will call equals,
// Therefore, the type stored in a collection needs to override its equals method
// (however, the equals methods of String and wrapper classes have been overridden and need not be overridden.)
public class CollectionTest02 {
    public static void main(String[] args) {
        // Take ArrayList as an example
        Collection array1 = new ArrayList();

        // String class:
        String s1 = "Hello";
        // Put s1 into array1
        array1.add(s1);
        // If an s2 is also defined as "Hello", will calling contains contain s2?
        String s2 = "Hello";
        System.out.println("array1 Include s2?" + array1.contains(s2)); // true
        // Because s1 is placed in array1, if s2 is removed, will s1 be removed?
        array1.remove(s2);
        System.out.println("remove s2 after s1 Is it still there array1 Medium?" + array1.contains(s1)); // false


        // The same applies to packaging:
        Integer num1 = 1000;
        // Put num1 in array1
        array1.add(num1);
        // Define a num2 that is also 1000
        Integer num2 = 1000;
        System.out.println("array1 Include num2?" + array1.contains(num2)); // true
        // Remove num2 and observe whether num1 will be removed
        array1.remove(num2);
        System.out.println("remove num2 after num1 Is it still there array1 Medium?" + array1.contains(num1)); // false
    }
}

Output results:

array1 Include s2?true
 remove s2 after s1 Is it still there array1 Medium? false
array1 Include num2?true
 remove num2 after num1 Is it still there array1 Medium? false

Custom type

This is the case of equals rewriting. Next, I customize a type to see what happens without rewriting;

import java.util.ArrayList;
import java.util.Collection;

public class CollectionTest03 {
    public static void main(String[] args) {
        // Or take ArrayList as an example
        Collection array = new ArrayList();

        // Create a User object u1
        User u1 = new User("Zhang San");
        // Put u1 object into array
        array.add(u1);
        // Create a User object whose content is the same as u1 and is "Zhang San". Will the object be included in the call to contains?
        System.out.println("array Does the include newly created objects?" + array.contains(new User("Zhang San"))); // false
        // Remove a new object with "Zhang San", u1 will it be removed?
        array.remove(new User("Zhang San"));
        System.out.println("After removal u1 Does it exist?" + array.contains(u1)); // true
    }
}

// Define a User class yourself
class User {
    // Member variable: name
    private String name;

    // Default construction
    User() {}
    // Parameterized structure: initialization name
    User(String name) {
        this.name = name;
    }
}

Output results:

array Does the include newly created objects? false
 After removal u1 Does it exist? true

It can be seen that the equals method is not overridden in the User method I defined, so when I call contains and remove, although the object content passed in and u1 are the same as "Zhang San", the object address is actually compared;

Next, I rewrite the User's equals method to see how the result is;

import java.util.ArrayList;
import java.util.Collection;

public class CollectionTest03 {
    public static void main(String[] args) {
        // Or take ArrayList as an example
        Collection array = new ArrayList();

        // Create a User object u1
        User u1 = new User("Zhang San");
        // Put u1 object into array
        array.add(u1);
        // Create a User object whose content is the same as u1 and is "Zhang San". Will the object be included in the call to contains?
        System.out.println("array Does the include newly created objects?" + array.contains(new User("Zhang San"))); // true
        // Remove a new object with "Zhang San", u1 will it be removed?
        array.remove(new User("Zhang San"));
        System.out.println("After removal u1 Does it exist?" + array.contains(u1)); // false
    }
}

// Define a User class yourself
class User {
    // Member variable: name
    private String name;

    // Default construction
    User() {}
    // Parameterized structure: initialization name
    User(String name) {
        this.name = name;
    }

    // Override the equals method to compare by name
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return name.equals(user.name);
    }
}

Output results:

array Does the include newly created objects? true
 After removal u1 Does it exist? false

I just rewritten an equals method. Nothing else has changed, and the result is completely different;

So this validates the previous conclusion: both the underlying implementations of contains and remove call the equals method;

summary

In fact, this article is to analyze the underlying implementation of contains and remove. What I want to say is:
Both the remove method and the contain method in the Collection will call equals at the bottom, so the equals method should be overridden as long as the type placed in the Collection; (except String and wrapper classes)

I hope you can form a good habit in learning Java and always think about the rewriting of equals. Otherwise, if you do a project, you can't even find the mistakes;

Tags: Java JavaSE

Posted on Sat, 18 Sep 2021 21:45:11 -0400 by Al42