Class DeepEquals
equals()
checks.
Handles nested objects, collections, arrays, and maps while detecting circular references.
Key features:
- Compares entire object graphs including nested structures
- Handles circular references safely
- Provides detailed difference descriptions for troubleshooting
- Supports numeric comparisons with configurable precision
- Supports selective ignoring of custom
equals()
implementations - Supports string-to-number equality comparisons
Options:
-
IGNORE_CUSTOM_EQUALS
(aSet<Class<?>>
):null
— Use all customequals()
methods (ignore none).- Empty set — Ignore all custom
equals()
methods. - Non-empty set — Ignore only those classes’ custom
equals()
implementations.
-
ALLOW_STRINGS_TO_MATCH_NUMBERS
(aBoolean
): If set totrue
, allows strings like"10"
to match the numeric value10
.
The options Map
acts as both input and output. When objects differ, the difference
description is placed in the options Map
under the "diff" key
(see deepEquals
).
"diff" output notes:
- Empty lists, maps, and arrays are shown with (∅) or [∅]
- A Map of size 1 is shown as Map(0..0), an int[] of size 2 is shown as int[0..1], an empty list is List(∅)
- Sub-object fields on non-difference path shown as {..}
- Map entry shown with 《key ⇨ value》 and may be nested
- General pattern is [difference type] ▶ root context ▶ shorthand path starting at a root context element (Object field, array/collection element, Map key-value)
- If the root is not a container (Collection, Map, Array, or Object), no shorthand description is displayed
Example usage:
Map<String, Object> options = new HashMap<>();
options.put(IGNORE_CUSTOM_EQUALS, Set.of(MyClass.class, OtherClass.class));
options.put(ALLOW_STRINGS_TO_MATCH_NUMBERS, true);
if (!DeepEquals.deepEquals(obj1, obj2, options)) {
String diff = (String) options.get(DeepEquals.DIFF); // Get difference description
// Handle or log 'diff'
Example output:
// Simple object difference
[field value mismatch] ▶ Person {name: "Jim Bob", age: 27} ▶ .age
Expected: 27
Found: 34
// Array element mismatch within an object that has an array
[array element mismatch] ▶ Person {id: 173679590720000287, first: "John", last: "Smith", favoritePet: {..}, pets: Pet[0..1]} ▶ .pets[0].nickNames[0]
Expected: "Edward"
Found: "Eddie"
// Map with a different value associated to a key (Map size = 1 noted as 0..0)
[map value mismatch] ▶ LinkedHashMap(0..0) ▶ 《"key" ⇨ "value1"》
Expected: "value1"
Found: "value2"
- Author:
- John DeRegnaucourt ([email protected])
Copyright (c) Cedar Software LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
License
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - See Also:
-
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic boolean
deepEquals
(Object a, Object b) Performs a deep comparison between two objects, going beyond a simpleequals()
check.static boolean
deepEquals
(Object a, Object b, Map<String, ?> options) Performs a deep comparison between two objects with optional comparison settings.static int
deepHashCode
(Object obj) Computes a deep hash code for the given object by traversing its entire graph.static boolean
hasCustomEquals
(Class<?> c) Determines whether the given class has a customequals(Object)
method distinct fromObject.equals(Object)
.static boolean
hasCustomHashCode
(Class<?> c) Determines whether the given class has a customhashCode()
method distinct fromObject.hashCode()
.
-
Field Details
-
IGNORE_CUSTOM_EQUALS
- See Also:
-
ALLOW_STRINGS_TO_MATCH_NUMBERS
- See Also:
-
DIFF
- See Also:
-
-
Constructor Details
-
DeepEquals
public DeepEquals()
-
-
Method Details
-
deepEquals
Performs a deep comparison between two objects, going beyond a simpleequals()
check.This method is functionally equivalent to calling
deepEquals(a, b, new HashMap<>())
, which means it uses no additional comparison options. In other words:IGNORE_CUSTOM_EQUALS
is not set (all customequals()
methods are used)ALLOW_STRINGS_TO_MATCH_NUMBERS
defaults tofalse
- Parameters:
a
- the first object to compare, may benull
b
- the second object to compare, may benull
- Returns:
true
if the two objects are deeply equal,false
otherwise- See Also:
-
deepEquals
Performs a deep comparison between two objects with optional comparison settings.In addition to comparing objects, collections, maps, and arrays for equality of nested elements, this method can also:
- Ignore certain classes' custom
equals()
methods according to user-defined rules - Allow string-to-number comparisons (e.g.,
"10"
equals10
) - Handle floating-point comparisons with tolerance for precision
- Detect and handle circular references to avoid infinite loops
Options:
-
DeepEquals.IGNORE_CUSTOM_EQUALS
(aCollection<Class<?>>
):null
— Use all customequals()
methods (ignore none). Default.- Empty set — Ignore all custom
equals()
methods. - Non-empty set — Ignore only those classes’ custom
equals()
implementations.
-
DeepEquals.ALLOW_STRINGS_TO_MATCH_NUMBERS
(aBoolean
): If set totrue
, allows strings like"10"
to match the numeric value10
. Default false.
If the objects differ, a difference description string is stored in
options
under the key"diff"
. The key"diff_item"
can provide additional context regarding the specific location of the mismatch.- Parameters:
a
- the first object to compare, may benull
b
- the second object to compare, may benull
options
- a map of comparison options and, on return, possibly difference details- Returns:
true
if the two objects are deeply equal,false
otherwise- See Also:
- Ignore certain classes' custom
-
hasCustomEquals
Determines whether the given class has a customequals(Object)
method distinct fromObject.equals(Object)
.Useful for detecting when a class relies on a specialized equality definition, which can be selectively ignored by deep-comparison if desired.
- Parameters:
c
- the class to inspect, must not benull
- Returns:
true
ifc
declares its ownequals(Object)
method,false
otherwise
-
hasCustomHashCode
Determines whether the given class has a customhashCode()
method distinct fromObject.hashCode()
.This method helps identify classes that rely on a specialized hashing algorithm, which can be relevant for certain comparison or hashing scenarios.
Usage Example:
Class<?> clazz = MyCustomClass.class; boolean hasCustomHashCode = hasCustomHashCodeMethod(clazz); System.out.println("Has custom hashCode(): " + hasCustomHashCode);
Notes:
-
A class is considered to have a custom
hashCode()
method if it declares its ownhashCode()
method that is not inherited directly fromObject
. -
This method does not consider interfaces or abstract classes unless they declare
a
hashCode()
method.
- Parameters:
c
- the class to inspect, must not benull
- Returns:
true
ifc
declares its ownhashCode()
method,false
otherwise- Throws:
IllegalArgumentException
- if the provided classc
isnull
- See Also:
-
A class is considered to have a custom
-
deepHashCode
Computes a deep hash code for the given object by traversing its entire graph.This method considers the hash codes of nested objects, arrays, maps, and collections, and uses cyclic reference detection to avoid infinite loops.
While deepHashCode() enables O(n) comparison performance in DeepEquals() when comparing unordered collections and maps, it does not guarantee that objects which are deepEquals() will have matching deepHashCode() values. This design choice allows for optimized performance while maintaining correctness of equality comparisons.
You can use it for generating your own hashCodes() on complex items, but understand that it *always* calls an instants hashCode() method if it has one that override's the hashCode() method defined on Object.class.
- Parameters:
obj
- the object to hash, may benull
- Returns:
- an integer representing the object's deep hash code
-