'Java collection like c# KeyedColllection<TKey,TItem>

Is there a Java collection that has the same behavior as the c# abstract KeyedCollection class (that is items can be retrieved by both key and index)? I have looked around but can't find anything similar.

Thanks, Nick



Solution 1:[1]

I think you can develop Your own class by extending a HashMap

Solution 2:[2]

I think the closest thing I can think of to what you want is a treemap (http://docs.oracle.com/javase/7/docs/api/java/util/TreeMap.html), however you cannot get items by index only by key.

Solution 3:[3]

Given an Entity class:

@Data // lombok.Data, to have getters and setters
public class Entity {
    private String myIndexingProperty;
    // Other fields here
}

An attempt to implement something like the KeyedCollection<T> of C# is (Java 8+):

import com.my.application.model.Entity;

import java.util.*;
import java.util.stream.Collectors;

public final class EntityMap implements Map<String, Entity> {
    private TreeSet<Entity> set;

    public EntityMap() {
        set = new TreeSet<>();
    }

    @Override
    public int size() {
        return set.size();
    }

    @Override
    public boolean isEmpty() {
        return set.isEmpty();
    }

    @Override
    public boolean containsKey(Object o) {
        return set.stream()
                .anyMatch(e -> String.valueOf(o).equalsIgnoreCase(e.getMyIndexingProperty()));
    }

    @Override
    public boolean containsValue(Object o) {
        return set.stream()
                .anyMatch(e -> String.valueOf(o).equalsIgnoreCase(e.getMyIndexingProperty()));
    }

    @Override
    public Entity get(Object o) {
        return set.stream()
                .filter(e -> String.valueOf(o).equalsIgnoreCase(e.getMyIndexingProperty()))
                .findFirst()
                .orElse(null);
    }

    @Override
    public Entity put(String s, Entity entity) {
        set.add(entity);
        return set.stream()
                .filter(e -> String.valueOf(entity.getMyIndexingProperty()).equalsIgnoreCase(e.getMyIndexingProperty()))
                .findFirst()
                .orElse(null);
    }

    @Override
    public Verb remove(Object o) {
        boolean removed = set.removeIf(e -> e.equals(o));
        if (!removed) {
            return null;
        }

        return get(o);
    }

    @Override
    public void putAll(Map<? extends String, ? extends Entity> map) {
        map.forEach(this::put);
    }

    @Override
    public void clear() {
        set.clear();
    }

    @Override
    public Set<String> keySet() {
        return set.stream()
                .map(Entity::getMyIndexingProperty)
                .collect(Collectors.toSet());
    }

    @Override
    public Collection<Entity> values() {
        return set;
    }

    @Override
    public Set<Entry<String, Entity>> entrySet() {
        return set.stream()
                .map(e -> new AbstractMap.SimpleEntry<>(e.getMyIndexingProperty(), e))
                .collect(Collectors.toSet());
    }
}

Solution 4:[4]

Here is a full implementation using a "KeyedItem" interface. I've included comments as best I could.

/** 
*  An interface that must be implemented by an item inserted into a KeyedItemHashMap.
*/
public interface KeyedItem {
    /** 
    *  Returns an ID for this item to be used in a KeyedItemHashMap.
    *  @return An ID for this item to be used in a KeyedItemHashMap.
    */
    public String getId();
}

And then the implementation of the HashMap class...

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Hash table based implementation of the {@code Map} interface.  This
 * implementation provides all of the optional map operations, and permits
 * {@code null} values and the {@code null} key.  (The {@code HashMap}
 * class is roughly equivalent to {@code Hashtable}, except that it is
 * unsynchronized and permits nulls.)  This class makes no guarantees as to
 * the order of the map; in particular, it does not guarantee that the order
 * will remain constant over time.
 *
 * Unlike a typical hash map, this implementation uses KeyedItem's as the 
 * value. A KeyedItem must implement the {@code KeyedItem} interface and 
 * provide a unique ID to avoid collisions within the map.
 * 
 * For more details see the {@code HashMap} class.
 */
public final class KeyedItemHashMap implements Map<String,KeyedItem>{
    private HashMap<String,KeyedItem> m_map;

    /**
     * Constructs an empty HashMap with the default initial capacity (16) and 
     * the default load factor (0.75).
     */
    public KeyedItemHashMap() {
        m_map = new HashMap<>();
    }

    /**
     * Returns the number of key-value mappings in this map.
     * @return The number of key-value mappings in this map.
     */
    public int size() {
        return m_map.size();
    }

    /**
     * Returns {@code true} if this map contains no key-value mappings.
     * @return {@code true} if this map contains no key-value mappings.
     */
    public boolean isEmpty() {
        return m_map.isEmpty();
    }

    /**
     * Returns {@code true} if this map contains a mapping for the specified key.
     * @param key The key whose presence in this map is to be tested.
     * @return {@code true} if this map contains a mapping for the specified key.
     */
    public boolean containsKey(Object key) {
        return m_map.containsKey(key);
    }

    public boolean containsValue(Object keyedItem) {
        return m_map.containsValue(keyedItem);
    }

    /**
     * Returns the string representation of the {@code Object} argument.
     *
     * @param   obj   an {@code Object}.
     * @return  if the argument is {@code null}, then a string equal to
     *          {@code "null"}; otherwise, the value of
     *          {@code obj.toString()} is returned.
     * @see     java.lang.Object#toString()
     */
    public KeyedItem get(Object obj) {
        return m_map.get(String.valueOf(obj));
    }

    /**
     * Associates the specified value with the keyedItem's ID in this map.
     * If the map previously contained a mapping for the keyedItem's ID, the old
     * value is replaced.
     *
     * @param key UNUSED here but necessary for override.
     * @param keyedItem Value that implements the KeyedItem interface. The getId() function will
     * be used to determine the key for the map.
     * @return the previous value associated with {@code keyedItem.getId()}, or
     *         {@code null} if there was no mapping for {@code keyedItem.getId()}.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with {@code keyedItem.getId()}.)
     */
    public KeyedItem put(String key, KeyedItem keyedItem) {
        return m_map.put(keyedItem.getId(), keyedItem);
    }

    /**
     * Associates the specified value with the keyedItem's ID in this map.
     * If the map previously contained a mapping for the keyedItem's ID, the old
     * value is replaced.
     *
     * @param keyedItem Value that implements the KeyedItem interface. The getId() function will
     * be used to determine the key for the map.
     * @return the previous value associated with {@code keyedItem.getId()}, or
     *         {@code null} if there was no mapping for {@code keyedItem.getId()}.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with {@code keyedItem.getId()}.)
     */
    public KeyedItem put(KeyedItem keyedItem) {
        return m_map.put(keyedItem.getId(), keyedItem);
    }

    /**
     * Removes the mapping for the specified keyedItem's ID from this map if present.
     *
     * @param  keyedItem KeyedItem whose mapping is to be removed from the map.
     * @return the previous value associated with {@code keyedItem.getId()}, or
     *         {@code null} if there was no mapping for {@code keyedItem.getId()}.
     *         (A {@code null} return can also indicate that the map
     *         previously associated {@code null} with {@code keyedItem.getId()}.)
     * @throws ClassCastException if the keyedItem does not implement the KeyedItem interface.
     */
    public KeyedItem remove(Object keyedItem) {
        return m_map.remove(((KeyedItem)keyedItem).getId());
    }

    /**
     * Copies all of the mappings from the specified map to this map.
     * These mappings will replace any mappings that this map had for
     * any of the keys currently in the specified map.
     *
     * @param map mappings to be stored in this map
     * @throws NullPointerException if the specified map is null
     */
    public void putAll(Map<? extends String, ? extends KeyedItem> map) {
        m_map.putAll(map);
    }

    /**
     * Removes all of the mappings from this map.
     * The map will be empty after this call returns.
     */
    public void clear() {
        m_map.clear();
    }

    /**
     * Returns a {@link Set} view of the keys contained in this map.
     * The set is backed by the map, so changes to the map are
     * reflected in the set, and vice-versa.  If the map is modified
     * while an iteration over the set is in progress (except through
     * the iterator's own {@code remove} operation), the results of
     * the iteration are undefined.  The set supports element removal,
     * which removes the corresponding mapping from the map, via the
     * {@code Iterator.remove}, {@code Set.remove},
     * {@code removeAll}, {@code retainAll}, and {@code clear}
     * operations.  It does not support the {@code add} or {@code addAll}
     * operations.
     *
     * @return a set view of the keys contained in this map
     */
    public Set<String> keySet() {
        return m_map.keySet();
    }

    /**
     * Returns a {@link Collection} view of the values contained in this map.
     * The collection is backed by the map, so changes to the map are
     * reflected in the collection, and vice-versa.  If the map is
     * modified while an iteration over the collection is in progress
     * (except through the iterator's own {@code remove} operation),
     * the results of the iteration are undefined.  The collection
     * supports element removal, which removes the corresponding
     * mapping from the map, via the {@code Iterator.remove},
     * {@code Collection.remove}, {@code removeAll},
     * {@code retainAll} and {@code clear} operations.  It does not
     * support the {@code add} or {@code addAll} operations.
     *
     * @return a view of the values contained in this map
     */
    public Collection<KeyedItem> values() {
        return m_map.values();
    }

    /**
     * Returns a {@link Set} view of the mappings contained in this map.
     * The set is backed by the map, so changes to the map are
     * reflected in the set, and vice-versa.  If the map is modified
     * while an iteration over the set is in progress (except through
     * the iterator's own {@code remove} operation, or through the
     * {@code setValue} operation on a map entry returned by the
     * iterator) the results of the iteration are undefined.  The set
     * supports element removal, which removes the corresponding
     * mapping from the map, via the {@code Iterator.remove},
     * {@code Set.remove}, {@code removeAll}, {@code retainAll} and
     * {@code clear} operations.  It does not support the
     * {@code add} or {@code addAll} operations.
     *
     * @return a set view of the mappings contained in this map
     */
    public Set<Entry<String, KeyedItem>> entrySet() {
        return m_map.entrySet();
    }
}

This should be flexible enough for you to just add the interface to any class you want to store.

You want to implement the map interface so that your new hashmap can be used in any capacity that a generic hashmap could be used, unfortunately this means you have a put function that takes in a key that isn't used. I've noted that in the comments.

Edit: fixed some syntax errors and included imports

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 RatheeshTS
Solution 2 TheVoicedElk
Solution 3 Leonel Sanches da Silva
Solution 4