When designing types to be reused by others, there are reasons to favor interfaces over abstract classes.One complication of using an interface-based approach stems from defining reasonable behavior for the equals and hashCode methods, especially if different implementations are intended to play well together when used in data structures like collections, in particular if an interface type is meant to serve as the key of a map or as the element type of a set.

Some interfaces, like CharSequence, are designed to not be a usable type for a map key or an element type of a set:
[The CharSequence] interface does not refine the general contracts of the equals and hashCode methods. The result of comparing two objects that implement CharSequence is therefore, in general, undefined. Each object may be implemented by a different class, and there is no guarantee that each class will be capable of testing its instances for equality with those of the other. It is therefore inappropriate to use arbitrary CharSequence instances as elements in a set or as keys in a map.

Amongst other problems, CharSequences are not required to be immutable so in general there are always hazards from time of check to time of use conditions.

Even if a type is not suitable as a map key, it can be fine as the type of the value to which a key gets mapped. Likewise, even if type cannot serve as the element type of a set, it can often still be perfectly fine as the element type of a list.

Expanding on a slide from my JavaOne talk Tips and Tricks for Using Language Features in API Design and Implementation, for interface types intended to be used as map keys or set elements, equality can be defined in several ways.First, equality can be defined solely in terms of information retrievable from methods of the interface. Alternatively, equality can be defined in terms of information retrievable via the interface methods as well as additional information. Finally, object identity (the == relation) is always a valid definition for equals and often a good implementation choice.

An example of the first kind of equality definition is specified for annotation types:
java.lang.annotation.Annotation.equals(Object):
Returns true if the specified object represents an annotation that is logically equivalent to this one. In other words, returns true if the specified object is an instance of the same annotation type as this instance, all of whose members are equal to the corresponding member of this annotation, as defined below: ...

java.lang.annotation.Annotation.hashCode():
Returns the hash code of this annotation, as defined below:
The hash code of an annotation is the sum of the hash codes of its members (including those with default values), as defined below:...

A consequence of defining equality in this manner is that the hashCode algorithm must also be specified. If it were not specified, the equals/hashCode contract would be violated since equal objects must have equal hashCodes. Therefore, different implementations of this style of interface must have enough information to implement the equals method and have a precise algorithm for hashCode.

An annotation type is a kind of interface. At runtime, dynamic proxies are used to create the core reflection objects implementing annotation types, such as the objects returned by thegetAnnotation method. After a quick identity check, the equals algorithm used in the proxy sees if the annotation type of the two annotation objects is the same and then compares the results of the annotation type's methods. This indirection allows the annotation objects from core reflection to interact properly with other implementations of annotation objects. The annotation objects generated for annotation processing in apt and javac both use the same underlying implementation as core reflection. However, completely independent annotation implementations are fine too. For example, the code below
import javax.annotation.processing.*;import javax.lang.model.SourceVersion;import java.lang.annotation.*;import java.lang.reflect.*;import java.util.*;/** * Demonstrate equality of different annotation implementations. */@SupportedSourceVersion(SourceVersion.RELEASE_6)pu blic class AnnotationEqualityDemonstration { static class MySupportedSourceVersion implements SupportedSourceVersion { private final SourceVersion sourceVersion; private MySupportedSourceVersion(SourceVersion sourceVersion) { this.sourceVersion = sourceVersion; } public Class annotationType() { return SupportedSourceVersion.class; } public SourceVersion value() { return sourceVersion; } public boolean equals(Object o) { if (o instanceof SupportedSourceVersion) { SupportedSourceVersion ssv = (SupportedSourceVersion) o; return ssv.value() == sourceVersion; } return false; } public int hashCode() { return (127 * "value".hashCode()) ^ sourceVersion.hashCode(); } } public static void main(String... args) { SupportedSourceVersion reflectSSV = AnnotationEqualityDemonstration.class.getAnnotatio n(SupportedSourceVersion.class); SupportedSourceVersion localSSV = new MySupportedSourceVersion(reflectSSV.value()); System.out.println("reflectSSV == localSSV is " + (reflectSSV == localSSV)); System.out.println("reflectSSV.equals(localSSV) is " + reflectSSV.equals(localSSV)); System.out.println("localSSV.equals(reflectSSV) is " + localSSV.equals(reflectSSV)); System.out.println("reflectSSV.getClass()equals(localSSV.getClass()) is " + reflectSSV.getClass().equals(localSSV.getClass())) ; System.out.println("\nreflectSSV.hashCode() is " + reflectSSV.hashCode()); System.out.println("localSSV.hashCode() is " + localSSV.hashCode()); }}
when run outputs:
reflectSSV == localSSV is falsereflectSSV.equals(localSSV) is truelocalSSV.equals(reflectSSV) is truereflectSSV.getClass()equals(localSSV.getClass( )) is falsereflectSSV.hashCode() is 1867635603localSSV.hashCode() is 1867635603
The second kind of equality definition is specified for the language modeling interfaces in the javax.lang.model.element package:
javax.lang.model.element.Element.equals(Object):
Note that the identity of an element involves implicit state not directly accessible from the element's methods, including state about the presence of unrelated types. Element objects created by different implementations of these interfaces should not be expected to be equal even if "the same" element is being modeled; this is analogous to the inequality of Class objects for the same class file loaded through different class loaders.

Inside javac, instance control is used for the implementation classes for javax.lang.model.element.Element subtypes. This allows the default pointer equality to be used and allows the hashing algorithm to not be specified. Just as you can't step in the same river twice, the identity of an Element object is tied to the context in which it is created. Operationally, one consequence of this context sensitivity is that Element objects modeling "the same" type produced during different rounds of annotation processing will not be equal even if there are equivalent methods, fields, constructors, etc. in both types in both rounds.

When independent implementations of an interface and not required to be equal to one another, the hashCode algorithm does not need to be specified, providing the implementer more flexibility. This second style of specification allows disjoint islands of implementations to be defined.

Which style of specification is more appropriate depends on how the interface type is intended to be used. Defining interoperable implementation is more difficult and limits the ability of the interface to be retrofitted onto existing types. For example, while the Element interface and other interfaces from JSR 269 were successfully implemented by classes in both javac and Eclipse, it would be impractical to expect Element objects from those disparate implementations to compare as equal.Mixin interfaces, like CharSequence and Closeable, should be cautious in defining equals behavior if the interface is intended to be widely implemented. In some cases, a mixin interface can finesse this issue by being limited to an existing type hierarchy with already defined equals and hashCode polices. For example, the Parameterizable and QualifiedNameable interfaces added to the javax.lang.model.element package in JDK 7 (6460529) are extensions to javax.lang.model.Element and therefore get to reuse the existing policies quoted above.



More...