avatarAvinashsoni

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

807

Abstract

over in this world.</p><p id="8ddb">© America Zed. Other Poetry by America Zed.</p><div id="5434" class="link-block"> <a href="https://readmedium.com/not-falling-down-3cc45c1386f"> <div> <div> <h2>Not Falling Down</h2> <div><h3>Poetry ~ Lyrics</h3></div> <div><p>medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*AALBWCv6jgOunFWn)"></div> </div> </div> </a> </div><div id="9fdb" class="link-block"> <a href="https://readmedium.com/the-end-b5188f13c8a0"> <div> <div> <h2>The End</h2> <div><h3>A micro

Options

poem</h3></div> <div><p>medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*Li_myndVY89BxQMp)"></div> </div> </div> </a> </div><div id="47d0" class="link-block"> <a href="https://readmedium.com/consumed-2d3b8a41935e"> <div> <div> <h2>Consumed</h2> <div><h3>Poetry</h3></div> <div><p>medium.com</p></div> </div> <div> <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*hC9xmX2zIRvdcfDa)"></div> </div> </div> </a> </div></article></body>

Map Interface Internals —[ Java Internals Part -4]

In this Article we will Deep Dive the Map Interface Which is Implemented in the Java.util Package.

Photo by AltumCode on Unsplash

So Lets First See Some Key Concepts About How Map are taken in Java

  1. it maps the key to some value
  2. it cannot have duplicate keys
  3. it has Three Collection Views :
  • Set of Keys
  • Collection of Values
  • Set of Key — Value Mappings

4. A map Cannot have itself as a Key

5. A map can have itself as value but in such a case the hascode and the equals method are not applicable

6. Unmodifiable Maps Can also be Created

So lets Start And See the Various Features the Map Interface Supports and how the Functionality are implemented Internally in Map :

Basic Function Definitions :

All these Functions are as per the what the name says

Besides these we have the 3 Views of the Map :

Lets Talk about these views One by One :

Set<K> keySet() :

  • it returns a set of Keys contained in the Given Map
  • changes in set and map are in sync
  • this set supports remove , removeAll, retainAll , clear operations but don’t allow add / addAll operations

Collection<V> values () :

this similar to what we had in the Set of the Keys , here instead of that we will have a Collection That Supports all the Values Present in the Map

Set<Map.Entry<K,V>> entrySet() :

  • this contains a set of Key — Value Pair Present in the Set
  • the Elements of this Set are only Valid during the Iteration only
  • in case our Map is Modified after the Entry is Returned by the Iterator then it is a Undefined Behaviour of the Map.

Now in our Map Interface we also have a Sub Interface Entry < K , V >

Entry<K,V> interface :

which have all the functionalities for our Key — Value Pairs and have the Functions Which We Can Perform on Key and Value Pairs , Like Compute , Merge , etc .

Some Basic Definitions in the Entry < K , V > interface are :

these are simple functions which can be understood by the name itself .

now lets see in detail about these following Function in Detail How they Work and What are their Logic behind .

Now lets learn about each of these methods one bye one :

equals()

Compare a object with the object on which this function is applied

let o1 and o2 be the two objects we are comparing 
then the logic behind equals() is
both key and value should satisfy the below criteria : 
if both objects are null then they are equal otherwise we compare them by thier 
respective values 
so the final logic is like  : 
o1.getKey() == null ? o2.getKey() == null : 
o1.getKey().equals(e2.getKey()) 
&&
o1.getValue() == null ? o2.getValue() == null : 
o1.getValue().equals(o2.getValue()); 

hashCode()

it returns the hash Code value of the key , value pair

the logic behind the hashCode() function is like :

if o is the object on which this function is called and o is having a key,value
pair then hashcode of the the Entry is : 
o.getKey() == null ? 0 : o.getKey().hashCode() 
^ 
o.getValue() == null ? 0 : o.getValue().hashCode() 

this implementation ensures that in case our objects are equal then thier

hash code are also same.

comparingByValue() :

  • it returns a Comparator which is Serializable and throws NPE on comparing with Null Values
  • the Return value is type casted with (Comparator> & Serializable)

implementation :

public static <K,V extends Comparable<? super V>> 
Comparator<Map.entry<K,V>> comparingByValue(){
   return (Comparator<Map.Entry<K,V>> & Serializable) 
           (c1,c2) -> c1.getValue().equals(c2.getValue())
}

