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

multimaps.mddocs/

Multi-Valued Maps (MultiMaps)

Multi-valued maps allow multiple values to be associated with each key. Unlike regular maps which have a 1:1 key-value relationship, multi-valued maps support 1:many relationships where each key can map to a collection of values.

Core Interfaces

MultiValuedMap<K, V> Interface

The primary interface for multi-valued maps that associates collections of values with keys.

import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import java.util.Collection;

MultiValuedMap<String, String> multiMap = new ArrayListValuedHashMap<>();

// Add multiple values for the same key
multiMap.put("colors", "red");
multiMap.put("colors", "green");
multiMap.put("colors", "blue");
multiMap.put("animals", "cat");
multiMap.put("animals", "dog");

// Get all values for a key (returns Collection<V>)
Collection<String> colors = multiMap.get("colors");     // ["red", "green", "blue"]
Collection<String> animals = multiMap.get("animals");   // ["cat", "dog"]
Collection<String> empty = multiMap.get("plants");      // [] (empty collection)

// Check presence
boolean hasColors = multiMap.containsKey("colors");     // true
boolean hasRed = multiMap.containsValue("red");         // true
boolean hasMapping = multiMap.containsMapping("colors", "red"); // true

// Size operations
int totalMappings = multiMap.size();                    // 5 (total key-value pairs)
int uniqueKeys = multiMap.keySet().size();              // 2 (colors, animals)
boolean isEmpty = multiMap.isEmpty();                   // false

// Remove operations
boolean removed = multiMap.removeMapping("colors", "red"); // Remove specific mapping
Collection<String> removedColors = multiMap.remove("colors"); // Remove all values for key

ListValuedMap<K, V> Interface

A multi-valued map where each key maps to a List of values, preserving order and allowing duplicates.

import org.apache.commons.collections4.ListValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import java.util.List;

ListValuedMap<String, Integer> scores = new ArrayListValuedHashMap<>();

// Add scores in order (duplicates allowed)
scores.put("Alice", 85);
scores.put("Alice", 92);
scores.put("Alice", 88);
scores.put("Alice", 92); // Duplicate allowed

// Get as List (preserves order and duplicates)
List<Integer> aliceScores = scores.get("Alice");        // [85, 92, 88, 92]

// List-specific operations on values
aliceScores.add(95);                                    // Modifies underlying multimap
int firstScore = aliceScores.get(0);                    // 85
int lastScore = aliceScores.get(aliceScores.size() - 1); // 95

SetValuedMap<K, V> Interface

A multi-valued map where each key maps to a Set of values, ensuring uniqueness.

import org.apache.commons.collections4.SetValuedMap;
import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import java.util.Set;

SetValuedMap<String, String> permissions = new HashSetValuedHashMap<>();

// Add permissions (duplicates automatically ignored)
permissions.put("admin", "read");
permissions.put("admin", "write");
permissions.put("admin", "delete");
permissions.put("admin", "read");  // Duplicate ignored

// Get as Set (no duplicates)
Set<String> adminPerms = permissions.get("admin");      // ["read", "write", "delete"]

// Set-specific operations
boolean hasReadPerm = adminPerms.contains("read");      // true
int uniquePerms = adminPerms.size();                    // 3

Concrete Implementations

ArrayListValuedHashMap<K, V>

HashMap-based implementation that stores values in ArrayList collections.

import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import java.util.Arrays;
import java.util.List;

ArrayListValuedHashMap<String, String> topics = new ArrayListValuedHashMap<>();

// Bulk operations
topics.putAll("java", Arrays.asList("collections", "streams", "generics"));
topics.putAll("python", Arrays.asList("lists", "dictionaries", "comprehensions"));

// Values maintain insertion order and allow duplicates
topics.put("java", "collections"); // Duplicate allowed
List<String> javaTopics = topics.get("java"); // ["collections", "streams", "generics", "collections"]

// Capacity optimization for known sizes
ArrayListValuedHashMap<String, Integer> optimized = new ArrayListValuedHashMap<>();
// Internal ArrayLists start with default capacity

HashSetValuedHashMap<K, V>

HashMap-based implementation that stores values in HashSet collections.

import org.apache.commons.collections4.multimap.HashSetValuedHashMap;
import java.util.Arrays;
import java.util.Set;

