CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-apache-commons--commons-collections4

The Apache Commons Collections package contains types that extend and augment the Java Collections Framework

Pending
Overview
Eval results
Files

bidimaps.mddocs/

Bidirectional Maps (BidiMaps)

Bidirectional maps allow efficient lookup in both directions - from key to value and from value to key. They maintain 1:1 relationships between keys and values, ensuring that both keys and values are unique within the map.

Core Interfaces

BidiMap<K, V> Interface

The primary interface for bidirectional maps.

import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;

BidiMap<String, Integer> bidiMap = new DualHashBidiMap<>();

// Standard map operations (key -> value)
bidiMap.put("one", 1);
bidiMap.put("two", 2);
bidiMap.put("three", 3);

Integer value = bidiMap.get("two");           // Returns 2

// Reverse lookup operations (value -> key)  
String key = bidiMap.getKey(2);               // Returns "two"
String removedKey = bidiMap.removeValue(3);   // Removes and returns "three"

// Get inverse view
BidiMap<Integer, String> inverse = bidiMap.inverseBidiMap();
String keyFromInverse = inverse.get(1);       // Returns "one"

// Both views reflect the same underlying data
bidiMap.put("four", 4);
Integer newValue = inverse.get("four");       // Returns 4

OrderedBidiMap<K, V> Interface

A bidirectional map that maintains insertion order.

import org.apache.commons.collections4.OrderedBidiMap;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import org.apache.commons.collections4.OrderedMapIterator;

OrderedBidiMap<String, Integer> orderedBidi = new DualLinkedHashBidiMap<>();
orderedBidi.put("first", 1);
orderedBidi.put("second", 2);
orderedBidi.put("third", 3);

// Navigate in insertion order
String firstKey = orderedBidi.firstKey();     // Returns "first"
String lastKey = orderedBidi.lastKey();       // Returns "third"
String nextKey = orderedBidi.nextKey("first"); // Returns "second"
String prevKey = orderedBidi.previousKey("third"); // Returns "second"

// Ordered iteration
OrderedMapIterator<String, Integer> iterator = orderedBidi.mapIterator();
while (iterator.hasNext()) {
    String key = iterator.next();
    Integer value = iterator.getValue();
    System.out.println(key + " -> " + value);
}

SortedBidiMap<K, V> Interface

A bidirectional map that maintains elements in sorted order.

import org.apache.commons.collections4.SortedBidiMap;
import org.apache.commons.collections4.bidimap.DualTreeBidiMap;
import java.util.Comparator;

// Natural ordering
SortedBidiMap<String, Integer> sortedBidi = new DualTreeBidiMap<>();
sortedBidi.put("zebra", 26);
sortedBidi.put("alpha", 1);
sortedBidi.put("beta", 2);

// Elements are maintained in sorted order
String firstKey = sortedBidi.firstKey();      // Returns "alpha"  
String lastKey = sortedBidi.lastKey();        // Returns "zebra"

// Custom comparators for keys and values
Comparator<String> keyComp = Comparator.reverseOrder();
Comparator<Integer> valueComp = Comparator.naturalOrder();
SortedBidiMap<String, Integer> customSorted = new DualTreeBidiMap<>(keyComp, valueComp);

Concrete Implementations

DualHashBidiMap<K, V>

Hash-based implementation using two HashMap instances for O(1) performance.

import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import java.util.Map;
import java.util.HashMap;

// Create empty bidirectional map
DualHashBidiMap<String, String> countryCapital = new DualHashBidiMap<>();

// Populate with data
countryCapital.put("USA", "Washington");
countryCapital.put("France", "Paris");
countryCapital.put("Japan", "Tokyo");
countryCapital.put("Germany", "Berlin");

// Efficient bidirectional lookups
String capital = countryCapital.get("France");        // "Paris"
String country = countryCapital.getKey("Tokyo");      // "Japan"

// Create from existing map
Map<String, String> existingMap = new HashMap<>();
existingMap.put("Italy", "Rome");
existingMap.put("Spain", "Madrid");
DualHashBidiMap<String, String> fromMap = new DualHashBidiMap<>(existingMap);

