A hash map is a data structure that uses a hash function to map keys to their associated values. A hash map consists of key-value pairs. The keys in a hash map must be unique, but the values do not have to be. A simple example of a hash map is a collection of employees (keys) and their managers (values). Each employee is unique, but employees can have the same manager. The ordering of elements in a hash map is not important. Hash maps are efficient, and allow faster access to elements than other types of collections.

OpenEdge provides a built-in HashMap collection data structure in the Progress.Collections framework. This diagram shows the classes and interfaces relevant to a HashMap.

This table describes the classes and interfaces for HashMap. (For collection-specific interfaces, see Collections in object-oriented ABL.)

Class or interface Description
Progress.Collections.HashMap<K,V> class Represents a strongly-typed collection of key-value pairs (map) backed by a hash table.
Progress.Collections.HashMapIterator<K,V> class Represents the type of the object returned by the GetIterator() method in a HashMap<K,V> object, allowing traversal of the elements in the HashMap.
Progress.Collections.IEqualityComparer<T> interface Provides an alternate implementation of HashCode() and Equals() to the map collection object.
Progress.Collections.IHashable interface Provides the implementation for generating the hash code and is also capable of identifying when a key is considered the same.
Progress.Collections.IMap<K,V> interface The root interface for map collection classes.
Progress.Collections.KeySet<K> class Class used by the Keys property of the HashMap to return a set containing all the keys in the HashMap instance.
Progress.Collections.KeySetIterator<K> class Represents the type of the object returned by the GetIterator() method in a KeySet<K> class, allowing traversal of the elements in the KeySet<K>.
Progress.Collections.KeyValuePair<K,V> class A class that holds a key-value pair for a Map collection.

How HashMap works

A HashMap relies on a hash function to compute an index (hash code) into an array of buckets, from which the desired value can be found. During lookup, the key is hashed, and the resulting hash indicates in which bucket the corresponding value is stored. The HashMap is able to hold keys and values as object-oriented types (that is, compatible with Progress.Lang.Object). Scalar types are not supported. To ensure keys are unique, you must define how to determine when the objects used as keys are considered to be equal.

To summarize, the requirements for using a HashMap are:
  • Identify when a key is already in the collection, that is, compare the keys for equality.
  • Generate a hash code for the key.
The HashMap enforces these requirements via an equality comparer. It provides a default equality comparer, which requires that the key object implement the IHashable interface. IHashable defines methods for getting the hash code for the key (HashCode() method), and for determining if two keys are the same (Equals() method). For cases where the class used for the key cannot be changed or may not provide the desired behavior, you can define a custom equality comparer. To define a custom equality comparer, you use a class that implements the IEqualityComparer<T> interface. This interface also provides methods for getting the hash code and for determining if two keys are the same. You can specify an instance of a custom equality comparer when a new instance of the HashMap is created.
Note: The default equality comparer does not allow unknown, or invalid reference values as a key. However, the custom equality comparer does. It can define the behavior for unknown values and decide if unknowns are allowed or not.

The HashMap is responsible for maintaining the uniqueness of the keys in the collection, but it does not define the uniqueness of the key. That job is the responsibility of the hashable object (that is, the key object that implements IHashable), or the custom equality comparer, and any issues in those components impact the ability of the HashMap to enforce uniqueness.

OpenEdge provides a built-in HASH-CODE function, which you can use in your HashCode() implementation, to return a hash code. This function is intended to be used with HashMaps.

The KeyValuePair<K,V> class is used to represent the collection of key-value pairs. When you iterate through the HashMap, you get instances of KeyValuePair<K,V>, which are the elements of the collection.

The following behavior also apply to the HashMap implementation:
  • Supports any object-oriented ABL object, including .NET objects. However, you must specify a custom equality comparer (by implementing the IEqualityComparer<T> interface) when adding .NET objects to a HashMap.
  • Duplicate keys are not allowed. This is dependent on the equality comparer implementation (whether using the default or a custom one). If the Equals() method returns TRUE, a key object is considered to be in the map already, and is not added again, that is, the existing key-value pair remains in the map.
  • The map does not guarantee any ordering for the key-value pairs, and there is no guarantee the order will remain constant over time (due to rehashing).
  • All the methods that take a key as a parameter determine the hash code and equality of the key based on the equality comparer implementation (whether using the default or a custom one).
  • The HashMap has a reference to any key and value objects added to it, and that prevents garbage collection of such added objects until the reference no longer exists.
  • Changing the key object's value of an existing element is not supported and may lead to undefined behavior (that is, the uniqueness of the map is not guaranteed).
  • It is not safe to delete key objects that are in the HashMap. Doing so potentially invalidates the complete collection as it may no longer be able to guarantee consistency or uniqueness of the HashMap.
  • If a deleted object reference is encountered during any operation that needs to access properties of the element or invoke a method on that element, the operation fails, and an error is raised indicating that a deleted object reference was encountered. That includes the equality comparer object, if one was specified for the HashMap.

