com.io7m.jequality 2.0.0 Documentation
Package Information
Orientation
Overview
The com.io7m.jequality package implements a set of functions and types for dealing with equality in Java.
Installation
Source compilation
The project can be compiled and installed with Maven:
$ mvn -C clean install
Maven
Regular releases are made to the Central Repository, so it's possible to use the com.io7m.jequality package in your projects with the following Maven dependency:
<dependency>
  <groupId>com.io7m.jequality</groupId>
  <artifactId>com.io7m.jequality-core</artifactId>
  <version>2.0.0</version>
</dependency>
All io7m.com packages use Semantic Versioning [0], which implies that it is always safe to use version ranges with an exclusive upper bound equal to the next major version - the API of the package will not change in a backwards-incompatible manner before the next major version.
Platform Specific Issues
There are currently no known platform-specific issues.
License
All files distributed with the com.io7m.jequality package are placed under the following license:
Copyright © 2014 <code@io7m.com> http://io7m.com

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
        
Usage
Annotations
Overview
In Java, if a class does not override the equals method provided by the standard java.lang.Object type, then the class effectively has reference equality. That is, the equals method simply compares the addresses of the operands. In some cases this is desirable; in most cases it is not. It is very easy to forget to override an equals method, leading to extremely subtle and dangerous bugs.
The com.io7m.jequality package provides a set of annotations and a validator to check that classes conform to their annotations. Essentially, the programmer annotates a class with an annotation that states that the class is expected to implement either structural or reference equality. A class is assumed to implement structural equality iff it overrides equals. The provided validator checks that classes are annotated, and that they implement the type of equality that their annotations claim. As an extra safety check, the validator checks that if equals is overridden, then hashCode is also overridden in the same class. The validator is not capable of checking that an overridden equals actually does implement structural equality (and does not, for example, simply delegate to java.lang.Object). Solving this problem is undecidable in general.
Validation
Validation of classes is expected to occur in the test suite of the project using the com.io7m.jequality package.
As an example:
@EqualityReference public final class RefEquality
{

}

public final class RefEqualityTests
{
  @Test public void testEquality()
  {
    Assert.assertEquals(ValidatorResult.VALIDATION_OK, EqualityValidator
      .validateClass(
        RefEquality.class,
        AnnotationRequirement.ANNOTATIONS_REQUIRED,
        true));
  }
}
The RefEquality class uses the EqualityReference annotation to indicate that that it implements reference equality. The validator checks that this is indeed the case. The following test fails:
@EqualityReference public final class RefEqualityWrong
{
  @Override public boolean equals(
    final @Nullable Object other)
  {
    return false;
  }

  @Override public int hashCode()
  {
    return 0;
  }
}

public final class RefEqualityTests
{
  @Test public void testReferenceEqualityNot()
  {
    Assert.assertEquals(
      ValidatorResult.VALIDATION_ERROR_WANTED_REFERENCE_EQUALITY,
      EqualityValidator.validateClass(
        RefEqualityWrong.class,
        AnnotationRequirement.ANNOTATIONS_REQUIRED,
        true));
  }
}
The RefEqualityWrong class claims that it implements reference equality, and yet overrides equals. The validator will catch this and the test will fail.
@EqualityStructural public final class StructuralEquality
{
  @Override public boolean equals(
    final @Nullable Object other)
  {
    return false;
  }

  @Override public int hashCode()
  {
    return 0;
  }
}

public final class RefEqualityTests
{
  @Test public void testStructuralEquality()
  {
    Assert.assertEquals(ValidatorResult.VALIDATION_OK, EqualityValidator
      .validateClass(
        StructuralEquality.class,
        AnnotationRequirement.ANNOTATIONS_REQUIRED,
        true));
  }
}
The StructuralEquality class claims to implement structural equality and does appear to override equals (even though the equality relation it defines is not correct). The validator assumes that this class is correct.
Complaints
Handling of equality in Java is usually considered to be one of the worst design mistakes of the language. The problems are manifold. Firstly, the equals method is defined on the java.lang.Object type from which all Java classes are forced to inherit. This means that equality must be defined for all types, even those that do not have sensible equality relations [1]. If an object forgets to override equals, then the object is given reference equality by default. Secondly, the argument to the equals method is of type java.lang.Object, meaning that it's perfectly possible to query the equality of values of different types (which almost never makes logical sense). Thirdly, the equals method must be consistent with the associated hashCode method in order for the standard library collections to behave correctly.
Had Java been designed today, the correct way to design equals, assuming that java.lang.Object still existed, would be for java.lang.Object to declare neither equals nor hashCode. A static method same would be defined in java.lang.Object in order to compare the addresses of two values. The following interface types would be provided by the standard library:
public interface Equals<A>
{
  boolean equals(A x);
}

public interface Hashable<A extends Equals<A>>
{
  int hashCode();
}
Effectively, equality is only defined for objects that explicitly implement Equals. The Equals type implies structural equality. Objects can only be placed into collections that require hashing if they explicitly implement Hashable [2]. The invariance of generic parameters means that comparing objects that are of different types is statically prevented.
This is the approach taken by far safer languages such as Haskell. Unfortunately, it is too late to fix these major design flaws in Java.
Floating point
The com.io7m.jequality package implements the floating point comparison functions described in Comparing floating point numbers, 2012 edition.
API Reference
Javadoc
API documentation for the package is provided via the included Javadoc.

[1]
Consider the required equality relation for function types.
[2]
The constraint on the generic parameter of Hashable is intended to imply that all Hashable objects also have a defined equality relation. This could arguably be removed.