HashSetValuedHashMap<String, String> tags = new HashSetValuedHashMap<>();

// Bulk operations with automatic deduplication
tags.putAll("article1", Arrays.asList("java", "tutorial", "beginner", "java")); // Duplicate "java" ignored
tags.putAll("article2", Arrays.asList("python", "advanced", "tutorial"));

// Values are unique within each key
Set<String> article1Tags = tags.get("article1");       // ["java", "tutorial", "beginner"]

// Fast contains operations (Set performance)
boolean hasJavaTag = tags.containsMapping("article1", "java"); // true - O(1) average case

Multi-Valued Map Operations

Adding Values

MultiValuedMap<String, String> contacts = new ArrayListValuedHashMap<>();

// Single value additions
contacts.put("John", "john@work.com");
contacts.put("John", "john@personal.com");
contacts.put("John", "john@mobile.com");

// Bulk additions
contacts.putAll("Jane", Arrays.asList("jane@work.com", "jane@home.com"));

// Add to existing collection
Collection<String> johnEmails = contacts.get("John");
johnEmails.add("john@backup.com"); // Modifies underlying multimap

// Conditional addition
if (!contacts.containsMapping("John", "john@spam.com")) {
    contacts.put("John", "john@spam.com");
}

Removing Values

// Remove specific key-value mapping
boolean removed = contacts.removeMapping("John", "john@mobile.com"); // true
boolean notRemoved = contacts.removeMapping("John", "nonexistent"); // false

// Remove all values for a key  
Collection<String> janeEmails = contacts.remove("Jane"); // Returns and removes all values
// janeEmails = ["jane@work.com", "jane@home.com"]

// Remove through collection view
Collection<String> johnEmails = contacts.get("John");
johnEmails.remove("john@work.com"); // Modifies underlying multimap

// Clear all mappings
contacts.clear();

Querying and Iteration

MultiValuedMap<String, Integer> studentGrades = new ArrayListValuedHashMap<>();
studentGrades.putAll("Alice", Arrays.asList(85, 92, 78, 95));
studentGrades.putAll("Bob", Arrays.asList(88, 76, 82));

// Key iteration
for (String student : studentGrades.keySet()) {
    System.out.println("Student: " + student);
}

// Value iteration (all values)
for (Integer grade : studentGrades.values()) {
    System.out.println("Grade: " + grade);
}

// Entry iteration (key-value pairs)
for (Map.Entry<String, Integer> entry : studentGrades.entries()) {
    System.out.println(entry.getKey() + " scored " + entry.getValue());
}

// Key-collection iteration  
for (Map.Entry<String, Collection<Integer>> entry : studentGrades.asMap().entrySet()) {
    String student = entry.getKey();
    Collection<Integer> grades = entry.getValue();
    double average = grades.stream().mapToInt(Integer::intValue).average().orElse(0.0);
    System.out.println(student + " average: " + average);
}

Advanced Usage Patterns

Grouping and Classification

import java.util.stream.Collectors;

public class StudentManager {
    private final MultiValuedMap<String, Student> studentsByGrade = new ArrayListValuedHashMap<>();
    private final MultiValuedMap<String, Student> studentsBySubject = new HashSetValuedHashMap<>();
    
    public void addStudent(Student student) {
        // Group by grade level
        studentsByGrade.put(student.getGrade(), student);
        
        // Group by subjects (Set semantics - each student appears once per subject)
        for (String subject : student.getSubjects()) {
            studentsBySubject.put(subject, student);
        }
    }
    
    public List<Student> getStudentsInGrade(String grade) {
        return new ArrayList<>(studentsByGrade.get(grade));
    }
    
    public Set<Student> getStudentsInSubject(String subject) {
        return new HashSet<>(studentsBySubject.get(subject));
    }
    
    public Map<String, Long> getGradeDistribution() {
        return studentsByGrade.asMap().entrySet().stream()
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> (long) entry.getValue().size()
            ));
    }
}

Configuration Management

public class ApplicationConfig {
    private final MultiValuedMap<String, String> properties = new ArrayListValuedHashMap<>();
    
    public void loadConfig(Properties props) {
        props.forEach((key, value) -> {
            String keyStr = key.toString();
            String valueStr = value.toString();
            
            // Support comma-separated values
            if (valueStr.contains(",")) {
                String[] values = valueStr.split(",");
                for (String v : values) {
                    properties.put(keyStr, v.trim());
                }
            } else {
                properties.put(keyStr, valueStr);
            }
        });
    }
    
