Wiki Markup |
---|
Objects that serve as keys in ordered sets and maps should be immutable. When some fields must be mutable, the {{equals()}}, {{hashCode()}} and {{compareTo()}} methods must consider only immutable state when comparing objects. Violations of this rule can produce inconsistent orderings in collections. The documentation of {{java.util.Interface Set<E>}} and {{java.util.Interface Map<K,V>}} warns against this. For example, the documentation for the Interface Map states \[[API 2006|AA. Bibliography#API 06]\]: |
Wiki Markup Note: great care must be exercised \[when\] mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects {{equals}} comparisons while the object is a key in the map. A special case of this prohibition is that it is not permissible for a map to contain itself as a key. While it is permissible for a map to contain itself as a value, extreme caution is advised: the {{equals}} and {{hashCode}} methods are no longer well defined on such a map.
Noncompliant Code Example
...
Code Block | ||
---|---|---|
| ||
// Mutable class Employee class Employee { private String name; private double salary; Employee(String empName, double empSalary) { this.name = empName; this.salary = empSalary; } public void setEmployeeName(String empName) { this.name = empName; } // ... including a hashCode implementation @Override public boolean equals(Object o) { if (!(o instanceof Employee)) { return false; } Employee emp = (Employee)o; return emp.name.equals(name); } } // Client code Map<Employee, Calendar> map = new ConcurrentHashMap<Employee, Calendar>(); // ... |
...
Code Block | ||
---|---|---|
| ||
// Mutable class Employee class Employee { private String name; private double salary; private final long employeeID; // Unique for each Employee Employee(String name, double salary, long empID) { this.name = name; this.salary = salary; this.employeeID = empID; } // ... including a hashCode implementation @Override public boolean equals(Object o) { if (!(o instanceof Employee)) { return false; } Employee emp = (Employee)o; return emp.employeeID == employeeID; } } // Client code remains same Map<Employee, Calendar> map = new ConcurrentHashMap<Employee, Calendar>(); // ... |
The Employee
class can now be safely used as a key for the map in the client code.
...
Wiki Markup |
---|
This noncompliant code example uses the {{Key}} class as the key index for the {{Hashtable}}. The {{Key}} class may or may not override {{Object.equals()}}, but it distinctly does not override {{Object.hashCode()}}. According to the Java API \[[API 2006|AA. Bibliography#API 06]\] class {{Hashtable}} documentation, |
To successfully store and retrieve objects from a hash table, the objects used as keys must implement the
hashCode
method and theequals
method.
This noncompliant code example follows the above advice , but can nevertheless fail after serialization and deserialization. Consequently, it may be impossible to retrieve the value of the object after deserialization by using the original key.
Code Block | ||
---|---|---|
| ||
class Key implements Serializable { // Does not override hashCode() } class HashSer { public static void main(String[] args) throws IOException, ClassNotFoundException { Hashtable<Key,String> ht = new Hashtable<Key, String>(); Key key = new Key(); ht.put(key, "Value"); System.out.println("Entry: " + ht.get(key)); // Retrieve using the key, works // Serialize the Hashtable object FileOutputStream fos = new FileOutputStream("hashdata.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(ht); oos.close(); // Deserialize the Hashtable object FileInputStream fis = new FileInputStream("hashdata.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Hashtable<Key, String> ht_in = (Hashtable<Key, String>)(ois.readObject()); ois.close(); if (ht_in.contains("Value")) // Check whether the object actually exists in the hash table System.out.println("Value was found in deserialized object."); if (ht_in.get(key) == null) // Gets printed System.out.println("Object was not found when retrieved using the key."); } } |
Compliant Solution
This compliant solution changes the type of the key value to be an Integer
object. Consequently, key values remain consistent across multiple runs of the program, across serialization and deserialization, and also across multiple JVMs.
Code Block | ||
---|---|---|
| ||
class HashSer { public static void main(String[] args) throws IOException, ClassNotFoundException { Hashtable<Integer, String> ht = new Hashtable<Integer, String>(); ht.put(new Integer(1), "Value"); System.out.println("Entry: " + ht.get(1)); // Retrieve using the key // Serialize the Hashtable object FileOutputStream fos = new FileOutputStream("hashdata.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(ht); oos.close(); // Deserialize the Hashtable object FileInputStream fis = new FileInputStream("hashdata.ser"); ObjectInputStream ois = new ObjectInputStream(fis); Hashtable<Integer, String> ht_in = (Hashtable<Integer, String>)(ois.readObject()); ois.close(); if (ht_in.contains("Value")) // Check whether the object actually exists in the Hashtable System.out.println("Value was found in deserialized object."); if (ht_in.get(1) == null) // Not printed System.out.println("Object was not found when retrieved using the key."); } } |
This problem can also be avoided by overriding the the hashcode()
method in the Key
class, though it is best to avoid serializing hash tables that are known to use implementation-defined parameters.
Risk Assessment
...
The Coverity Prevent Version 5.0 MUTABLE_COMPARISON checker can detect the instances where compareTo method is reading from a non-constant field. If the non-constant field is modified, the value of compareTo might change, which may break program invariants.
...
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="23d2d1ef1c830f70-4a94697d-4e474d81-9c99aefb-47e914678d7e72e093f10742"><ac:plain-text-body><![CDATA[ | [[API 2006 | AA. Bibliography#API 06]] | | ]]></ac:plain-text-body></ac:structured-macro> |
...