// Duplicate value detection
try {
    countryCapital.put("UK", "Paris"); // Throws IllegalArgumentException
} catch (IllegalArgumentException e) {
    System.out.println("Paris is already mapped to France");
}

DualLinkedHashBidiMap<K, V>

LinkedHashMap-based implementation that maintains insertion order.

import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;

DualLinkedHashBidiMap<Integer, String> priorities = new DualLinkedHashBidiMap<>();

// Add in specific order
priorities.put(1, "High");
priorities.put(2, "Medium");  
priorities.put(3, "Low");

// Maintains insertion order during iteration
for (Map.Entry<Integer, String> entry : priorities.entrySet()) {
    System.out.println("Priority " + entry.getKey() + ": " + entry.getValue());
}
// Output: Priority 1: High, Priority 2: Medium, Priority 3: Low

// Inverse also maintains corresponding order
BidiMap<String, Integer> inversePriorities = priorities.inverseBidiMap();
for (String level : inversePriorities.keySet()) {
    System.out.println(level + " has priority " + inversePriorities.get(level));
}
// Output: High has priority 1, Medium has priority 2, Low has priority 3

DualTreeBidiMap<K, V>

TreeMap-based implementation that maintains sorted order for both keys and values.

import org.apache.commons.collections4.bidimap.DualTreeBidiMap;
import java.util.Comparator;

// Natural ordering for both keys and values
DualTreeBidiMap<String, Integer> grades = new DualTreeBidiMap<>();
grades.put("Alice", 95);
grades.put("Bob", 87);
grades.put("Charlie", 92);

// Keys sorted alphabetically
System.out.println("First student: " + grades.firstKey());    // "Alice"
System.out.println("Last student: " + grades.lastKey());      // "Charlie"

// Values also sorted in inverse map
SortedBidiMap<Integer, String> byGrades = grades.inverseBidiMap();
System.out.println("Lowest grade: " + byGrades.firstKey());   // 87
System.out.println("Highest grade: " + byGrades.lastKey());   // 95

// Custom comparators
Comparator<String> nameComp = (a, b) -> a.length() - b.length(); // By name length
Comparator<Integer> gradeComp = Comparator.reverseOrder();        // Descending grades
DualTreeBidiMap<String, Integer> customOrder = new DualTreeBidiMap<>(nameComp, gradeComp);

TreeBidiMap<K, V>

Red-black tree implementation for Comparable objects with excellent performance.

import org.apache.commons.collections4.bidimap.TreeBidiMap;
import java.time.LocalDate;
import java.util.Map;
import java.util.HashMap;

// For Comparable types
TreeBidiMap<LocalDate, String> events = new TreeBidiMap<>();
events.put(LocalDate.of(2024, 1, 1), "New Year");
events.put(LocalDate.of(2024, 7, 4), "Independence Day");
events.put(LocalDate.of(2024, 12, 25), "Christmas");

// Sorted by date
LocalDate firstDate = events.firstKey();      // 2024-01-01
LocalDate lastDate = events.lastKey();        // 2024-12-25

// Create from existing map
Map<String, Integer> scoreMap = new HashMap<>();
scoreMap.put("Alpha", 100);
scoreMap.put("Beta", 85);
scoreMap.put("Gamma", 92);
TreeBidiMap<String, Integer> scores = new TreeBidiMap<>(scoreMap);

Bidirectional Map Operations

Unique Key-Value Constraints

BidiMaps enforce 1:1 relationships between keys and values.

BidiMap<String, String> usernames = new DualHashBidiMap<>();

// Initial mapping
usernames.put("john.doe", "John Doe");
usernames.put("jane.smith", "Jane Smith");

// Attempting to add duplicate value removes existing mapping
String oldKey = usernames.put("j.doe", "John Doe"); // oldKey is null
// Now "John Doe" maps to "j.doe", "john.doe" mapping is removed

System.out.println(usernames.getKey("John Doe"));  // Returns "j.doe"
System.out.println(usernames.get("john.doe"));     // Returns null

