Make Matrix equals and hashCode obey the Object contract - #1945
Merged
kevinherron merged 2 commits intoSep 7, 2026
Conversation
Matrix.hashCode hashed the elements with Objects.hash(flatArray, dataType). flatArray is declared Object, so for an array that takes the identity hash rather than the contents, and two Matrices that are equal hash differently: a Matrix is not found in a HashSet and cannot be used as a HashMap key. A Variant or DataValue carrying a Matrix inherits this, because Variant.valueHash falls through to the value's own hashCode for anything that is not an array. equals had a second, related defect. The thisArray == thatArray shortcut returned true before dimensions and dataType were compared, so two Matrices sharing a backing array compared equal even with different dimensions or a different data type, while hashCode did account for both. That is the same contract violation from the other side: equal values, unequal hashes. Matrix does not copy the elements it is given, so a shared backing array is reachable through the public Matrix(Object, int[]) constructors. Hash the elements by value, boxing primitive arrays first so that a Matrix of primitives hashes the same as the equal Matrix of the boxed type, which is how equals already compares them. Compare dimensions and dataType up front in equals, which also removes the duplicated comparison from both branches. Co-authored-by: Oleksandr Klymenko <19151554+alxkm@users.noreply.github.com> Signed-off-by: alxkm <alexanderklmn@gmail.com>
Hash primitive arrays directly while preserving equality and hash compatibility with boxed arrays. This avoids allocating a boxed copy on every Matrix hash calculation. Cover floating-point edge cases, nested values, and hash lookups through Variant and DataValue. Document backing-array ownership and the need to keep shared values unchanged and nested values acyclic.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Matrixviolates theObject.equals/Object.hashCodecontract in two ways, so an equalMatrixis not found in aHashSetand cannot be used as aHashMapkey.VariantandDataValuecarrying aMatrixinherit it, becauseVariant.valueHash()falls through to thevalue's own
hashCode()for anything that is not an array.1.
hashCode()hashes the array identity, not its contentsObjects.hash(flatArray, dataType)callsflatArray.hashCode(), andflatArrayis declaredObject, so for a primitive or object array that is the identity hash:equals()boxes primitive arrays before comparing, sonew Matrix(int[][])andnew Matrix(Integer[][])are equal (pinned by the existingprimitiveBoxedEqualitytest) butalso hash differently today.
2.
equals()shortcuts pastdimensionsanddataTypeThe
thisArray == thatArrayshortcut returnstruebefore either is compared, whilehashCode()does account for both — the same contract violation from the other side:Matrixdoes not copy the elements it is given andgetElements()hands the backing array outdirectly, so a shared backing array is reachable through the public
Matrix(Object, int[])constructors.
The two are entangled: fixing only
hashCode()leaves case 2 violating the contract, and fixingonly
equals()leaves case 1. Neither half restores it on its own, so both are here.Change
Matrixof primitives hashes thesame as the equal
Matrixof the boxed type, matching howequals()compares them.Arrays.deepHashCode(box(prim[]))equalsArrays.hashCode(prim[])for all eight primitivetypes, so this is consistent in both directions.
dataTypein the hash alongsidedimensions, both of whichequals()compares.dimensionsanddataTypeup front inequals(), which also removes the duplicatedcomparison from its two branches.
No behaviour change for unequal matrices, and no in-tree caller relies on the old behaviour.
Tests
Four tests added to
MatrixTest. All four fail before the change and pass after:Verified on JDK 17 with
mvn -pl opc-ua-sdk/integration-tests -am test: stack-core, transport,sdk-core, sdk-client, dtd-core, dtd-reader, sdk-server, encoding-json, encoding-xml and the
integration tests all pass — 2283 tests, 0 failures, 0 errors.
mvn -pl opc-ua-stack/stack-core verifypasses, includingspotless:check.