comparingByKey()

Similar to what we did with Key but here we get a Comparator in the Argument which will be used as a logic to Compare our Keys

public static <K,V> 
Comparator<Map.Entry<K,V>> comparingByKey( Comparator<? super K> cmp){
   Objects.requireNonNull(cmp);
   return (Comparator<Map.Entry<K,V>> & Serializable) 
 ( c1 , c2 ) -> cmp.compare(c1.getKey(),c2.getKey());
} 

Similar Overloaded Function for comparingByValue() is also there

getOrDefault()

it return the value of the key in case it is not null otherwise it returns a default value

implementation :

default V getOrDefault(Object key , V defaultValue){
  V v;
  (v = get(key)!=null || containsKey(key)) ? v : defaultValue;
}

forEach()

as the name suggest forEach iterates through map and performs the given operation on the Entry in the Map .

the Action which is to Performed is Passed as a BiConsumer to the ForEach Method and then we use the accept function in the BiConsumer to Apply this to the Entry in the Map .

implementation :

default void forEach(BiConsumer<? super K, ? super V> action) {
    Objects.requireNonNull(action);
    for (Map.Entry<K, V> entry : entrySet()) {
        K k;
        V v;
        try {
            k = entry.getKey();
            v = entry.getValue();
        } catch (IllegalStateException ise) {
           throw new ConcurrentModificationException(ise);
        }
        action.accept(k, v);
    }
}
  • BiConsumer < ? super K, ? super V> is passed as an argument
  • entrySet() view is used to iterate through to the Map
  • accept method of BiConsumer is used apply the given logic to the Map Entry

Example :

Map<String,String> map  = new HashMap<>();
map.put("A","B");
map.put("C","D");
map.forEach((k,v)->{
    System.out.println(k + " val - " + v);
});

in case we do some modification during the iteration to the key,value then we get the ConcurrentModificationException

Example :

Map<String,String> map  = new HashMap<>();
map.put("A","B");
map.put("C","D");
map.forEach((k,v)->{
    k = k + "X";
    v = v + "Y";
    map.put(k,v);

});

this is as per the view condition for EntrySet() we cannot do any modification in the map once the Entry is Returned by the Iterator

so any operation add/remove on the Entry of the map will give us the ConcurrentModificationException

replaceAll

Applies the Given Function on the Value of the Entry in the Map and do it for all the Entry

Three phases in replaceAll are :

  1. Getting the Key
  2. Applying the Function to get New Value
  3. Setting the new Value to the entry

implementation :

default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
    Objects.requireNonNull(function);
    for (Map.Entry<K, V> entry : entrySet()) {
        K k;
        V v;
       // Getting the Key , Value 
        try {
            k = entry.getKey();
            v = entry.getValue();
        } catch (IllegalStateException ise) {
           throw new ConcurrentModificationException(ise);
        }
        // Applying the Function on the Key,Value Pair and Storing 
         the Result in  
        v = function.apply(k, v);

        try {
            entry.setValue(v);
        } catch (IllegalStateException ise) {
            // this usually means the entry is no longer in the map.
            throw new ConcurrentModificationException(ise);
        }
    }
}
  • It Accepts a BiFunction which we need to apply on the Key Value Pairs to get the new Value
  • entrySet() view is used

putIfAbsent()

this function adds a value to the key in case there is no value assigned to that key

in case there is no value associated to the key it puts the given value to the key and returns null

in case the key already associated with some value then it returns the current value

implementation :

default V putIfAbsent(K key , V value){
  V v = get(key);
  if( v == null){
    put(key,value);
  }
  return v; 
}

remove

This Function as the name suggest removes the key,value pair.

the only condition is that the key should be mapped to the passed value otherwise it doesn’t remove

implemenation :

default boolean remove(Object key , Object value){
 Object currVal = get(key);
 if(!currVal.equals(currVal,value) || currVal == null && !containsKey(key)){
     return false;
 }
 remove(key);
 return true;
}

replace

if the given key is mapped to the given value then only it replaces the value with the replacement .

implemenatation:

default boolean replace(K key , V oldVal, V newVal){
  Object currval = get(key);
  if(!Objects.equals(currval,oldval) || currval == null && !containsKey(key)){
   return false;
  }
  put(key,newVal);
  return true;
}

computeIfAbsent

here it first check if key is associated to some non null value , now if there is no such case then we apply the function passed ( as we want to compute only if it is absent )

