Collections.unmodifiableSet are immutable views of sets in Java, meaning their contents cannot be modified after creation. This article at compare.edu.vn explores whether you can compare two Collections.unmodifiableSet
effectively, examining their characteristics, use cases, and methods for comparison, ultimately helping you determine the best approach for your needs. We will explore different comparison techniques, optimization strategies, and potential pitfalls, providing you with a comprehensive guide to mastering Collections.unmodifiableSet
comparisons.
1. What is Collections.UnmodifiableSet?
Collections.unmodifiableSet
is a wrapper class in Java’s Collections
framework that provides an unmodifiable view of an existing set. This means that any attempt to modify the set through this view will result in an UnsupportedOperationException
. This immutability is useful in scenarios where you want to protect the original set from unintended modifications, ensuring data integrity and preventing unexpected behavior in multithreaded environments.
1.1 Key Characteristics of Collections.UnmodifiableSet
- Immutability: The primary characteristic is that it cannot be modified. Attempts to add, remove, or clear elements will throw an
UnsupportedOperationException
. - View, not a Copy: It is a view of the original set, not a copy. Any changes to the underlying set will be reflected in the unmodifiable set.
- Wrapper Class: It wraps an existing set, providing a layer of immutability.
- Thread Safety: Useful in multithreaded environments as it prevents concurrent modifications.
1.2 Why Use Collections.UnmodifiableSet?
- Data Integrity: Prevents accidental modifications to the set.
- Security: Ensures that the set remains unchanged when passed to untrusted code.
- Thread Safety: Useful in multithreaded environments to prevent concurrent modifications.
- Defensive Programming: Adds a layer of protection against unintended side effects.
2. Understanding Set Comparison in Java
Before diving into comparing Collections.unmodifiableSet
, it’s essential to understand how set comparison works in Java. Sets, by definition, are unordered collections of unique elements. Therefore, comparing sets involves checking if they contain the same elements, regardless of the order.
2.1 The equals()
Method
The equals()
method is the standard way to compare objects in Java. For sets, the equals()
method checks if two sets contain the same elements. Specifically, set1.equals(set2)
returns true
if set1
and set2
have the same size and every element in set1
is also in set2
(and vice versa).
2.1.1 How equals()
Works for Sets
- Size Check: First, the method checks if the sizes of the two sets are equal. If they are not, the sets cannot be equal.
- Element Check: Then, it iterates through one of the sets and checks if each element is present in the other set. If any element is not found, the sets are not equal.
2.1.2 Example of Using equals()
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class SetComparisonExample {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
set1.add("apple");
set1.add("banana");
Set<String> set2 = new HashSet<>();
set2.add("banana");
set2.add("apple");
Set<String> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<String> unmodifiableSet2 = Collections.unmodifiableSet(set2);
System.out.println("Are the sets equal? " + unmodifiableSet1.equals(unmodifiableSet2)); // Prints: true
}
}
2.2 The hashCode()
Method
The hashCode()
method returns an integer value that represents the object’s hash code. If two objects are equal according to the equals()
method, they must have the same hash code. However, the reverse is not necessarily true: two objects with the same hash code are not necessarily equal.
2.2.1 Importance of hashCode()
in Set Operations
- Efficiency: Hash codes are used in hash-based collections like
HashSet
andHashMap
to quickly locate elements. - Consistency: If you override the
equals()
method, you must also override thehashCode()
method to maintain consistency.
2.2.2 Example of Using hashCode()
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class SetHashCodeExample {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
set1.add("apple");
set1.add("banana");
Set<String> set2 = new HashSet<>();
set2.add("banana");
set2.add("apple");
Set<String> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<String> unmodifiableSet2 = Collections.unmodifiableSet(set2);
System.out.println("HashCode of set1: " + unmodifiableSet1.hashCode());
System.out.println("HashCode of set2: " + unmodifiableSet2.hashCode());
System.out.println("Are the hashCodes equal? " + (unmodifiableSet1.hashCode() == unmodifiableSet2.hashCode())); // Prints: true
}
}
2.3 Considerations for Custom Objects in Sets
When sets contain custom objects, it’s crucial to properly implement the equals()
and hashCode()
methods in the custom class.
2.3.1 Implementing equals()
for Custom Objects
The equals()
method should compare the relevant fields of the objects to determine if they are equal.
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
2.3.2 Implementing hashCode()
for Custom Objects
The hashCode()
method should generate a hash code based on the same fields used in the equals()
method. A common approach is to use the Objects.hash()
method.
import java.util.Objects;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
2.3.3 Example with Custom Objects
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class CustomObjectSetExample {
public static void main(String[] args) {
Person person1 = new Person("Alice", 30);
Person person2 = new Person("Bob", 25);
Set<Person> set1 = new HashSet<>();
set1.add(person1);
set1.add(person2);
Person person3 = new Person("Alice", 30);
Person person4 = new Person("Bob", 25);
Set<Person> set2 = new HashSet<>();
set2.add(person3);
set2.add(person4);
Set<Person> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<Person> unmodifiableSet2 = Collections.unmodifiableSet(set2);
System.out.println("Are the sets equal? " + unmodifiableSet1.equals(unmodifiableSet2)); // Prints: true
}
}
3. Comparing Collections.UnmodifiableSet
Comparing Collections.unmodifiableSet
is essentially the same as comparing regular sets, as the unmodifiable nature of the sets does not affect the comparison logic. The key is to ensure that the elements within the sets are comparable and that the equals()
and hashCode()
methods are properly implemented if the sets contain custom objects.
3.1 Using the equals()
Method
The most straightforward way to compare two Collections.unmodifiableSet
is by using the equals()
method. This method checks if both sets contain the same elements, regardless of their order.
3.1.1 Example of Comparing Unmodifiable Sets with equals()
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class UnmodifiableSetEqualsExample {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
set1.add("apple");
set1.add("banana");
Set<String> set2 = new HashSet<>();
set2.add("banana");
set2.add("apple");
Set<String> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<String> unmodifiableSet2 = Collections.unmodifiableSet(set2);
System.out.println("Are the unmodifiable sets equal? " + unmodifiableSet1.equals(unmodifiableSet2)); // Prints: true
}
}
3.1.2 Comparing Unmodifiable Sets with Custom Objects
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class UnmodifiableCustomObjectSetExample {
public static void main(String[] args) {
Person person1 = new Person("Alice", 30);
Person person2 = new Person("Bob", 25);
Set<Person> set1 = new HashSet<>();
set1.add(person1);
set1.add(person2);
Person person3 = new Person("Alice", 30);
Person person4 = new Person("Bob", 25);
Set<Person> set2 = new HashSet<>();
set2.add(person3);
set2.add(person4);
Set<Person> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<Person> unmodifiableSet2 = Collections.unmodifiableSet(set2);
System.out.println("Are the unmodifiable sets equal? " + unmodifiableSet1.equals(unmodifiableSet2)); // Prints: true
}
}
3.1.3 Performance Considerations for equals()
The equals()
method has a time complexity of O(n), where n is the number of elements in the set. This is because it needs to iterate through the elements to check for equality.
3.2 Using the hashCode()
Method for Quick Checks
While the hashCode()
method cannot definitively determine if two sets are equal, it can be used for a quick check. If the hash codes of two sets are different, then the sets are definitely not equal.
3.2.1 Example of Using hashCode()
for Unmodifiable Sets
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class UnmodifiableSetHashCodeExample {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
set1.add("apple");
set1.add("banana");
Set<String> set2 = new HashSet<>();
set2.add("banana");
set2.add("apple");
Set<String> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<String> unmodifiableSet2 = Collections.unmodifiableSet(set2);
if (unmodifiableSet1.hashCode() == unmodifiableSet2.hashCode()) {
System.out.println("Hash codes are equal, checking equals method: " + unmodifiableSet1.equals(unmodifiableSet2)); // Prints: true
} else {
System.out.println("Hash codes are not equal, sets are not equal.");
}
}
}
3.2.2 Caveats of Using hashCode()
- Hash Collision: It is possible for two different sets to have the same hash code (hash collision). Therefore, you must always use the
equals()
method to confirm equality. - Not Definitive: Equal hash codes do not guarantee that the sets are equal.
3.3 Comparing Sets Using Iterators
Another approach to comparing sets is to use iterators. This involves manually iterating through the elements of both sets and comparing them.
3.3.1 Example of Comparing Unmodifiable Sets Using Iterators
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class UnmodifiableSetIteratorExample {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
set1.add("apple");
set1.add("banana");
Set<String> set2 = new HashSet<>();
set2.add("banana");
set2.add("apple");
Set<String> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<String> unmodifiableSet2 = Collections.unmodifiableSet(set2);
boolean areEqual = true;
if (unmodifiableSet1.size() != unmodifiableSet2.size()) {
areEqual = false;
} else {
Iterator<String> iterator1 = unmodifiableSet1.iterator();
while (iterator1.hasNext()) {
if (!unmodifiableSet2.contains(iterator1.next())) {
areEqual = false;
break;
}
}
}
System.out.println("Are the unmodifiable sets equal? " + areEqual); // Prints: true
}
}
3.3.2 Performance Considerations for Iterators
Using iterators can be less efficient than using the equals()
method, especially for large sets. The time complexity is O(n), similar to the equals()
method, but with potentially higher overhead due to manual iteration.
3.4 Comparing Sets Using Streams
Java Streams provide a functional approach to processing collections. You can use streams to compare two Collections.unmodifiableSet
by converting them to streams and comparing their elements.
3.4.1 Example of Comparing Unmodifiable Sets Using Streams
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Arrays;
public class UnmodifiableSetStreamExample {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
set1.add("apple");
set1.add("banana");
Set<String> set2 = new HashSet<>();
set2.add("banana");
set2.add("apple");
Set<String> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<String> unmodifiableSet2 = Collections.unmodifiableSet(set2);
boolean areEqual = unmodifiableSet1.size() == unmodifiableSet2.size() &&
unmodifiableSet1.stream().allMatch(unmodifiableSet2::contains);
System.out.println("Are the unmodifiable sets equal? " + areEqual); // Prints: true
}
}
3.4.2 Performance Considerations for Streams
Using streams can be more concise and readable, but it may not always be the most efficient approach. The performance of streams can vary depending on the size of the sets and the complexity of the operations.
4. Practical Examples and Use Cases
Understanding how to compare Collections.unmodifiableSet
is useful in various practical scenarios. Let’s explore some use cases where comparing unmodifiable sets can be beneficial.
4.1 Configuration Management
In configuration management, you might have sets of allowed or disallowed values. Comparing Collections.unmodifiableSet
can help determine if the current configuration matches a predefined state.
4.1.1 Example of Configuration Management
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class ConfigurationManagementExample {
public static void main(String[] args) {
Set<String> allowedPermissions1 = new HashSet<>();
allowedPermissions1.add("read");
allowedPermissions1.add("write");
Set<String> allowedPermissions2 = new HashSet<>();
allowedPermissions2.add("write");
allowedPermissions2.add("read");
Set<String> unmodifiablePermissions1 = Collections.unmodifiableSet(allowedPermissions1);
Set<String> unmodifiablePermissions2 = Collections.unmodifiableSet(allowedPermissions2);
if (unmodifiablePermissions1.equals(unmodifiablePermissions2)) {
System.out.println("Configurations are identical.");
} else {
System.out.println("Configurations are different.");
}
}
}
4.2 Data Validation
When validating data, you might need to check if a set of input values matches a predefined set of valid values. Using Collections.unmodifiableSet
can ensure that the set of valid values remains unchanged during the validation process.
4.2.1 Example of Data Validation
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class DataValidationExample {
public static void main(String[] args) {
Set<String> validColors = new HashSet<>();
validColors.add("red");
validColors.add("green");
validColors.add("blue");
Set<String> inputColors = new HashSet<>();
inputColors.add("red");
inputColors.add("blue");
Set<String> unmodifiableValidColors = Collections.unmodifiableSet(validColors);
if (unmodifiableValidColors.containsAll(inputColors)) {
System.out.println("Input data is valid.");
} else {
System.out.println("Input data is invalid.");
}
}
}
4.3 Caching Strategies
In caching, you might use sets to store cached keys or values. Comparing Collections.unmodifiableSet
can help determine if two caches contain the same data.
4.3.1 Example of Caching Strategies
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class CachingStrategiesExample {
public static void main(String[] args) {
Set<String> cacheKeys1 = new HashSet<>();
cacheKeys1.add("user1");
cacheKeys1.add("user2");
Set<String> cacheKeys2 = new HashSet<>();
cacheKeys2.add("user2");
cacheKeys2.add("user1");
Set<String> unmodifiableCacheKeys1 = Collections.unmodifiableSet(cacheKeys1);
Set<String> unmodifiableCacheKeys2 = Collections.unmodifiableSet(cacheKeys2);
if (unmodifiableCacheKeys1.equals(unmodifiableCacheKeys2)) {
System.out.println("Caches contain the same keys.");
} else {
System.out.println("Caches contain different keys.");
}
}
}
4.4 Testing and Assertions
In unit testing, you might want to compare the contents of two sets to verify the correctness of your code. Using Collections.unmodifiableSet
can ensure that the expected set remains unchanged during the test.
4.4.1 Example of Testing and Assertions
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestingAssertionsExample {
@Test
public void testSetEquality() {
Set<String> expectedSet = new HashSet<>();
expectedSet.add("item1");
expectedSet.add("item2");
Set<String> actualSet = new HashSet<>();
actualSet.add("item2");
actualSet.add("item1");
Set<String> unmodifiableExpectedSet = Collections.unmodifiableSet(expectedSet);
Set<String> unmodifiableActualSet = Collections.unmodifiableSet(actualSet);
assertEquals(unmodifiableExpectedSet, unmodifiableActualSet, "Sets should be equal");
}
}
4.5 Protecting Immutable Data
When dealing with immutable data, you can use Collections.unmodifiableSet
to ensure that the data remains unchanged, especially when passing it to other components or methods.
4.5.1 Example of Protecting Immutable Data
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class ImmutableDataProtectionExample {
public static void main(String[] args) {
Set<String> immutableData = new HashSet<>();
immutableData.add("value1");
immutableData.add("value2");
Set<String> unmodifiableImmutableData = Collections.unmodifiableSet(immutableData);
// Pass unmodifiableImmutableData to other methods or components
processData(unmodifiableImmutableData);
}
public static void processData(Set<String> data) {
// The data cannot be modified here, ensuring immutability
System.out.println("Processing data: " + data);
}
}
5. Optimization Strategies for Comparing Large Sets
When dealing with large Collections.unmodifiableSet
, the performance of comparison operations can become critical. Here are some strategies to optimize the comparison process.
5.1 Using Hash Codes for Early Exit
Before performing a full comparison using the equals()
method, compare the hash codes of the two sets. If the hash codes are different, the sets are guaranteed to be different, and you can avoid the more expensive equals()
check.
5.1.1 Example of Using Hash Codes for Optimization
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class HashCodeOptimizationExample {
public static void main(String[] args) {
Set<Integer> set1 = new HashSet<>();
for (int i = 0; i < 100000; i++) {
set1.add(i);
}
Set<Integer> set2 = new HashSet<>();
for (int i = 0; i < 100000; i++) {
set2.add(i);
}
Set<Integer> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<Integer> unmodifiableSet2 = Collections.unmodifiableSet(set2);
long startTime = System.nanoTime();
boolean areEqual;
if (unmodifiableSet1.hashCode() != unmodifiableSet2.hashCode()) {
areEqual = false;
} else {
areEqual = unmodifiableSet1.equals(unmodifiableSet2);
}
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1000000;
System.out.println("Are the sets equal? " + areEqual);
System.out.println("Comparison time: " + duration + " ms");
}
}
5.2 Comparing Sizes First
Before comparing the contents of the sets, check if their sizes are equal. If the sizes are different, the sets cannot be equal, and you can avoid further comparison.
5.2.1 Example of Comparing Sizes First
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class SizeComparisonExample {
public static void main(String[] args) {
Set<Integer> set1 = new HashSet<>();
for (int i = 0; i < 100000; i++) {
set1.add(i);
}
Set<Integer> set2 = new HashSet<>();
for (int i = 0; i < 100001; i++) {
set2.add(i);
}
Set<Integer> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<Integer> unmodifiableSet2 = Collections.unmodifiableSet(set2);
long startTime = System.nanoTime();
boolean areEqual;
if (unmodifiableSet1.size() != unmodifiableSet2.size()) {
areEqual = false;
} else {
areEqual = unmodifiableSet1.equals(unmodifiableSet2);
}
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1000000;
System.out.println("Are the sets equal? " + areEqual);
System.out.println("Comparison time: " + duration + " ms");
}
}
5.3 Using HashSet
for Faster Lookups
If you need to perform frequent lookups to check if an element exists in a set, using a HashSet
can significantly improve performance. HashSet
provides O(1) average-time complexity for contains()
operations.
5.3.1 Example of Using HashSet
for Faster Lookups
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class HashSetLookupExample {
public static void main(String[] args) {
Set<Integer> set1 = new HashSet<>();
for (int i = 0; i < 100000; i++) {
set1.add(i);
}
Set<Integer> set2 = new HashSet<>();
for (int i = 0; i < 100000; i++) {
set2.add(i);
}
Set<Integer> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<Integer> unmodifiableSet2 = Collections.unmodifiableSet(set2);
long startTime = System.nanoTime();
boolean areEqual = true;
for (Integer element : unmodifiableSet1) {
if (!unmodifiableSet2.contains(element)) {
areEqual = false;
break;
}
}
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1000000;
System.out.println("Are the sets equal? " + areEqual);
System.out.println("Comparison time: " + duration + " ms");
}
}
5.4 Parallel Processing with Streams
For very large sets, you can leverage parallel processing with Java Streams to speed up the comparison. By processing the elements in parallel, you can utilize multiple cores and reduce the overall comparison time.
5.4.1 Example of Parallel Processing with Streams
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class ParallelStreamExample {
public static void main(String[] args) {
Set<Integer> set1 = new HashSet<>();
for (int i = 0; i < 1000000; i++) {
set1.add(i);
}
Set<Integer> set2 = new HashSet<>();
for (int i = 0; i < 1000000; i++) {
set2.add(i);
}
Set<Integer> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<Integer> unmodifiableSet2 = Collections.unmodifiableSet(set2);
long startTime = System.nanoTime();
boolean areEqual = unmodifiableSet1.size() == unmodifiableSet2.size() &&
unmodifiableSet1.parallelStream().allMatch(unmodifiableSet2::contains);
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1000000;
System.out.println("Are the sets equal? " + areEqual);
System.out.println("Comparison time: " + duration + " ms");
}
}
5.5 Choosing the Right Data Structure
The choice of data structure can also impact the performance of set comparisons. While HashSet
is generally efficient for most operations, other set implementations like TreeSet
or LinkedHashSet
might be more appropriate depending on the specific requirements.
5.5.1 Comparing Different Set Implementations
Feature | HashSet | TreeSet | LinkedHashSet |
---|---|---|---|
Ordering | Unordered | Sorted according to natural order or comparator | Insertion order |
Performance | O(1) average for add, remove, contains | O(log n) for add, remove, contains | O(1) average for add, remove, contains |
Implementation | Hash table | Red-Black tree | Hash table and linked list |
Memory Usage | Can be higher due to hash table overhead | Generally lower than HashSet | Higher than HashSet due to linked list overhead |
Use Cases | General-purpose, fast lookups | Sorted sets, range queries | Maintaining insertion order |
6. Common Pitfalls and How to Avoid Them
When comparing Collections.unmodifiableSet
, there are some common pitfalls that you should be aware of. Understanding these pitfalls and how to avoid them can help you write more robust and efficient code.
6.1 Null Elements
If the sets contain null
elements, you need to handle them carefully. The equals()
method should be able to handle null
values correctly.
6.1.1 Example of Handling Null Elements
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class NullElementExample {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
set1.add("apple");
set1.add(null);
Set<String> set2 = new HashSet<>();
set2.add(null);
set2.add("apple");
Set<String> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<String> unmodifiableSet2 = Collections.unmodifiableSet(set2);
System.out.println("Are the unmodifiable sets equal? " + unmodifiableSet1.equals(unmodifiableSet2)); // Prints: true
}
}
6.2 Mutable Elements
If the sets contain mutable objects, changes to those objects after they have been added to the set can affect the behavior of the set and the correctness of the comparison.
6.2.1 Example of Mutable Elements
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
class MutableObject {
private String value;
public MutableObject(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
MutableObject that = (MutableObject) obj;
return value.equals(that.value);
}
@Override
public int hashCode() {
return value.hashCode();
}
}
public class MutableElementExample {
public static void main(String[] args) {
MutableObject obj1 = new MutableObject("initial");
MutableObject obj2 = new MutableObject("initial");
Set<MutableObject> set1 = new HashSet<>();
set1.add(obj1);
Set<MutableObject> set2 = new HashSet<>();
set2.add(obj2);
Set<MutableObject> unmodifiableSet1 = Collections.unmodifiableSet(set1);
Set<MutableObject> unmodifiableSet2 = Collections.unmodifiableSet(set2);
System.out.println("Are the unmodifiable sets equal? " + unmodifiableSet1.equals(unmodifiableSet2)); // Prints: true
obj2.setValue("modified");
System.out.println("Are the unmodifiable sets equal after modification? " + unmodifiableSet1.equals(unmodifiableSet2)); // Prints: false
}
}
6.2.2 How to Avoid Pitfalls with Mutable Elements
- Use Immutable Objects: Prefer using immutable objects in sets to avoid unexpected behavior.
- Defensive Copying: If you must use mutable objects, create defensive copies before adding them to the set.
- Avoid Modifying Objects: Avoid modifying objects that are already in the set.
6.3 Incorrect equals()
and hashCode()
Implementations
If the equals()
and hashCode()
methods are not implemented correctly for custom objects, set comparisons can produce incorrect results.
6.3.1 Example of Incorrect Implementations
class IncorrectPerson {
private String name;
private int age;
public IncorrectPerson(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
return false; // Always return false
}
@Override
public int hashCode() {
return 0; // Always return 0
}
}
public class IncorrectEqualsHashCodeExample {
public static void main(String[] args) {
IncorrectPerson person1 = new IncorrectPerson("Alice", 30);
IncorrectPerson person2 = new IncorrectPerson("Alice", 30);
Set<IncorrectPerson> set1 = new HashSet<>();