Serialization and remote parameter passing

A HashMap object can be serialized using the present forms of serialization, which include binary and JSON serialization, via the built-in Progress.IO.BinarySerializer and Progress.IO.JsonSerializer classes, respectively.

You can also pass such objects to, or from, a Progress Application Server (PAS) for OpenEdge remote call, if both sides are running version 12.7.

Serialization and remote parameter passing for object-oriented ABL objects require that all objects are marked as SERIALIZABLE. Therefore, the objects inside the collection also need to be SERIALIZABLE, which includes the key and value objects in the collection, otherwise an error is raised during object serialization. This includes the equality comparer object, if one is specified when the HashMap object was created. That means that if the equality comparer object is not serializable, you cannot serialize the HashMap object either.

Example: HashMap collection

The following example shows how to create, manage, and iterate over a HashMap. The example uses an Employee class (shown) and assumes a class called Manager (not shown).
Employee.cls
CLASS Employee IMPLEMENTS Progress.Collections.IHashable:

  CONSTRUCTOR Employee(pcID AS CHARACTER):
    EmployeeID = pcID.
  END.

  DEFINE PROPERTY EmployeeID AS CHARACTER GET. PRIVATE SET.

  METHOD PUBLIC INTEGER HashCode(). 
    // Hash on the value of property EmployeeID
    RETURN HASH-CODE(THIS-OBJECT:EmployeeID).
  END.

  METHOD PUBLIC OVERRIDE LOGICAL Equals(otherObj AS Progress.Lang.Object): 
    IF NOT VALID-OBJECT(otherObj) OR
      otherObj:GetClass() NE THIS-OBJECT:GetClass() THEN
      RETURN FALSE.

     // It’s up to the implementation to decide if case-sensitiveness
     // is required or not. This is case-insensitive.
     IF THIS-OBJECT:EmployeeID = CAST(otherObj, Employee):EmployeeID
     THEN
       RETURN TRUE.
     RETURN FALSE.
  END.
  
END.
VAR Progress.Collections.IMap<Employee,Manager> employeeMap.
VAR Progress.Collections.HashMapIterator<Employee,Manager> iterator.
VAR Employee employee1.
VAR Manager  manager1.
VAR Progres.Collections.KeyValuePair<Employee,Manager> pair.

employeeMap = NEW Progress.Collections.HashMasp<Employee,Manager>().

// Add 4 key-value pairs to the collection
employeeMap:Add(NEW Employee("thomas1","Thomas"), NEW Manager("George")).
employeeMap:Add(NEW Employee("paul1", "Paul "), NEW Manager("George")).
employeeMap:Add(NEW Employee("george1", "George"), NEW Manager("John")).
employeeMap:Add(NEW Employee("john1", "John"), NEW Manager("Tim")).

// Same key will not be added to the collection
IF employeeMap:Add(NEW Employee("george1", "George"), NEW Manager("bruce1","Bruce")) EQ FALSE THEN
   MESSAGE "George is already in the map".

// Print out the number of pairs in the set (4)
MESSAGE "Count:" employeeMap:Count.

employee1 = NEW Employee("paul1", "Paul").
// Remove the pair using a given key if it exists
IF employeeMap:ContainsKey(employee1) THEN 
   employeeMap:Remove(employee1).

// John now reports to David
manager1 = NEW Manager("john1", "John").
employeeMap:Set(manager1, NEW Manager("david1", "David")).

// Using a KeyValuePair is also allowed
// Paul was removed above. Add Paul reporting to John
pair = NEW Progress.Collections.KeyValuePair<Employee,Manager>(employee1,
           manager1). 

IF employeeMap:Add(pair1) EQ FALSE THEN
   MESSAGE "Paul is already in the map".

// If hashmap contains key and value in pair, then remove it
pair = NEW Progress.Collections.KeyValuePair<Employee,Manager>(employee1,
           NEW Manager("david1", "David")).
// This won’t find it or remove it because there is no pair 
// with Paul and David.
IF employeeMap:Contains(pair) THEN 
   employeeMap:Remove(pair).

// Iterate over the entries in the map
// which should show the following in no particular order
// Thomas -> George
// Paul -> John
// George -> John
// John -> David
iterator = CAST(employeeMap:GetIterator() 
           Progress.Collections.HashMapIterator<Employee,Manager>).
REPEAT WHILE iterator:MoveNext():
MESSAGE iterator:Current:Key:FullName “->” 
        iterator:Current:Value:FullName.
END.