now the value which the function returns in case it is null then no operation is done else we update the key with new value .

implementation :

default V computeIfAbsent(K key , Function < ? super K , ? extends V > fn){
 Objects.requireNonNull(fn);
 V v;
 if(v = get(key) == null){
    V newVal; 
    if( newVal = fn.apply(key)!=null){
     put(key,newVal);
     return newVal; 
    }
 }
 return v;
 
}

example :

Generally this is used to either initialize something in case the key is not present

for ex :

mp.computeIfAbsent(key , k -> new z(3));

where z is some function which do some calculation on the passed value and returns it .

note that this function is applied only when given key is not present in the map

for ex :

this can also be used to throw some exception in case the we want that value should be present otherwise exception should be thrown

mp.computeIfAbsent(key, k -> throw new RuntimeExceoption(“value should be present”);

computeIfPresent

this applies a function to the entry in case the key is present and value not null .

if the computed value is null then it removes the key from the map or else update the entry with new value

implementation :

default V computeIfPresent(K key, 
BiFunction<? super K,? super V, ? extends V> fn){
 Objects.requireNonNull(fn);
 // Compute the Old Value 
 V oldVal;
 // check if it is non null 
 if( oldVal = get(key) !=null){
  // compute the new value by applying the function 
  V newVal = fn.apply(key,oldVal);
  // if newVal is not null then update the Entry
  if(newVal!=null){
   put(key,newVal);
   return newVal;
  }
  else
  {
    // if the new value is NULL then remove the entry 
    remove(key); 
    return null;
  }
 }
 else
 {
  return null;
 }

}

compute

compute method first applies the function to the entry and checks if the returned value is null or not .

in case it is null then if our old value is not null or present in the map then it removes it as the computed value is null or else in case it is already null then it do nothing .

in case our new Value is not Null then it simply puts the value to the key

implementation :

default V compute(K key ,
 BiFunction < ? super K , ? super V , ? extends V > fn){
   Objects.requireNonNull(fn);
   V oldval = get(key); // compute the old val as per the key 
   V newval = fn.apply(key,oldval); // apply the function to the entry to get 
 // the new val 

//if newval is non-null then update the entry 
   if(newval!=null){
     put(key,newval);
     return newval;
   }
   else
   {
    // if newval is null then if the oldval is not null or key present 
    // remove it as after computation it is null 
       if(oldval!=null || containsKey(key)){
          remove(key); 
          return null;
       }
       else
       {
          // do nothing just return null in case oldval was also null 
         return null;
       }
   }
}

merge

we pass the merge function with three items :

  • key
  • value
  • BiFunction

if the oldvalue of the key is null then our newVal is the value which we have passed in the argument ,

else in case it is not null then our new Val is the value which we get after computing the Function .

if the computed value is also null then we simply remove the key

else we put the new key — Val pair

default V merge(K key , V value , 
BiFunction<? super V, ? super V , ? extends V> fn ){
  Objects.requireNonNull(fn);
  Objects.requireNonNull(value);
  V oldval = get(key);
  V newVal = oldVal == null ? value : fn.apply(key , oldVal) ;
  if(newVal == null){
   remove(key);
  }
  else
  {
    put(key,newVal);
  }
  return newVal;

}

Unmodifiable Maps :

Unmodifiable Maps can also be created in Java using the

Map.of

Map.ofEntries

Map.copyOf

Properties :

  • Keys cannot be added , removed , updated
  • null keys and values are not allowed
  • serializable only if All K,V pairs are serializable
## Zero Mappings :
static < K, V> Map<K,V> of(){
return ImmutableCollections.emptyMap();
}
## One Mappings :
static <K, V> Map<K, V> of(K k1, V v1) {
return new ImmutableCollections.Map1<>(k1, v1);
}
## Two Mappings :
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2);
}
## Three Mappings :
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3);
}

and so on upto 10 mappings can be created .

Since the usage of unmodifiable maps is not very common i have just done a brush up on how it is implemented and its basic properties

More about Unmodifiable maps -> https://docs.oracle.com/en/java/javase/15/core/creating-immutable-lists-sets-and-maps.html#GUID-271F524A-9F5C-45F0-8C5B-7419525605B8

Thanks for reading.Happy learning 😄

Do support our publication by following it

Also refer to the following articles.

Maps
Java
Internal
Java Interview Questions
Java Map
Recommended from ReadMedium