// Using putAll with conflicting values
Map<String, String> newMappings = Map.of(
    "admin", "Administrator",
    "jane.doe", "Jane Smith"  // Conflicts with existing value
);
usernames.putAll(newMappings);
// "Jane Smith" now maps to "jane.doe", "jane.smith" is removed

Inverse Map Views

Inverse maps provide a flipped view of the same underlying data.

BidiMap<String, Integer> original = new DualHashBidiMap<>();
original.put("A", 1);
original.put("B", 2);

BidiMap<Integer, String> inverse = original.inverseBidiMap();

// Both views share the same data
System.out.println(original.size());           // 2
System.out.println(inverse.size());            // 2

// Modifications through inverse affect original
inverse.put(3, "C");
System.out.println(original.get("C"));        // Returns 3

// Removing through inverse
String removed = inverse.remove(2);            // Returns "B"
System.out.println(original.containsKey("B")); // false

// Getting inverse of inverse returns original
BidiMap<String, Integer> doubleInverse = inverse.inverseBidiMap();
System.out.println(doubleInverse == original); // true

MapIterator Usage

Efficient iteration over bidirectional maps.

import org.apache.commons.collections4.MapIterator;

BidiMap<String, Double> stockPrices = new DualHashBidiMap<>();
stockPrices.put("AAPL", 150.25);
stockPrices.put("GOOGL", 2750.80);
stockPrices.put("MSFT", 305.15);

// Iterate with MapIterator
MapIterator<String, Double> iterator = stockPrices.mapIterator();
while (iterator.hasNext()) {
    String symbol = iterator.next();
    Double price = iterator.getValue();
    
    // Update prices with 5% increase  
    iterator.setValue(price * 1.05);
    
    System.out.println(symbol + ": $" + iterator.getValue());
}

// Iterate inverse map
MapIterator<Double, String> priceIterator = stockPrices.inverseBidiMap().mapIterator();
while (priceIterator.hasNext()) {
    Double price = priceIterator.next();
    String symbol = priceIterator.getValue();
    System.out.println("$" + price + " -> " + symbol);
}

Advanced Usage Patterns

Caching and Lookup Tables

public class UserManager {
    private final BidiMap<String, Long> usernameToId = new DualHashBidiMap<>();
    
    public void registerUser(String username, Long userId) {
        // BidiMap ensures both username and userId are unique
        usernameToId.put(username, userId);
    }
    
    public Long getUserId(String username) {
        return usernameToId.get(username);
    }
    
    public String getUsername(Long userId) {
        return usernameToId.getKey(userId);
    }
    
    public boolean isUsernameTaken(String username) {
        return usernameToId.containsKey(username);
    }
    
    public boolean isUserIdTaken(Long userId) {
        return usernameToId.containsValue(userId);
    }
    
    public void deleteUser(String username) {
        usernameToId.remove(username);
    }
    
    public void deleteUserById(Long userId) {
        usernameToId.removeValue(userId);
    }
}

State Machine Mappings

public enum OrderState { PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED }
public enum StatusCode { 100, 200, 300, 400, 500 }

public class OrderStateMachine {
    private final BidiMap<OrderState, StatusCode> stateCodes = new DualHashBidiMap<>();
    
    public OrderStateMachine() {
        stateCodes.put(OrderState.PENDING, StatusCode.100);
        stateCodes.put(OrderState.CONFIRMED, StatusCode.200);
        stateCodes.put(OrderState.SHIPPED, StatusCode.300);
        stateCodes.put(OrderState.DELIVERED, StatusCode.400);
        stateCodes.put(OrderState.CANCELLED, StatusCode.500);
    }
    
    public StatusCode getStatusCode(OrderState state) {
        return stateCodes.get(state);
    }
    
    public OrderState getState(StatusCode code) {
        return stateCodes.getKey(code);
    }
    
    public Set<OrderState> getAllStates() {
        return stateCodes.keySet();
    }
    
    public Set<StatusCode> getAllCodes() {
        return stateCodes.values();
    }
}

Configuration Management

public class ConfigurationManager {
    private final OrderedBidiMap<String, String> properties = new DualLinkedHashBidiMap<>();
    