    public List<String> getPropertyValues(String key) {
        return new ArrayList<>(properties.get(key));
    }
    
    public String getFirstPropertyValue(String key) {
        Collection<String> values = properties.get(key);
        return values.isEmpty() ? null : values.iterator().next();
    }
    
    public void addPropertyValue(String key, String value) {
        properties.put(key, value);
    }
    
    public boolean hasProperty(String key) {
        return properties.containsKey(key);
    }
    
    public boolean hasPropertyValue(String key, String value) {
        return properties.containsMapping(key, value);
    }
}

Graph Adjacency Lists

public class DirectedGraph<T> {
    private final MultiValuedMap<T, T> adjacencyList = new HashSetValuedHashMap<>();
    
    public void addEdge(T from, T to) {
        adjacencyList.put(from, to);
    }
    
    public void removeEdge(T from, T to) {
        adjacencyList.removeMapping(from, to);
    }
    
    public Set<T> getNeighbors(T vertex) {
        return new HashSet<>(adjacencyList.get(vertex));
    }
    
    public Set<T> getAllVertices() {
        Set<T> vertices = new HashSet<>(adjacencyList.keySet());
        vertices.addAll(adjacencyList.values());
        return vertices;
    }
    
    public int getOutDegree(T vertex) {
        return adjacencyList.get(vertex).size();
    }
    
    public int getInDegree(T vertex) {
        return (int) adjacencyList.entries().stream()
            .filter(entry -> entry.getValue().equals(vertex))
            .count();
    }
    
    public boolean hasPath(T from, T to) {
        if (from.equals(to)) return true;
        
        Set<T> visited = new HashSet<>();
        Queue<T> queue = new LinkedList<>();
        queue.offer(from);
        visited.add(from);
        
        while (!queue.isEmpty()) {
            T current = queue.poll();
            for (T neighbor : getNeighbors(current)) {
                if (neighbor.equals(to)) return true;
                if (!visited.contains(neighbor)) {
                    visited.add(neighbor);
                    queue.offer(neighbor);
                }
            }
        }
        return false;
    }
}

Inverted Index

public class InvertedIndex {
    private final MultiValuedMap<String, Document> termToDocuments = new HashSetValuedHashMap<>();
    private final MultiValuedMap<Document, String> documentToTerms = new HashSetValuedHashMap<>();
    
    public void addDocument(Document document) {
        Set<String> terms = tokenize(document.getContent());
        
        for (String term : terms) {
            termToDocuments.put(term.toLowerCase(), document);
            documentToTerms.put(document, term.toLowerCase());
        }
    }
    
    public Set<Document> search(String term) {
        return new HashSet<>(termToDocuments.get(term.toLowerCase()));
    }
    
    public Set<Document> searchMultiple(String... terms) {
        if (terms.length == 0) return Collections.emptySet();
        
        Set<Document> result = search(terms[0]);
        for (int i = 1; i < terms.length; i++) {
            result.retainAll(search(terms[i])); // Intersection
        }
        return result;
    }
    
    public Set<Document> searchAny(String... terms) {
        Set<Document> result = new HashSet<>();
        for (String term : terms) {
            result.addAll(search(term)); // Union
        }
        return result;
    }
    
    public void removeDocument(Document document) {
        Collection<String> terms = documentToTerms.remove(document);
        for (String term : terms) {
            termToDocuments.removeMapping(term, document);
        }
    }
    
    private Set<String> tokenize(String content) {
        // Simple tokenization - in practice, use proper text processing
        return Arrays.stream(content.toLowerCase().split("\\W+"))
            .filter(s -> !s.isEmpty())
            .collect(Collectors.toSet());
    }
}

Utility Operations

MultiMapUtils Class

import org.apache.commons.collections4.MultiMapUtils;

// Create empty multimaps
MultiValuedMap<String, String> listMap = MultiMapUtils.newListValuedHashMap();
MultiValuedMap<String, String> setMap = MultiMapUtils.newSetValuedHashMap();

// Check if empty (null-safe)
boolean isEmpty = MultiMapUtils.isEmpty(null); // true
boolean isNotEmpty = MultiMapUtils.isEmpty(listMap); // false (depends on content)

