Friday, March 25, 2022

Internal Working of HashMap in Java

No comments:

 Lets first see what a HashMap is:

  • HashMap is an implementation of Map interface which stores key/value pairs.
  • main benefit: adds/retrieves elements by key in a constant time

The below code creates a HashMap

HashMap<String, Integer> map = new HashMap<>();

Here a hash map is created. Consider hash map is a array and one element of that array is known as a bucket. By default say that hash map is created it contains 16 buckets (indexed from 0 to 15)

Now lets insert a key value pair to our map

map.put("apple",25);

At this point the following things happen internally
  • Calculate the hash code of the key "apple":
    Hash code calculated using hashCode() method. By default returns the memory reference of object in integer form. The process of converting an object into integer by using the method hashCode() is called hashing. We can override the hashCode() method to define our own implementation.

  • Calculate the index 
    Since hash code generated for a key is a large number, we need to generate an index which is not greater than the size of our array (which is 16)
    index = hashCode(key) & (n-1)
    where n = number of buckets 

  • Create a node object
    Since we have a key value pair we create a node to store them. Node can have hashcode, key, value and next (which contains address of the next node)

  • Place the created node 
    Now place the create in one of the buckets. Say if we got the index as 5 then place the node in the bucket-5
Let insert another key value pair
map.put("banana",30);

At this point the following things happen internally
  • Calculate the hash code of the key "banana"

  • Calculate the index 

  • Create a node object

  • Place the created node 
    Say if we got the index as 7 then place the node in the bucket-7

Let insert another key value pair
map.put("mango",40);

At this point the following things happen internally
  • Calculate the hash code of the key "mango"

  • Calculate the index 

  • Create a node object

  • Place the created node 
    Say if we got the index as 5. But in bucket-5 we already have a node. Hence there is a collision. What we do is we link the node(apple) with the newly created node(mango), i.e. we place the address of mango node in the next portion of apple node.
    








Why overriding equals() and hashcode() is required in Java?

No comments:

First go through the code below

Map<String, Integer> stringMap = new HashMap<>();
stringMap.put("a", 1);
stringMap.put("b", 2);
stringMap.put("a", 3);
System.out.println(stringMap);

The output for the above code will be 

{a=3, b=2}

As you can since the key is same even though we inserted 3 key/value pairs the map contains only two key/value pairs since the key a is repeating.

Now check the below code

Map<Employee, Integer> employeeMap = new HashMap<>();
employeeMap.put(new Employee("anu"), 1);
employeeMap.put(new Employee("binoy"), 2);
employeeMap.put(new Employee("anu"), 3);
System.out.println(employeeMap);
class Employee {
private String name;

public Employee(String name) {
this.name = name;
}

@Override
public String toString() {
return "Employee{" + name + '}';
}
}

The output for the above code will be

{Employee{binoy}=2, Employee{anu}=3, Employee{anu}=1}

Even though the name of the employee is the same the map is treating it as a separate key, because default implementation only compares the object reference to decide whether an object is equal or not. Note that these references are values generated by hashcode() method. 

You can override the equals() and hashcode() method to solve the above problem. Below is the sample on how to override these methods.

class Employee {
private String name;

public Employee(String name) {
this.name = name;
}

@Override
public String toString() {
return "Employee{" + name + '}';
}

@Override
public boolean equals(Object o) {
// If object is compared with itself
if (o == this) return true;

if (!(o instanceof Employee)) return false;

// typecast o to Employee to compare data members
Employee e = (Employee) o;
// Compare the data members and return accordingly
return name.compareTo(e.name) == 0;
}

@Override
public int hashCode() {
return this.name.hashCode();
}
}
public class Main {
public static void main(String[] args) {
Map<Employee, Integer> employeeMap = new HashMap<>();
employeeMap.put(new Employee("anu"), 1);
employeeMap.put(new Employee("binoy"), 2);
employeeMap.put(new Employee("anu"), 3);
System.out.println(employeeMap);
}
}

The output  now will be

{Employee{binoy}=2, Employee{anu}=3}









Static Blocks in Java

No comments:


  • Static blocks executed only once when the class is loaded into the memory for the first time.

    public class Main {
      {
          System.out.println("inside instance block");
      }
    
      static {
          System.out.println("inside static block");
      }
    
      public static void main(String[] args) {
          new Main();
          new Main();
          System.out.println("Main Program ");
      }
    }
    //output
    inside static block
    inside instance block
    inside instance block
    Main Program
    
  • A static block will be executed even if there is no main method, for example we print something on the console without creating main() method.
    class Main {
      static {
          System.out.print("Static block without main method");
      }
    }
    //output (works only if JDK version is 1.6 or previous)
    Static block without main method
    
  • Static blocks gets executed before the constructor
  • Static blocks are used for the initialization of static variables
  • A class can have any number of static blocks, and they can appear anywhere in the class body.
  • If there are multiple static blocks then those will be executed from top to bottom.

 

Instance Blocks

No comments:
  • Instance blocks can be used to initialize variables or to execute any logic during object creation.
  • Whenever an object is created, instance blocks will be executed
  • Instance blocks will be executed before constructor
  • If there are multiple instance blocks then those will be executed from top to bottom.
  • A class can have any number of instance blocks, and they can appear anywhere in the class body.
public class Main {
    private int number = 200;
    {
        System.out.println("inside instance block");
        this.number = 300;
    }
    public Main(int number) {
        System.out.println("inside constructor");
        this.number = number;
    }
    public static void main(String[] args) {
        var mainObj = new Main(100);
        System.out.println(mainObj.number);
    }
}

//output
inside instance block
inside constructor 
100 

Lambda expressions in Java

No comments:

Lambda expressions are used to provide the implementation of a functional interface. As shown below it saves a lot of code.

@FunctionalInterface
interface Shape {
    void draw();
}

public class Main {
    public static void main(String[] args) {
        //implementing Shape using anonymous class
        Shape shape1 = new Shape() {
            @Override
            public void draw() {
                System.out.println("Drawing a shape without lambda");
            }
        };
        shape1.draw();

        //implementing Shape with lambda
        Shape shape2 = () -> System.out.println("Drawing a shape with lambda");
        shape2.draw();
    }
}

Lambda expressions can be stored in variables if the variable's type is an interface which has only one method. As shown below we have stored the filter condition in a variable.

List<String> list = new ArrayList<>();
list.add("apple");
list.add("ball");
list.add("cat");
list.add("aeroplane");
Predicate<String> startsWithLetterA = s -> s.startsWith("a");
list.stream().filter(startsWithLetterA).forEach(System.out::println);

Choose valid lambda expression for the following code

List<String> list = new ArrayList<>();
list.removeIf(______________________);
  • [x] s -> s.isEmpty() //valid
  • [ ] s -> {s.isEmpty()} //invalid since return keyword is missing
  • [ ] s -> {s.isEmpty();} //invalid since return keyword is missing
  • [x] s -> {return s.isEmpty();} //valid
  • [ ] s -> {return s.isEmpty()} //invalid since semicolon expected
  • [ ] String s -> s.isEmpty() //invalid since missing parantheses around String s
  • [x] ( String s ) -> s.isEmpty() //valid
  • [ ] s -> {String s = ""; return s.isEmpty();} // invalid since variable s is already in use and cannot be redefined