    public void loadConfiguration(Properties props) {
        props.forEach((key, value) -> properties.put(key.toString(), value.toString()));
    }
    
    public String getProperty(String key) {
        return properties.get(key);
    }
    
    public String getPropertyKey(String value) {
        return properties.getKey(value);
    }
    
    public void setProperty(String key, String value) {
        properties.put(key, value);
    }
    
    // Maintain insertion order for configuration display
    public Map<String, String> getAllProperties() {
        return new LinkedHashMap<>(properties);
    }
    
    // Find all keys with a specific value pattern
    public Set<String> getKeysForValue(String valuePattern) {
        return properties.entrySet().stream()
            .filter(entry -> entry.getValue().matches(valuePattern))
            .map(Map.Entry::getKey)
            .collect(Collectors.toSet());
    }
}

Performance Characteristics

Implementation Comparison

ImplementationKey LookupValue LookupOrderingMemory Overhead
DualHashBidiMapO(1)O(1)NoneLow
DualLinkedHashBidiMapO(1)O(1)InsertionMedium
DualTreeBidiMapO(log n)O(log n)SortedMedium
TreeBidiMapO(log n)O(log n)NaturalLow

Memory Considerations

// DualHashBidiMap uses two HashMap instances
BidiMap<String, Integer> hash = new DualHashBidiMap<>();
// Memory: ~2x HashMap overhead + entry objects

// TreeBidiMap uses single tree structure  
BidiMap<String, Integer> tree = new TreeBidiMap<>();
// Memory: ~1.5x TreeMap overhead + navigation pointers

// For memory-critical applications with large datasets
BidiMap<Integer, Integer> compact = new TreeBidiMap<>(); // More memory efficient
BidiMap<String, String> spacious = new DualHashBidiMap<>(); // Faster but more memory

Thread Safety

BidiMaps are not thread-safe by default. For concurrent access:

// Option 1: External synchronization
BidiMap<String, Integer> bidiMap = new DualHashBidiMap<>();
Map<String, Integer> syncMap = Collections.synchronizedMap(bidiMap);

// Option 2: Concurrent wrapper (custom implementation needed)
public class ConcurrentBidiMap<K, V> {
    private final BidiMap<K, V> map;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public ConcurrentBidiMap(BidiMap<K, V> map) {
        this.map = map;
    }
    
    public V get(K key) {
        lock.readLock().lock();
        try {
            return map.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public K getKey(V value) {
        lock.readLock().lock();
        try {
            return map.getKey(value);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public V put(K key, V value) {
        lock.writeLock().lock();
        try {
            return map.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

Best Practices

Choosing the Right Implementation

// Fast lookups, no ordering requirements
BidiMap<String, Integer> fast = new DualHashBidiMap<>();

// Need to maintain insertion order
BidiMap<String, Integer> ordered = new DualLinkedHashBidiMap<>();

// Need sorted iteration, have Comparable keys/values
BidiMap<String, Integer> sorted = new DualTreeBidiMap<>();

// Memory efficient for Comparable types
BidiMap<String, Integer> efficient = new TreeBidiMap<>();

Error Handling

BidiMap<String, String> errorProne = new DualHashBidiMap<>();

try {
    errorProne.put("key1", "value1");
    errorProne.put("key2", "value1"); // May remove key1 mapping
} catch (Exception e) {
    // Handle potential issues
}

// Safer approach - check before inserting
public boolean safePut(BidiMap<String, String> map, String key, String value) {
    if (map.containsValue(value)) {
        String existingKey = map.getKey(value);
        if (!key.equals(existingKey)) {
            System.out.println("Warning: Value '" + value + "' already mapped to '" + existingKey + "'");
            return false;
        }
    }
    map.put(key, value);
    return true;
}

Bidirectional maps provide efficient two-way lookup capabilities while maintaining data consistency through 1:1 key-value relationships. Choose the appropriate implementation based on your performance requirements and ordering needs.

Install with Tessl CLI

npx tessl i tessl/maven-org-apache-commons--commons-collections4

docs

advanced-collections.md

bags.md

bidimaps.md

collection-utilities.md

functional-programming.md

index.md

multimaps.md

tile.json