Search code examples
javagenericscollectionsawtunbounded-wildcard

How can I insert into a Map<K, ?>?


Normally, I would call this a bug, but this is in the Java Standard Library.

There is a method -- java.awt.Font.getAttributes() that returns a Map<TextAttribute, ?>.

Now, this threw me for a loop because I don't know how I can insert elements into this map. After all, the parameterized type for the values we are inserting is ?, so how can we insert?

If it was fetching, I would simply store whatever I get into Object, since that is a catch all, much like the unbounded wildcard "?" is.

But this is inserting. How can I insert unless I do an unsafe cast?


Solution

  • Found the answer myself -- you cannot.

    This is actually a clue that I was misusing the API. In my defense, the API was not very obvious. But long story short, there is a constructor that TAKES IN a Map<TextAttribute, ?>. This means that we are PROVIDING our own Map for them to use. Now, Map<TextAttribute, Object> can definitely fit into Map<TextAttribute, ?>, so that means that we are good for providing a value to pass into this constructor.

    And if the goal is to see what the contents of the Map are so that we can construct/populate our own, simply extract each object out into an Object or var, then do some instanceof checks to see what it is. From there, you can construct the proper type V to put for your Map<K, V>.

    Here is a simple code example to more clearly explain the point.

    I want to insert a TextAttribute into a java.awt.Font.

    final Map<TextAttribute, Object> map = new HashMap<>(); //Empty map to hold ALL attributes
    
    map.putAll(someFont.getAttributes()); //These are the pre-existing attributes on my font
    
    map.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON); //Adding an attribute
    
    final Font font = Font.getFont(map); //Create font that has the new attribute + old ones too
    

    EDIT - Special thanks as always to @Holger for showing an even better way to make this work. Here it is below.

    final Font font = 
        someFont
            .deriveFont
            (
                Map.of
                (
                    TextAttribute.STRIKETHROUGH,
                    TextAttribute.STRIKETHROUGH_ON
                )
            )
            ;
    

    If we look at the documentation for this method, Font.deriveFont(Map< extends AttributedCharacterIterator.Attribute,?>), we can see that it takes in a Map whose values are of type ?. Furthermore, the documentation itself says that it "Creates a new Font object by replicating the current Font object and applying a new set of font attributes to it." So, it makes a duplicate, and then applies the changes that you passed in as a parameter to it. This is a more efficient way of doing what I described earlier.