Checker EqualsHashCode

belongs to group Basic
Identify incorrect definitions of equals()/hashCode()

Frameworks supported by this checker

  • java up to 11
  • android up to API level 28
  • dotnet

Warnings generated by this checker

  • EqualsNotAgainstObjectWarning: equals() is defined against a non-object class [ CWE227 ]
  • NoEqualsWarning: the equals() method seems needed [ CWE581 ]
  • NoHashCodeWarning: the hashCode() method seems needed [ CWE581 ]
  • SuspiciousInheritanceOfEqualsWarning: equals() is inherited but extra fields have been added [ CWE187 ]

Options accepted by this checker

  • none

Annotations understood by this checker

  • @com.juliasoft.julia.checkers.equalsHashCode.ImplementsEqualsThatChecksAllFields
  • @com.juliasoft.julia.checkers.equalsHashCode.IrrelevantForEquals


Description

Redefinitions of the equals()/hashCode() methods from java.lang.Object must be consistent in the sense that, for instance, if two objects are equals() then hashCode() must yield the same value on both. The validity of such a property is in general undecidable, but most incorrect definitions amount to simple cases, where one of the two methods is missing. However, it is often correct to redefine only one of those methods and Julia is aware of many such situations.

Inconsistent definitions of equals()/hashCode() induce unexpected behaviors when objects are put inside most collection classes of the standard Java library.

Action: make equals() consistent with hashCode(). In many cases, this just amounts to provide the missing definition for one of them.

Redefinitions of the Equals()/GetHashCode() methods from System.Object must be consistent in the sense that, for instance, if two objects are Equals() then GetHashCode() must yield the same value on both. The validity of such a property is in general undecidable, but most incorrect definitions amount to simple cases, where one of the two methods is missing. However, it is often correct to redefine only one of those methods and Julia is aware of many such situations.

Inconsistent definitions of Equals()/GetHashCode() induce unexpected behaviors when objects are put inside most collection classes of the standard .NET library.

Action: make Equals() consistent with GetHashCode(). In many cases, this just amounts to provide the missing definition for one of them.

Examples

Consider the following classes:

public class EqualsHashCode {
  private final String name;

  public EqualsHashCode(String name) {
    this.name = name;
  }

  @Override
  public boolean equals(Object other) {
    return other instanceof EqualsHashCode && ((EqualsHashCode) other).name.equals(name);
  }
}
class B {
  private final String name;

  public B(String name) {
    this.name = name;
  }

  @Override
  public int hashCode() {
    return name.hashCode();
  }

  @Override
  public boolean equals(Object other) {
    return other instanceof B && ((B) other).name.equals(name);
  }
}

and

class C extends B {
  private final int age;

  public C(String name, int age) {
    super(name);

    this.age = age;
  }

  @Override
  public boolean equals(Object other) {
    return super.equals(other) && other instanceof C && ((C) other).age == age;
  }
}

This checker issues the following warning:

EqualsHashCode.java:10: [EqualsHashCode: NoHashCodeWarning] equals(Object) defined here, but no hashCode()

since the missing hashCode() allows one to create two instances of EqualsHashCode that are equals() but have distinct hashCode(). Note that there is no warning related to the missing definition of hashCode() inside class C, since the definition of equals() inside C requires equality wrt the superclass B and hence entails the identity of the hashcodes; then, it is correct to miss that definition of hashCode().

In this example, the programmer should add the following definition (or similar) to class EqualsHashCode:

@Override
public int hashCode() {
  return name.hashCode();
}
Adding fields while redefining classes

If a class defines its equals() method and a subclass adds an instance field without redefining equals(), this checker will issue a warning since the extra field should likely be included in the test of equals(). That might however not be the case, if for instance the value of the extra field is the same when two objects are equals(). In that case, it is possible to annotate the extra field with @IrrelevantForEquals, so that this checker will not issue any more warning for that situation.

Consider the following classes:


namespace DocumentationExamples
{


    public class EqualsHashCode
    {
        public static void Main(string[] args)
        { } 

        private readonly string name;
        public EqualsHashCode(string name)
        {
            this.name = name;
        }
        public override bool Equals(object other)
        {
            return other is EqualsHashCode && ((EqualsHashCode)other).name.Equals(name);
        }
    }

    public class EqualsHashCodeB
    {
        private readonly string name;
        public EqualsHashCodeB(string name)
        {
            this.name = name;
        }
        public override int GetHashCode()
        {
            return name.GetHashCode();
        }
        public override bool Equals(object other)
        {
            return other is EqualsHashCodeB && ((EqualsHashCodeB)other).name.Equals(name);
        }
    }
    public class EqualsHashCodeC : EqualsHashCodeB
    {
        private readonly int age;
        public EqualsHashCodeC(string name, int age) : base(name)
        {
            this.age = age;
        }
        public override bool Equals(object other)
        {
            return base.Equals(other) && other is EqualsHashCodeC && ((EqualsHashCodeC)other).age == age;
        }
    }
}

This checker issues the following warning:

DocumentationExamples.cs:17: [EqualsHashCode: NoHashCodeWarning] No method "GetHashCode" is paired to this method "Equals" of class "EqualsHashCode"

since the missing GetHashCode() allows one to create two instances of EqualsHashCode that are equals() but have distinct GetHashCode(). Note that there is no warning related to the missing definition of GetHashCode() inside class C, since the definition of equals() inside C requires equality wrt the superclass B and hence entails the identity of the hashcodes; then, it is correct to miss that definition of GetHashCode().

In this example, the programmer should add the following definition (or similar) to class EqualsHashCode:

public override int GetHashCode() {
	return name.GetHashCode();
}
Adding fields while redefining classes

If a class defines its Equals() method and a subclass adds an instance field without redefining Equals(), this checker will issue a warning since the extra field should likely be included in the test of equals(). That might however not be the case, if for instance the value of the extra field is the same when two objects are Equals(). In that case, it is possible to annotate the extra field with [IrrelevantForEquals], so that this checker will not issue any more warning for that situation.