1

I am trying to write a class that has a Map as a field. The field is as follows:

Map<String, Collection<String>> courses;

In the constructor, I have to have the field in the form:

Map<String, Set<String>;

without changing the field at all. I am getting an error when I try to initialize the field with the set. Can someone tell me why or what to do without altering the original field?

  • 1
    That's because a map of string to a collection of strings is not of the same type (or is not a subtype) of a map of string to a set of strings, and in Java you can't assign variables of different types (except when you have a type in the left side of the = sign and a subtype at the right). Do you really need to have the argument in the constructor as a map of sets? – fps Apr 12 '17 at 01:51
  • Yes I do, since it has to store a set of the courses a student is taking without duplicates. I also need to implement methods to add classes and the project decription says so – Johnny Appleseed Apr 12 '17 at 01:58
  • 2
    Well, if it's a `Map>`, then it's _not_ a `Map>`. You'd be able to store an `ArrayList` as a value in a `Map>`, but not in a `Map>`. So one of these types is _not_ a subtype of the other. You need to decide which of the two you actually want. – Dawood ibn Kareem Apr 12 '17 at 02:09

2 Answers2

0

Despite Set<String> is actually a subtype of Collection<String>, a Map<String, Set<String>> is not a subtype of Map<String, Collection<String>>. In fact, they are completely different types and you can't assign one to the other.

Luckily, the Map interface defines the putAll method, which has the following signature:

void putAll(Map<? extends K,? extends V> m)

This means that the putAll method accepts a map whose keys and values might be of types that are subtypes of its own key and value types, respectively.

So, in your example, you could do as follows:

public class YourClass {

    private final Map<String, Collection<String>> courses = new HashMap<>();

    public YourClass(Map<String, Set<String>> courses) {
        this.courses.putAll(courses);
    }
}

You only have to make sure that the courses attribute has been instantiated before invoking putAll on it.

fps
  • 33,623
  • 8
  • 55
  • 110
0

I'm not sure what actual question is about, but... code below is working because of Type erasure at Runtime

public class SimpleTest {

  protected Map<String, ? extends Collection<String>> courses;

  public SimpleTest(Map<String,Set<String>> setsMap)
  {
     courses = setsMap;
  }

  public static void main(String... args) {

    Map<String, ? extends Collection<String>> setMap = new HashMap<String, Set<String>>();

    SimpleTest stInstance = new SimpleTest((Map<String, Set<String>>) setMap);

    String str1 = "Hi";
    String str2 = "Hello";
    Set<String> stringSet = new HashSet<>();
    stringSet.add(str1);

    List<String> stringList = new ArrayList<>();
    stringList.add(str2);

    ((Map<String, Collection<String>>)setMap).put("set1", stringSet);

    ((Map<String, Collection<String>>)setMap).put("list1", stringList);

    System.out.println("set1 class: " + stInstance.courses.get("set1").getClass().getName());
    System.out.println("list1 class: " + stInstance.courses.get("list1").getClass().getName());
    System.out.println("map content: " + stInstance.courses);

  }
}

output is:

set1 class:java.util.HashSet
list1 class:java.util.ArrayList
map content:{list1=[Hello], set1=[Hi]}

PS. I do not recommend to use such "technique", at all. But as experiment it is interesting and funny :-)

Vadim
  • 4,027
  • 2
  • 10
  • 26
  • This is interesting, but you have changed the field `courses` and in the question it says "without changing the field at all". – fps Apr 12 '17 at 02:53
  • Yes I did. Because you are right. There is no way except using `putAll` – Vadim Apr 12 '17 at 02:55
  • Yeah, [PECS](http://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super) is quite tricky, still trying to *domesticate* it. You know, when you use `? extends Collection` as the type of your values in your map, you can't invoke `put` on it. Actually, you can't even invoke any method that accepts an argument of the type of the value. This is why you had to cast `setMap` to `Map>` before invoking `put` on it. – fps Apr 12 '17 at 03:07
  • Sure. All of those are about compile time. At run time there is nothing but old `Map` or let say `Map` – Vadim Apr 12 '17 at 03:12