Does calling HashMap.entrySet() instantiate and populate a new set every time? - java

I have need of iterating over some HashMaps on each frame of an OpenGL loop. I do it like this:
for (Map.Entry<MyKey, MyValue> entry : myMap.entrySet(){...}
My concern is about whether this call to entrySet() actually instantiates and populates a brand new Map.Entry object every time it's called, because if it is, the GC will be more busy than I'd like when animating in OpenGL. My gut says no, because the HashMap documentation says that you can directly modify the HashMap using the returned entry set, but I don't know how to tell for sure.
And I'd also like to know about other Map implementations as well, like Hashtable, TreeMap, and LinkedHashMap.

After reviewing the source, the answer is no. It lazily instantiates the entry set on the first call to entrySet() and then returns a reference to the same object on each subsequent call.
The same is true for LinkedHashMap, Hashtable, and TreeMap.

No, it does not create a new Set, rather a light-weight wrapper, like eg. Arrays.asList does, API says The set is backed by the map, so changes to the map are reflected in the set, and vice-versa.

The implementation of HashMap.entrySet is simply a cheap view, and takes O(1) time to create every time you call it. The entry objects you get by iterating over it are, in fact, the objects used by the map to implement its internal data structures.
(That, and there's really no other way to do the things you would want to do with it.)

Related

While iterating over a collections of Java objects--how can I mutate current object?

I will be using an iterator to loop through a collection of objects in Java. I am a little confused about using an Iterator however (used to using for each loop). let say I have code like this:
Iterator<Organization> orgIter = orgs.iterator();
while (orgIter.hasNext()) {
Organization orgObj = orgIter.next();
orgObj.setChildOrgsTree(generateOrgChildTree(orgObj, new ArrayList<Organization>()));
}
I create a new object and then change the fields inside the object. My plan was to then set the original object I formed the iteroter out equal to a List of this Object that I create (List is not shown above, but I just figured out that I would need it).
But it would be so much easier if I didn't have to create a new Object to do all of this. Is there a way I can get the current object and mutate that?
Try this:
for(Organization org : orgs)
org.setChildOrgsTree(generateOrgChildTree(org, new ArrayList<Organization>());
If your iterator iterates over a List or Queue instance, then there are fewer restrictions. As long as you do not change the structure of the list, you can do whatever you wish with the object returned by Iterator.next(). Of course, the class of that specific object should allow it to be modified, but I presume that you can add the necessary code...
On the other hand, the only list structure modification that is allowed without invalidating the Iterator object, is to remove the current element using Iterator.remove(), which, incidentally, is an optional operation. If you try to modify the list directly then the iterator will be invalidated and you will get a ConcurrentModificationException the next time you try to use it.
Additionally, if you are iterating over a Set, you are generally far more constrained: depending on the details of the actual Set implemenation, you are not generally able to modify arbitrary fields in your objects. For example, the value returned by hashCode() should not change once an object has been inserted in a HashSet...
As a sidenote, for-each loops in Java are just syntactic sugar for using an Iterator. If you are not going to use Iterator.remove(), just use a for-each loop instead of the explicity iterator and be done with it.

can a Map also be a Collection?

I'd like to have a Map that is also a Collection. Or more specifically, I'd like to be able to iterate over the entries in a Map, including the case where there are multiple entries for a particular key.
The specific problem I'm trying to solve is providing an object that can be used in jstl both to iterate over using c:forEach and in an expression like ${a.b.c}. In this example, I'd want ${a.b.c} to evaluate to the the first value of c (or null if there are none), but also be able to iterate over all cs with <c:forEach items="${a.b.c}"> and have the loop body see each individual value of c in turn, although they have the same key in the Map.
Looking at things from a method point of view, this should be straightforward, just provide a Map implementation whose entrySet() method returns a set with multiple Entries with the same key. But since this seems to violate the contract of a Map, will things break in subtle yet disastrous ways? Has anyone else done this sort of thing?
(If you guessed I'm trying to present xml, you'd be correct)
EDIT
Please note that this is for use in jstl, so whatever interface I present must meet 2 conditions:
for use with the [] and . operators, it must be a Map, List, array or JavaBeans object (and of those it can't be a List or array because the indexes will not be numbers)
for use with forEach it must be an array, Collection, Iterator, Enumeration, Map, or String.
So I guess the real question is, can I count on jstl only calling .containsKey(), .get(), and .entrySet() and not caring about invariants being violated, and not internally making a copy of the Map which would not preserve the special iteration behavior.
What you are looking for is a Multimap. Guava provides an implementation of it and specifically you are looking for ArrayListMultimap.
I barely remember jstl, but what you're saying sounds a kind of controversial:
In foreach:
here ${a.b.c} should point to some container of values and then we iterate over it.
On the other hand you say, ${a.b.c} "should evaluate to the the first value of c" (or null...)
Its an ambiguous definition.
If you feel like Multimap is not what you want, you can provide your own collection implementation (probably internally based on Multimap)
Just as an idea you can always look at a single element as a list (that accidentally
is comprised of one element). This way you would resolve your ambiguity, I guess.
I hope this helps
Having a Map with multiple entries for the same key irreparably breaks the Map contract. If Multimap doesn't work for you, then there's no way to do this without breaking a lot of things.
Specifically, if you pass your monstrosity to something that's specified to take a Map, it'll almost certainly break...and it sounds like that's what you want to do with it, so yeah.
how about you use a Map with Collections as values? then you can have different values for the same key and you can iterate over them by a nested foreach-loop
you can also easily write a wrapper for an existing map-implementation, which gives you a single iterator over all values, if you need it that way

get the number of instances of an element in a guava multiset without iterating

I have a multiset in guava and I would like to retrieve the number of instances of a given element without iterating over this multiset (I don't want to iterate because I assume that iterating takes quite some time, as it looks through all the collection).
To do that, I was thinking first to use the entryset() method of multiset, to obtain a set with single instances and their corresponding count. Then, transform this set into a hashmap (where keys are the elements of my set, and values are their instance count). Because then I can use the methods of hashmap to directly retrieve a value from its key - done! But this makes sense only if I can transform the set into the hashmap in a quick way (without iterating trhough all elements): is it possible?
(as I said I expect this question to be flawed on multiple counts, I'd be happy if you could shed light on the conceptual mistakes I probably make here. Thx!)
Simply invoke count(element) on your multiset -- voila!
You may know in Guava Multiset is an interface, not a class.
If you just want to know the repeated number of an element, call Multiset.count(Object element).
Please forget my following statement:
Then if you are using a popular implementation HashMultiset, there is already a HashMap<E, AtomicInteger> working under the scene.
That is, when the HashMultiset iterates, also a HashMap iterates. No need to transform into another HashMap.

Java: Compact a HashMap (analogue of ArrayList#trimToSize)

Is there a way to compact a HashMap in the sense that you can with an ArrayList through its trimToSize() method?
One way I can think of is to iterate through all of the entries in the present map and populate a new one, then replace the original with the new one.
Is there a better way to accomplish this?
Well you don't need to go through iterating manually - you can just use:
map = new HashMap<String, String>(map); // Adjust type arguments as necessary
I believe that will do all the iteration for you. It's possible that clone() will do the same thing, but I don't know for sure.
Either way, I don't believe you're missing anything - I don't think there's any way of performing a "trim" operation in the current API. Unlike ArrayList, such an operation would be reasonably complex anyway (as expansion is) - it's not just a case of creating a new array and performing a single array copy. The entries need to be redistributed. The benefit of getting HashMap to do this itself internally would probably just be that the hash codes wouldn't need recomputing.
If you use the trove library instead this has support for hash map and hash set trimming (see THashMap object, compact method) and best of all, automatically trims on removal of objects when the map becomes too sparse. This should be quicker than building a new map using the standard java HashMap implementation as (presumably) it doesn't have to reorder the objects according to their hashcode, but can just use the order it already knows.

What basic operations on a Map are permitted while iterating over it?

Say I am iterating over a Map in Java... I am unclear about what I can to that Map while in the process of iterating over it. I guess I am mostly confused by this warning in the Javadoc for the Iterator interface remove method:
[...] The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method.
I know for sure that I can invoke the remove method without any issues. But while iterating over the Map collection, can I:
Change the value associated with a key with the Map class put method (put with an existing key)?
Add a new entry with the Map class put method (put with a new key)?
Remove an entry with the Map class remove method?
My guess is that I can probably safely do #1 (put to an existing key) but not safely do #2 or #3.
Thanks in advance for any clarification on this.
You can use Iterator.remove(), and if using an entrySet iterator (of Map.Entry's) you can use Map.Entry.setValue(). Anything else and all bets are off - you should not change the map directly, and some maps will
not permit either or both of the aforementioned methods.
Specifically, your (1), (2) and (3) are not permitted.
You might get away with setting an existing key's value through the Map object, but the Set.iterator() documentation specifically precludes that and it will be implementation specific:
If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation, or through the setValue operation on a map entry returned by the iterator) the results of the iteration are undefined. (emphasis added)
If you take a look at the HashMap class, you'll see a field called 'modCount'. This is how the map knows when it's been modified during iteration. Any method that increments modCount when you're iterating will cause it to throw a ConcurrentModificationException.
That said, you CAN put a value into a map if the key already exists, effectively updating the entry with the new value:
Map<String, Object> test = new HashMap<String, Object>();
test.put("test", 1);
for(String key : test.keySet())
{
test.put(key, 2); // this works!
}
System.out.println(test); // will print "test->2"
When you ask if you can perform these operations 'safely,' you don't have to worry too much because HashMap is designed to throw that ConcurrentModificationException as soon as it runs into a problem like this. These operations will fail fast; they won't leave the map in a bad state.
There is no global answer. The map interface let the choice to the users. Unfortunately, I think that all the implementations in the jdk use the fail-fast implementation (here is the definition of fail-fast, as it stated in the HashMap Javadoc):
The iterators returned by all of this
class's "collection view methods" are
fail-fast: if the map is structurally
modified at any time after the
iterator is created, in any way except
through the iterator's own remove
method, the iterator will throw a
ConcurrentModificationException. Thus,
in the face of concurrent
modification, the iterator fails
quickly and cleanly, rather than
risking arbitrary, non-deterministic
behavior at an undetermined time in
the future.
In general, if you want to change the Map while iterating over it, you should use one of the iterator's methods. I have not actually tested to see if #1 will will work, but the others definitely will not.

Categories