11

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.

durron597
  • 31,968
  • 17
  • 99
  • 158
Yên Đậu
  • 141
  • 1
  • 9
  • 1
    You correctly declared a map using strings as keys and having maps as values. You use [Map.get()](https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#get-java.lang.Object-) like always to get the value. Since the value is a map you can do the same operation with it. – dsh Aug 22 '15 at 14:55

5 Answers5

11

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.

Community
  • 1
  • 1
durron597
  • 31,968
  • 17
  • 99
  • 158
  • 1
    So Elliot Frisch's answer is wrong because he did not specify how to handle the case where the inner map does not exists ? While your is valid because you said that the main solution is to use an external library ? I agree that Guava should be added to any major projects. However, I don't think the OP were seeking for validation, but instead seeking for an simple answer on how to retrieve the map by the two index which is what Elliot Frisch answered. It would be a plus to mention how to deal with the inner map, but it was not the initial OP's problem. – Jean-François Savard Aug 22 '15 at 03:06
  • 2
    @Jean-FrançoisSavard I was working on making a simple implementation. Also, the case where the inner map doesn't exist happens very often and is a core part of the problem. – durron597 Aug 22 '15 at 03:08
  • While I still think the other answer is valid, +1 for your new edit and your lego sith profile image. – Jean-François Savard Aug 22 '15 at 03:10
  • Flag this comment as non-constructive, but your profile says that you are "a lego jedi at the rest of the time", but on your profile picture your sword is red :/ – Jean-François Savard Aug 22 '15 at 03:16
  • 2
    @Jean-FrançoisSavard I didn't say whether I was a dark Jedi or not :) – durron597 Aug 22 '15 at 03:17
7

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);
Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
  • 2
    @durron597, unless you are using Java-8 (in this case `map.computeIfAbsent("a", k -> new HashMap<>()).put("b")`) – Tagir Valeev Aug 22 '15 at 02:57
  • @TagirValeev 1. OP didn't say they were using Java 8. 2. That information needs to be in the answer. – durron597 Aug 22 '15 at 02:58
  • @Elliott Thank you for support to me. So and i want set Object to map by index1, index 2, i don't know to do that. Help me. – Yên Đậu Aug 22 '15 at 03:01
  • @durron597 Thanks and edited, I *appreciate* **constructive** criticism. I hope it reads better now. – Elliott Frisch Aug 22 '15 at 19:03
  • I removed my downvote, as this answer is no longer incorrect; however it's still difficult to use in practice. Nature of the beast; this question lends itself to a lot of boilerplate code unless you wrap all the behavior in an entirely new class. – durron597 Aug 22 '15 at 19:19
5

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 keys and values) you can write your own Pair class to have the same effect.

Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
  • 1
    Alternatively, if you're willing to go with something third-party, several someones have already implemented [tuples](http://www.javatuples.org) for Java. – Sean Vieira Aug 22 '15 at 04:46
  • `Map.Entry` is for key/value pairs, not for key/key pairs. Using tuples or, in absence of these, a custom key class seems preferable to me. – Silly Freak Aug 22 '15 at 10:58
2

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");
}
Kenny Tai Huynh
  • 1,464
  • 2
  • 11
  • 23
  • So when i want set by index1, and index 2. Can't you help me. In my Map then Map have a lot of the same key. I want but (index1, index2) is different. – Yên Đậu Aug 22 '15 at 03:07
  • what do you mean about set the index?! Do you want to put the index1 and index2 to maps?! Or you want to get the Map from index1, then get the Object from index2, then change the Object from index2?! – Kenny Tai Huynh Aug 22 '15 at 05:16
2

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...

Marco13
  • 53,703
  • 9
  • 80
  • 159