Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How inefficient is passing Collections.unmodifiable* an instance which is already wrapped with Collections.unmodifiable*?

I have bits of piecework being done by different custom (source code unavailable) frameworks which hand back Map instances. Unfortunately, these frameworks are not consistent in their returning Map instances which have been wrapped with Collections.unmodifiableMap. To ensure a higher degree of immutability (for easier multi-threaded use) in my code, I have just uniformly called Collections.unmodifiableMap on anything returned by these frameworks.

Map<String, Record> immutableMap = framework.getRecordsByName();
//does this created a nested set of unmodifiableMap wrapper instances?
this.immutableField = Collections.unmodifiableMap(immutableMap);
.
.
.
Map<String, Record> maybeImmutableMap = framework.getRecordsByName();
//is there some means to get instanceof to work?
if (!(maybeImmutableMap instanceof Collections.UnmodifiableMap))
{
    this.immutableField = Collections.unmodifiableMap(maybeImmutableMap);
}

I realized that I might have a performance issue around this part of my design. And that in some instances, I was calling Collections.unmodifiableMap passing it an instance which had already been wrapped by the framework by the same call. And that my re-wrapping was likely causing an extra method call across the entire instance.

It appears that using "instanceof Collections.UnmodifiableMap" doesn't work. And I cannot find any way to detect (excluding using reflection which is not an option in this situation - WAY too slow) if the Map instance I am currently referencing needs to be wrapped or not.

Questions:

    A) Does the Collections.unmodifiableMap() method check to see if it was passed an instance of UnmodifiableMap, and if so just return the same reference (thereby avoiding the need to check prior to calling the method)?
    B) In order to proactively avoid receiving modification exceptions, is there a way to query a Map instance (other than using reflection) to detect if it is mutable (or immutable)?
    C) If the answer to A is no, then is there some efficiencies in the JVM/HotSpot which eliminate the overhead of calling through the multiple method hops to get to the core instance?
like image 295
chaotic3quilibrium Avatar asked Dec 10 '22 12:12

chaotic3quilibrium


2 Answers

To the best of my knowledge:

  • A) No.
  • B) No.
  • C) No.

Guava's Immutable* collections don't have this problem. If ImmutableList.copyOf(list) is called with a list that is itself an ImmutableList, the argument itself is returned. Additionally, you can refer to them as (and check with instanceof for) the Immutable* type rather than the interface, making it easy to know if you have an immutable instance or not. So one option is to copy the results from the framework into these immutable collections and use those throughout your own code. (They also have the advantage of being truly immutable... unmodifiable wrappers allow the original mutable instance that they wrap to be mutated itself if something has a reference to it.)

All that said, I wouldn't worry too much about the possible overhead of passing a method call through 1 or 2 unmodifiable wrapper layers, as long as you're not going to somehow wrap them again and again. As others have pointed out, it's highly unlikely that you'll ever notice a performance issue because of this.

like image 185
ColinD Avatar answered Jan 16 '23 11:01

ColinD


You don't have to worry about performance when you wrap one unmodifiable map into another. Have a look at UnmodifiableMap class:

private static class UnmodifiableMap<K,V> implements Map<K,V>, Serializable {
    ...
UnmodifiableMap(Map<? extends K, ? extends V> m) {
        if (m==null)
            throw new NullPointerException();
        this.m = m;
    }

public int size()                {return m.size();}
    ...
public V put(K key, V value) {
    throw new UnsupportedOperationException();
    }
public V remove(Object key) {
    throw new UnsupportedOperationException();
    }

public Set<K> keySet() {
    if (keySet==null)
    keySet = unmodifiableSet(m.keySet());
    return keySet;
}

public Set<Map.Entry<K,V>> entrySet() {
    if (entrySet==null)
    entrySet = new UnmodifiableEntrySet<K,V>(m.entrySet());
    return entrySet;
}
    ...

You can see that this class is only a thin wrapper around the real map. All methods like getSize, isEmpty and other methods that don't affect map's state are delegated to the wrapped map instance. Other methods that affect map's state (put, remove) just throw UnsupportedOperationException So there is almost zero performance overload.

like image 22
Denis Kniazhev Avatar answered Jan 16 '23 12:01

Denis Kniazhev