Wednesday, March 30, 2022
this vs super in Java
this vs super
- this can be used to read all members declared within the class
- this can all be used to read all inherited members in the direct parent class
- this can be used in an instance method, constructor and instance block
- this cannot be used in a static method or static initializer block
- super can read only inherited members declared in the parent class
- super excludes any members found in the current class
- super always refer to the direct parent
- this() can be used to invoke current class constructor
- super() invokes constructor of the parent class
- if we choose to call this() or super() , it must be the first statement in the constructor body. There can be only one call.
- this refers to an instance of the class, while this() refers to a constructor call within the class
- Java compiler automatically inserts a call to the no-argument constructor super() if we do not explicitly call this() or super() as the first line of a constructor.
- static methods do not have reference to this or super
Constructor in Java
Constructor
- A special method that matches the name of the class and has no return type
- Constructors are executed when a new instance of the class is created (
new Main()). This process is called instantiation, because it creates a new instance of the class. - The method name and the class name should be exactly same (note that Java is case sensitive)
Constructors can include parameters, like arrays, primitive types or generics. But cannot include var. The below constructor will not compile.
class Main { public Main( var number ) { } }- There can be multiple constructors in a class, provided that the constructor parameters are distinct.
- Declaring multiple constructors with different signatures is reffered to as constructor overloading.
- Every class in Java will have a constructor. If you didn't code it Java will include the default no-argument constructor automatically during compilation process.
Private Constructor is a special constructor which is declared as private. Having private constructors prevents other classes from the class. It is generally used when the class has only static methods.
class Main { private Main() { } }- Classes with a private constructor can be extended only by an inner class, because an inner class is the only one that can access a private constructor by calling super().
Monday, March 28, 2022
Comparable vs Comparator in Java
Comparable Interface
is used to sort the objects of a user defined class. It contains only one method: compareTo()
public interface Comparable<T>{
int compareTo(T o);
}
We can implement the logic of the sorting into the compareTo() method (an example is given below). compareTo() method must returns an integer based on the following rules:
- 0 is returned when current object is equal to the argument object
- -1 is returned when current object is smaller than the argument object
- 1 is returned when current object is greater than the argument object
class Employee implements Comparable<Employee> {
private String empCode;
private String name;
private int age;
//not including getters, setters, constructor etc
@Override
public String toString() {
return "Emp{" + name + "," + age + "," + empCode + "}";
}
@Override
public int compareTo(Employee o) {
int result = this.name.compareTo(o.getName());
if (result != 0) return result;
return this.getAge() - o.getAge();
}
}
public class Main {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("402", "anu", 35));
employees.add(new Employee("399", "binoy", 32));
employees.add(new Employee("400", "anu", 31));
//printing before sort
System.out.println(employees); //outputs [Emp{anu,35,402}, Emp{binoy,32,399}, Emp{anu,31,400}]
Collections.sort(employees);
//printing after sort
System.out.println(employees); //outputs [Emp{anu,31,400}, Emp{anu,35,402}, Emp{binoy,32,399}]
}
}
Comparator Interface
Comparator is also an interface which is used to sort the objects of a user defined class. It contains compare() method. Using Comparable interface we can implement only one sorting logic. What if we need to implement sorting logic based on other data members also. At that time Comparator becomes handy. In the above example, of Comparable interface, if we need to sort the employees based on their employee code (empCode). Then we can implement it by modifying the main code as shown below:
//sorting based on empCode
Comparator<Employee> comparatorByEmpCode = (e1, e2) -> e1.getEmpCode().compareTo(e2.getEmpCode());
Collections.sort(employees, comparatorByEmpCode);
System.out.println(employees); //outputs [Emp{binoy,32,399}, Emp{anu,31,400}, Emp{anu,35,402}]
We can also combine the comparators to get a particular sorting sequence. For example say we need to display the employee based on their name. But when name is same then the employee with smallest age will be listed first. Find the example below:-
Comparator<Employee> comparatorByName = Comparator.comparing(Employee::getName);
Comparator<Employee> comparatorByAge = Comparator.comparing(Employee::getAge);
Comparator<Employee> comparatorCombined = comparatorByName.thenComparing(comparatorByAge);
Collections.sort(employees, comparatorCombined);
System.out.println(employees);
Comparable vs Comparator
As seen above both are used for sorting purposes. The differences between them are as follows:
- Comparable provides only a single sorting logic whereas with the help of Comparator we can implement multiple sorting logic.
- Comparable has compareTo() method with a single parameter. Comparator has compare() method with two parameters
- Comparable is present in java.lang package while Comparator is present in java.util package
- To implement sorting using Comparable we need to modify the class, but in the case of Comparator we do not need to modify the class.
- If a class implements Comparable interface then collection of that object can be sorted using Collections.sort() or Arrays.sort() method. They will be sorted based on the logic defined by compareTo() method.
Friday, March 25, 2022
Internal Working of HashMap in Java
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<>();
- 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
- 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
- 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?
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
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
- 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
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
