0

So, the main idea is to create for example:

Map<Map<String, String>, Integer> map = new LinkedHashMap<>();

And I need to put in this map for example items:

{{France=Paris}=12, {Spain=Madrid}=2, ... }

How to make this? How to implement the "put" function? map.put("France","Paris",2) does not work.

heilala
  • 770
  • 8
  • 19
Vlad Gric
  • 27
  • 4
  • The first argument to `put` would have to be a `Map` in this case, not a String. – takendarkk Jul 31 '20 at 16:52
  • 5
    Having a mutable object as a key in your map is a recipe for trouble... What exactly are you trying to achieve here? – Mureinik Jul 31 '20 at 16:53
  • 3
    Are you _sure_ that what you want is a map from a map to an integer? There are better ways to have multi-part keys or values. What does your map represent and how will you be using it? – khelwood Jul 31 '20 at 16:53
  • 3
    What is it that you are trying to do system and data wise? Cause this question sounds like a setup that needs a different design considered. – Tim Hunter Jul 31 '20 at 16:59
  • You should not use `Map` as key here you found details explanation why we should not https://stackoverflow.com/questions/25782057/can-we-have-a-nested-map-as-key-within-other-map – Eklavya Jul 31 '20 at 18:22

3 Answers3

6

You need to first create a Map (e.g. Map.of("France", "Paris")) and put it inside the bigger Map.

Do it as follows:

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

public class Main {
    public static void main(String[] args) {
        Map<Map<String, String>, Integer> map = new LinkedHashMap<>();
        map.put(Map.of("France", "Paris"), 2);
        System.out.println(map);
    }
}

Output:

{{France=Paris}=2}

Alternatively,

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<Map<String, String>, Integer> map = new LinkedHashMap<>();
        Map<String, String> data = new HashMap<>();
        data.put("France", "Paris");
        map.put(data, 2);
        System.out.println(map);
    }
}

Caution: As already mentioned by Mureinik, having mutable objects as the keys (the alternative solution) in a map is error-prone. You can check this answer for an explanation.

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • Small side note that the first solution requires JDK 9 or higher. The alternative does not. – aboger Jul 31 '20 at 16:58
  • 1
    @aboger - Thanks for the note. **OP:** Please note that only the first solution requires the minimum version as Java-9. The second one will work with all versions. – Arvind Kumar Avinash Jul 31 '20 at 17:00
  • For you second solution getting from map will not work properly I think – Eklavya Jul 31 '20 at 17:53
  • @ArvindKumarAvinash Sorry I can't give you an example but you can read here what's problem will happen and why we should not use mutable map as key https://stackoverflow.com/questions/25782057/can-we-have-a-nested-map-as-key-within-other-map – Eklavya Jul 31 '20 at 18:10
  • @RiwimoHerbs - Thank you for sharing this link. I've added it to my answer so that the answer can be more helpful to future visitors. – Arvind Kumar Avinash Jul 31 '20 at 18:27
2

Though Arvind Kumar Avinash's answer perfectly solves the question, please note that you should reconsider your data structure.

Having a Map as a key for another Map is not advisable. You should rather craft another class that serves the purpose of being a key of a Map. If you are interested in having capitals as keys, consider implementing an immutable class such as this one, in which equals() and hashcode() are properly overridden:

public class Capital {

    private final String country;
    private final String name;

    public Capital(String country, String name) {
        this.country = country;
        this.name = name;
    }

    public boolean equals(final Object o) {
        if (o == this) return true;
        if (!(o instanceof Capital)) return false;
        final Capital other = (Capital) o;
        if (!other.canEqual((Object) this)) return false;
        final Object this$country = this.country;
        final Object other$country = other.country;
        if (this$country == null ? other$country != null : !this$country.equals(other$country)) return false;
        final Object this$name = this.name;
        final Object other$name = other.name;
        if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
        return true;
    }

    protected boolean canEqual(final Object other) {
        return other instanceof Capital;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final Object $country = this.country;
        result = result * PRIME + ($country == null ? 43 : $country.hashCode());
        final Object $name = this.name;
        result = result * PRIME + ($name == null ? 43 : $name.hashCode());
        return result;
    }

    public String toString() {
        return "Capital(country=" + this.country + ", name=" + this.name + ")";
    }
}

If you want to get rid of the boilerplate code, consider using the Lombok Project. The above-listed class is the delomboked version of the following code:

@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
public class Capital {

    private final String country;
    private final String name;
    
}

Update after Holger's comment:

Other implementations for equals() and hashCode() than the above-mentioned, delomboked version are possible, differing in some criterias such as code style and performance. Same as Holger, I think the delomboked implementation should not be hand-crafted. It is simply the implementation that results through the usage of the Lombok @EqualsAndHashCode annotation.

There are requirements to be fulfilled in order to properly override hashCode() and equals():

Though performance and good code-sytle are important, they are not amongst these requirements. With the usage of Lombok, in my opinion, code-style is less important, since the code is generated. One usually does not see it during development.

The canEqual() method is present since Lombok also generates it.

aboger
  • 2,214
  • 6
  • 33
  • 47
  • 2
    If you want to get rid of the boilerplate code, just don’t write such boilerplate code. Where’s the sense in this “`canEqual`” method? You are casting `this`, which is a `Capital` instance for sure, to `Object`, just to perform an `…instanceof Capital` check that can never fail. The entire code after `final Capital other = (Capital) o;` can be reduced to a simple `return Objects.equals(this.country, other.country) && Objects.equals(this.name, other.name);`. Likewise, the `hashCode` method can be implemented as simple as `return Objects.hashCode(this.country, this.name);` – Holger Aug 04 '20 at 16:03
  • True, thanks, I've update the answer to point out that this solution should not be _hand-written_, but _generated_. – aboger Aug 05 '20 at 05:22
  • 1
    Yes, we should avoid doing things that a machine can do for us, but in the end, what the machine does has been predetermined by humans and so we sometimes have to find out that the generated code is not better than human written (and I encountered cases where it had significant flaws). It would be interesting why the Lombok authors added that `canEqual` method, but in the end, it doesn’t matter. In the close future, we can simply write `public record Capital(String country, String name) {}` in Java and we’re done, without the need for 3rd party tools. – Holger Aug 05 '20 at 08:48
1

As you are using one pair of string as the key, you can use Map.Entry as key of map rather use Map<String, String>

Map<Map.Entry<String, String>, Integer> map = new LinkedHashMap<>();
map.put(Map.entry("France", "Paris"), 12);
map.put(Map.entry("Spain", "Madrid"), 2);
System.out.println(map.get(Map.entry("France", "Paris")));

Output : 12

Eklavya
  • 17,618
  • 4
  • 28
  • 57