// Create unmodifiable views
MultiValuedMap<String, String> unmodifiable = MultiMapUtils.unmodifiableMultiValuedMap(listMap);

// Create empty immutable multimap
MultiValuedMap<String, String> empty = MultiMapUtils.emptyMultiValuedMap();

// Transform multimaps
Transformer<String, String> upperCase = String::toUpperCase;
Transformer<String, String> addPrefix = s -> "prefix_" + s;

MultiValuedMap<String, String> transformed = MultiMapUtils.transformedMultiValuedMap(
    listMap, 
    upperCase,    // Key transformer
    addPrefix     // Value transformer
);

Working with Map Views

MultiValuedMap<String, Integer> scores = new ArrayListValuedHashMap<>();
scores.putAll("Alice", Arrays.asList(85, 92, 78));
scores.putAll("Bob", Arrays.asList(88, 76));

// Get as regular Map<K, Collection<V>>
Map<String, Collection<Integer>> asMap = scores.asMap();

// Modifications through map view affect original
asMap.get("Alice").add(95); // Adds to original multimap
Collection<Integer> bobScores = asMap.remove("Bob"); // Removes from original

// Create defensive copies
Map<String, List<Integer>> copyMap = new HashMap<>();
for (Map.Entry<String, Collection<Integer>> entry : asMap.entrySet()) {
    copyMap.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}

Performance Considerations

Implementation Choice Guidelines

// Use ArrayListValuedHashMap when:
// - Order of values matters
// - Duplicates are needed
// - Values are accessed by index
ListValuedMap<String, String> orderedWithDuplicates = new ArrayListValuedHashMap<>();

// Use HashSetValuedHashMap when:  
// - Uniqueness of values is required
// - Fast contains() operations needed
// - Order doesn't matter
SetValuedMap<String, String> uniqueValues = new HashSetValuedHashMap<>();

Memory Usage

// Memory efficient for sparse data (few keys, many values per key)
MultiValuedMap<String, String> sparse = new ArrayListValuedHashMap<>();
sparse.putAll("key1", Arrays.asList("v1", "v2", "v3", /* ... many values ... */));

// Less efficient for dense data (many keys, few values per key)  
MultiValuedMap<String, String> dense = new ArrayListValuedHashMap<>();
for (int i = 0; i < 1000; i++) {
    dense.put("key" + i, "value" + i); // Creates many small collections
}

// For dense data, consider regular Map<K, V> instead
Map<String, String> regularMap = new HashMap<>();

Bulk Operations Performance

MultiValuedMap<String, Integer> numbers = new ArrayListValuedHashMap<>();

// Efficient bulk addition
List<Integer> manyNumbers = IntStream.range(1, 1000).boxed().collect(Collectors.toList());
numbers.putAll("range", manyNumbers); // Single operation

// Less efficient individual additions
for (int i = 1; i < 1000; i++) {
    numbers.put("range2", i); // Multiple operations, more overhead
}

// Efficient collection manipulation
Collection<Integer> existing = numbers.get("range");
existing.addAll(Arrays.asList(1001, 1002, 1003)); // Direct collection modification

Thread Safety

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

// Option 1: External synchronization
MultiValuedMap<String, String> multiMap = new ArrayListValuedHashMap<>();
MultiValuedMap<String, String> syncMultiMap = MultiMapUtils.synchronizedMultiValuedMap(multiMap);

// Option 2: Custom concurrent wrapper
public class ConcurrentMultiValuedMap<K, V> {
    private final MultiValuedMap<K, V> map = new ArrayListValuedHashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public boolean put(K key, V value) {
        lock.writeLock().lock();
        try {
            return map.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    public Collection<V> get(K key) {
        lock.readLock().lock();
        try {
            return new ArrayList<>(map.get(key)); // Defensive copy
        } finally {
            lock.readLock().unlock();
        }
    }
}

// Option 3: Use ConcurrentHashMap with CopyOnWriteArrayList
Map<String, Collection<String>> concurrent = new ConcurrentHashMap<>();
String key = "example";
concurrent.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()).add("value");

Multi-valued maps provide powerful abstractions for one-to-many relationships and are particularly useful for grouping, classification, and graph-like data structures. Choose between List and Set semantics based on whether you need ordering/duplicates or uniqueness guarantees.

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