简体   繁体   中英

How can I make a Map with two indexes?

I have one Map in java like this:

Map<String index1, Map<String index 2, Object obj>> map = new HashMap<>();

I want to get my Object in the map by using index1 and index2 as lookups.

The easiest way to do this would be to use Guava's Table , if you're willing to use a third party library.

It works like this:

Table<String, String, Object> table = HashBasedTable.create();
table.put(index1, index2, obj);
Object retrievedObject = table.get(index1, index2);

You can add it to your project by following these instructions: How to add Guava to Eclipse project


If you don't want to use Guava, you have a big problem. If you try to insert an element with new first key, you have to make sure the innermap already exists. This means, every time you do put , you have to retrieve the innerMap , see if it exists, and then create it if it does not. You will have to do this every time you call Map.put . Also, you risk throwing a NullPointerException if the inner map doesn't exist when you call get on the inner map.

If you do this, should wrap your Map<String, Map<String, Object> in an outer class to manage these problems, or use Java 8's computeIfAbsent . But the easiest way is to just use Table as above.

If you make your own class to use instead of Table , it would be something like:

public class DoubleMap<R, C, V> {
  private final Map<R, Map<C, V>> backingMap;

  public DoubleMap() {
    this.backingMap = new HashMap<>();
  }

  public V get(R row, C column) {
    Map<C, V> innerMap = backingMap.get(row);
    if(map == null) return null;
    else return innerMap.get(column);
  }

  public void put(R row, C column, V value) {
    Map<C, V> innerMap = backingMap.get(row);
    if(innerMap == null) {
      innerMap = new HashMap<C, V>();
      backingMap.put(row, innerMap);
    }
    innerMap.put(column, value);
  }
}

You would use this class by doing:

DoubleMap<String, String, Object> map = new DoubleMap();

Note that this answer has a lot less features than the Guava version.

Getting a Value from a Map

If I understand your question, then with an index a and b that might look like (guarding against null with a ternary or Conditional Operator ? : ),

Object obj = (map.get("a") == null) ? null : map.get("a").get("b");

Using a Generic Type

And you might be more specific, like

Map<String, Map<String, Something>> map = new HashMap<>();
Something s = (map.get("a") == null) ? null : map.get("a").get("b");

Adding values to the Map

Assuming you want to add your Something value to the map that could be done with something like,

Map<String, Map<String, Something>> map = new HashMap<>();
if (map.get("a") == null) {
    map.put("a", new HashMap<>());
}
map.get("a").put("b", value);

If you don't need regular access to the entire "row", but just quick access to each cell you can use the built-in Map.Entry as your key:

Map<Map.Entry<String, String>, Object> table = new Map<>();
table.put(new Map.SimpleEntry("index1", "index2"), "Hello world");

Alternatively, if you're willing to go with something third-party, several someones have already implemented tuples for Java.

If you are in a situation where you cannot pull in a third-party library easily, but you don't like the semantics of Map.Entry (which is written in terms of key s and value s) you can write your own Pair class to have the same effect.

As my understanding, you can do like:

Map<String, Map<String, Object> map= new HashMap();
Map<String, Object> subMap = map.get("index1");
if(subMap != null) {
    Object obj = subMap.get("index2");
}

The best solution probably depends on how this map is intended to be used:

  • Is it used in a limited scope, or is it part of a public API?
  • Are the "indices" always of type String , or do they have to be generic?
  • Will it always be two indices, or may you need more indices later?
  • ...

A pragmatic solution focussed on the question as you described it would be to introduce a StringPair class that can be used for indexing. This saves you from the hassle of doing 2D-lookups of inner maps (and possible cleanups when the inner maps become empty!), does not require any third-party libraries, and is readable and efficient.

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

public class StringPairMapTest
{
    public static void main(String[] args)
    {
        Map<StringPair, Object> map = new LinkedHashMap<StringPair, Object>();

        map.put(StringPair.of("A","B"), 12);
        map.put(StringPair.of("C","D"), 34);

        System.out.println(map.get(StringPair.of("A","B")));
        System.out.println(map.get(StringPair.of("C","D")));
        System.out.println(map.get(StringPair.of("X","Y")));
    }
}

class StringPair
{
    private final String s0;
    private final String s1;

    static StringPair of(String s0, String s1)
    {
        return new StringPair(s0, s1);
    }

    private StringPair(String s0, String s1)
    {
        this.s0 = s0;
        this.s1 = s1;
    }

    @Override
    public String toString()
    {
        return "("+s0+","+s1+")";
    }

    @Override
    public int hashCode()
    {
        return Objects.hash(s0, s1);
    }

    @Override
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        StringPair other = (StringPair) obj;
        return Objects.equals(s0, other.s0) && Objects.equals(s1, other.s1); 
    }
}

Generalizations to a Pair<T> or Tuple<S,T> would be possible, of course, but this did not seem to be what you have been looking for...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM