Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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]\]:

...

Code Block
bgColor#FFCCCC
// 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>();
// ...

Use of the Employee object as a key to the map is insecure because the properties of the object may could change after an ordering has been established. For example, a client may could modify the name field when the last name of an employee changes. As a result, clients may would observe non-deterministic nondeterministic behavior.

Compliant Solution

...

Code Block
bgColor#ccccff
// 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>();
// ...

...

Wiki Markup
This noncompliant code example uses the {{KeyMyKey}} class as the key index for the {{Hashtable}}. The {{KeyMyKey}} class may or may not override overrides {{Object.equals()}}, but ituses distinctly does not overridethe default {{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 the equals method.

This noncompliant code example follows the above that 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
bgColor#FFcccc
class KeyMyKey implements Serializable {
  // Does not override hashCode()
}

class HashSer {
  public static void main(String[] args)
                     throws IOException, ClassNotFoundException {
    Hashtable<KeyHashtable<MyKey,String> ht = new Hashtable<KeyHashtable<MyKey, String>();
    KeyMyKey key = new KeyMyKey();
    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<KeyHashtable<MyKey, String> ht_in =
 (Hashtable<Key       (Hashtable<MyKey, 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.");
  }
}

...

Code Block
bgColor#ccccff
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 could also be have been avoided by overriding the the hashcode() method in the Key MyKey class, though it is best to avoid serializing hash tables that are known to use implementation-defined parameters.

...

Failure to ensure that the keys used in a comparison operation are immutable can lead to non-deterministic nondeterministic behavior.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

MET11-J

low

probable

high

P2

L3

Automated Detection

The Coverity Prevent Version 5.0 MUTABLE_COMPARISON checker Some available static analysis tools can detect the instances where the compareTo() method is reading from a non-constant nonconstant field. If the non-constant nonconstant field is were modified, the value of compareTo() might change, which may could break program invariants.

...

<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="c6639280d6f8e836-2f5ae77c-45d246d5-b9589873-ddd41e8633a7806e6e2a7246"><ac:plain-text-body><![CDATA[

[[API 2006

AA. Bibliography#API 06]]

java.util.Interface Set<E> and java.util.Interface Map<K,V>

]]></ac:plain-text-body></ac:structured-macro>

...