Understanding Static vs Instance Members and Proper equals/hashCode Implementation
Static vs Instance Variables and Methods
In object-oriented languages like Java, static members belong to the class itself rather than any specific instance. A static variable is initialized when the class is loaded and shared across all instances. In cotnrast, instance (non-static) variables are created separate for each object upon instantiation.
Consider this example:
public class Example {
private static String classField = "Shared";
private String instanceField;
public Example(String value) {
this.instanceField = value;
}
public static void main(String[] args) {
Example obj = new Example("Unique");
System.out.println(obj.classField); // Warning: accessing static via instance
// System.out.println(Example.instanceField); // Compile error: non-static field
}
}
Accessing a static field through an instance reference is discouraged and generates a warning. Conversely, referencing a non-static field from a static context (like main) results in a compilation error.
A common mistake is declaring collection fields as static when they should be per-instance. For example, in a graph class meant to represent individual friendship networks, using a static Set<String> to store nodes causes all instances to share the same set—leading to test contamination or incorrect behavior when multiple graphs exist concurrently. The fix is to remove static, ensuring each graph maintains its own node set.
Regarding methods:
staticmethods can only directly access otherstaticmembers and cannot usethisorsuper.- Instance methods can access both
staticand non-static members. - The
mainmethod must bestaticso the JVM can invoke it without creating an instance.
Implementing equals and hashCode
The default Object.equals() uses reference equality (==). To enable logical equality based on content, override equals. However, if two objects are equal according to equals, they must return the same hashCode. Violating this breaks hash-based collections like HashMap or HashSet.
Only overriding equals works functionally but harms performence in large collections, as hash codes enable fast bucket lookups before invoking equals. Only overriding hashCode is invalid—it violates the contract that equal objects must have equal hash codes.
A standard approach for hashCode uses prime multiplication (commonly 31):
@Override
public int hashCode() {
final int prime = 31;
int hash = 1;
hash = prime * hash + (field1 == null ? 0 : field1.hashCode());
hash = prime * hash + (field2 == null ? 0 : field2.hashCode());
return hash;
}
This leverages the fact that 31 allows efficient computation via bit shifts (31 * n == (n << 5) - n) and reduces collision likelihood due to its primality.
Override both methods only when object equality matters—e.g., for Person objects used as map keys—but skip them for classes like FriendshipGraph if graph comparison isn’t required.