Java Concepts

Java Concepts in Details



DS- Find Maximum element in Binary Tree(Not BST)


Finding Maximum in a binary tree can be done by below approach.

  1. data in current node.
  2. Find maximum in left subtree.
  3. Find maximum in right subtree.
Then Compare all three value to get maximum.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
int findMax(Node node) {
    if(node == null)
        return Integer.MIN_VALUE;
         
    int max = node.data;
    int leftMax = findMax(node.left);
    int rightMax = findMax(node.right);
     
    if (leftMax > max)
        max = leftMax;
    if (rightMax > max)
        max = rightMax;
    return max;
}

Type Promotion



Type promotion happens in method calling.

A low type can be promoted to high type.
But type promotion not happens in case of Wrapper class like Integer, Long Double.
it always happens with primitive type like int, double, char, byte..etc

  • Shot can be promoted to byte, int, long, float and double.
  • byte can be promoted to int, long, float and double.
  • char can be promoted to int, long, float and double.
  • int can be promoted to long, float and double.
  • long can be promoted to float and double.
  • float can be promoted to double.

Below type promotion can place take when we call a method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

package practice.problem;
 
public class TypePromition {
 
    public static void test(long a) {
        System.out.println("float " + a);
    }
     
    public static void test(double a) {
        System.out.println("double " + a);
    }
     
    public static void main(String[] args) {
         
        test('h'); // it will call the long method
         
        test(13L); // it will also call the long method
         
        test(13.4f); // it will call the double method
    }
}


Garbage Collection


Garbage Collector is used to destroy the objects that are not in use or unreachable. So that heap memory can be object for application.

Its a daemon thread that runs periodically , and user don’t have to worry about running or executing it.

So if you have created any object without new, you can use finalize method to perform cleanup processing (destroying remaining objects).
This thread calls the finalize() method before object is garbage collected.

Mark & Sweep:-     

        It works in two steps:-

    • Step 1: All currently running active objects are marked live.
    • Step 2: Any object that is not marked live is removed


  1. In this approach first it defines some specific root objects (local variable and input parameters of the currently executing methods, active threads, static field of the loaded classes). Now GC travels whole object graph starting from root and marks all object live that are reachable or connected in that graph. And in second step it remove all object that are not marked as live. Disadvantage: While running this approach application thread need to be stopped, as GC can not traverse the graph if it keep changing.

  1. Concurrent Mark Sweep
    • It is advanced version of Mark and Sweep. It uses multiple thread s for scanning the heap.
    • Also it runs in parallel with application threads. So gives better performance than Mark & Sweep.
  1. Serial Garbage Collection
    • This algorithm uses mark-copy for the Young Generation and mark-sweep-compact for the Old Generation. It works on a single thread.
    • while executing it suspends all application threads until GC result has been concluded.
       2. Parallel Garbage Collection
    • Like Serial GC it also uses mark-copy for the Young Generation and mark-sweep-compact for the Old Generation.
    • It works on multiple threads.
    • We can configure the number of thread in JVM arguments.
        3. G1 (Garbage First) Garbage Collection
    • This basically divides the entire heap in small small segments.
    • and each segment is marked as either Young generation or odd generation.
    • This is parallel, concurrent, incrementally compacting low pause algorithm. This keeps tracking of the amount of live data in each region or segment. This information is used in knowing that which region keeps the most garbage. and that region is collection first. that is why the name is garbage first.

The Garbage collector of JVM collects only those objects that are created by new keyword.

Garbage collection is performed by a daemon thread called Garbage Collector(GC).

Below are the algorithm used for GC.    


Thread Local


ThreadLocal can be used to provide a copy of variable for each thread.
That means each thread works on their own copy of variable and they can not see other threads variable value.

We use Thread Local to achieve thread safety apart from writing immutable classes.

InheritableThreadLocal

The InheritableThreadLocal class is a subclass of ThreadLocal.
Instead of each thread having its own value inside a ThreadLocal, the InheritableThreadLocal grants access to values to a thread and all child threads created by that thread.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

class Client implements Runnable{
    ThreadLocal<Long> threadLocal;
    Long sleepTime;
    Client(ThreadLocal<Long> threadLocal, Long sleepTime){
        this.threadLocal = threadLocal;
    this.sleepTime = sleepTime;
    }
     
    @Override
    public void run() {
        this.threadLocal.set(sleepTime);
    try {
          Thread.sleep(sleepTime);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + ": " +
        threadLocal.get());
    }  
}
 
public class ThreadLocalExample {  
  public static void main(String args[]) {
      ThreadLocal<Long> threadLocal = new ThreadLocal<Long>();
      Client client1 = new Client(threadLocal, 10000L);
      Client client2 = new Client(threadLocal, 5000L);
      new Thread(client1).start();
      new Thread(client2).start();
    }
}



Exception Class Hierarchy






Reentrant V/S Synchronization 


There are four major differences between Reentrant Lock & Synchronization.

  • Reentrant can be fairness. By passing fairness value true we can make sure the longest waited thread get the lock first.
  • On the other hand, Synchronization wont guarantee that which thread will acquire the lock first once it is released.
  • Reentrant provides tryLock method so that if the lock is not free the current thread will not be blocked, Or it will wait for provided period
  • of time. While in synchronization,all threads trying to acquire the lock will be block if the lock is not released.
  • In reentrant a lock can be acquire in one method and it can be unlocked in another method. While in Synchronization there is no such way, and lock is put on a
  • block, method or scope.
  • ReentrantLock also provides convenient method to get List of all threads waiting for lock.

Reentrant Lock, Cyclic Barrier, Count Down Latch




Cyclic Barrier: (Start & Wait) – Used to make threads wait for each other. It can be reset.
https://www.geeksforgeeks.org/java-util-concurrent-cyclicbarrier-java/

Count Down Latch: (Wait & Start) – Used to make sure that a task waits for other threads before it starts.

https://www.geeksforgeeks.org/countdownlatch-in-java/


FixedThreadPool v/s CachedThreadPool


Fixed Thread Pool:

In Fixed thread pool a pool is created of fixed size.
And every time a new job comes, then a thread is pooled from the pool and assigned the job.
In some scenario when all thread are busy serving another request, the the new request/job has to wait in queue. As soon as a thread finishes its current request, it takes the new waiting request.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

/*
         * Below pool a fixed size (5) threads pool, where only 5 threads will be there
         */
        ExecutorService fixedThredPool = Executors.newFixedThreadPool(5);
        for(int i =0; i<100; i++) {
            fixedThredPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }


Cashed Thread Pool:

Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks. Calls to execute will reuse previously constructed threads if available. If no existing thread is available, a new thread will be created and added to the pool. Threads that have not been used for sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will not consume any resources.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
         * The below thread pool is cached thread pool.
         * Here when a new job comes, a new thread is created and after completing its task that thread is kept in pool till 60 seconds.
         * And if within 30 seconds a new job comes then the same thread is pooled out and assigned the job.
         */
        ExecutorService cachedThredPool = Executors.newCachedThreadPool();
        for(int i =0; i<100; i++) {
            cachedThredPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

Returning Outcome:

We need to call submit method of ExecutorService when we want our thread to return some outcome. The submit method implements the Callable interface. The submit method is also capable of implementing the Runnable interface, but in that case it wont return any outcome.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> outcome =
     executor.submit(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            return 10;
        }
     });
System.out.println("outcome: "+ outcome.get());
         
//Submit method implementing Runnable interface
    executor.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println("No outcome thread...");
        }
    });


Threading- Print Even Odd Using two Threads


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

/**
 *
 */
package practice.problem;
 
/**
 * @author chandan
 *
 */
 
class PrintEvenOdd implements Runnable {
 
    static volatile int val = 1;
 
     
    @Override
    public void run() {
        while(val <= 10) {
            print();
        }
    }
     
     
    synchronized private void print() {
        notify();
        try {
            if(Thread.currentThread().getName().equals("EvenThread")) {
                if(val % 2 == 1)
                    return;
            }
            if(Thread.currentThread().getName().equals("OddThread")) {
                if(val % 2 == 0)
                    return;
            }
            System.out.println(Thread.currentThread().getName() + " " +  val++);
                wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
     
}
public class ThreadPrintEvenOdd {
 
    public static void main(String args[]) {
        PrintEvenOdd print = new PrintEvenOdd();
        Thread even = new Thread(print, "EvenThread");
        Thread odd = new Thread(print, "OddThread");
        odd.start();
        even.start();
         
         
    }
}



Sorting a HashMap


A hash map can be sorted by using below approaches.

  • Using a TreeMap (default sorting based on key).
  • Sorting using Comparator (Functional Interface).
  • Sorting using Lambda Expression.
  • Sorting Using Java Stream API.

Please See the below example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171

/**
 *
 */
package practice.problem;
 
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
 
/**
 * @author chandan
 *
 */
 
 
public class HashMapSorting {
    public static void main(String args[]) {
         
        HashMapSorting ob = new HashMapSorting();
         
        Map<String, Integer> map = new HashMap<String, Integer>();
        map.put("Guvava", 3);
        map.put("Orange", 5);
        map.put("Apple", 1);
        map.put("Grapes", 2);
        map.put("Banana", 4);
         
        ob.sortingUsingTreeMap(map);
        ob.sortingUsingComparator(map);
        ob.sortingUsingLambdaExpression(map);
        ob.sortingUsingStream(map);
         
        /*
         *
         * Now we will see how to sort a map using stream, when that map is having custom object as key
         */
        Map<Fruite, Integer> fruiteMap = new HashMap<Fruite, Integer>();
        Fruite guvava =     new Fruite("Guvava", 3);
        Fruite orange =     new Fruite("Orange", 5);
        Fruite apple =      new Fruite("Apple", 1);
        Fruite grapes =     new Fruite("Grapes", 2);
        Fruite banana =     new Fruite("Banana", 4);
         
        fruiteMap.put(guvava, 3);
        fruiteMap.put(orange, 5);
        fruiteMap.put(apple, 1);
        fruiteMap.put(grapes, 2);
        fruiteMap.put(banana, 4);
         
        ob.sortingFruiteMapUsingStream(fruiteMap);
    }
     
     
    //Sorting using tree map
    //This method sorts the map on key (default)
    public void sortingUsingTreeMap(Map<String, Integer> map) {
        Map<String, Integer> treeMap = new TreeMap<String, Integer>(map);
        List<Entry<String, Integer>> list = new LinkedList<Entry<String, Integer>>(treeMap.entrySet());
        printMap(list, "Sorting Using Tree Map");
    }
     
     
    //Sorting using comparator
    //This approach we can use to sort a map based on key or value
    public void sortingUsingComparator(Map<String, Integer> map) {
        List<Entry<String, Integer>> list = new LinkedList<Entry<String, Integer>>( map.entrySet());
         
        //Sorting Based on Key
        Collections.sort(list, new Comparator<Entry<String, Integer>>() {
            @Override
            public int compare(Entry<String, Integer> element1, Entry<String, Integer> element2) {
                return element1.getKey().compareTo(element2.getKey());
            }
        });
        printMap(list, "Sorting Usgin Comparator, Based on Key");
         
         
        //Sorting based on value
        Collections.sort(list, new Comparator<Entry<String, Integer>>() {
 
            @Override
            public int compare(Entry<String, Integer> element1, Entry<String, Integer> element2) {
                return element1.getValue() - element2.getValue();
            }
        });
        printMap(list, "Sorting Usgin Comparator, Based on Value");
    }
     
     
    /*
     * Below method is lambda expression version of above method (sortingUsingComparator).
     * Since Comparator is a function interface so we can use lambda expression also to sort a map.
     */
    public void sortingUsingLambdaExpression(Map<String, Integer> map) {
        List<Entry<String, Integer>> list = new LinkedList<>(map.entrySet());
         
        //Sorting based on key
        Collections.sort(list, (o1, o2) -> o1.getKey().compareTo(o2.getKey()));
        printMap(list, "Sorting Usgin Lambda Expression, Based on Key");
        //Sorting based on value
         
        Collections.sort(list, (o1,o2) -> o1.getValue() - o2.getValue());
        printMap(list, "Sorting Usgin Lambda Expression, Based on Value");
    }
     
     
    //Sorting using stream API
    public void sortingUsingStream(Map<String, Integer> map) {
        System.out.println("Sorting Usgin Stream API, Based on Key");
        map.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(System.out::println);
        System.out.println("Sorting Usgin Stream API, Based on Value");
        map.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(System.out::println);
    }
     
     
    //Sorting custom object map using streamAPI
    public void sortingFruiteMapUsingStream(Map<Fruite, Integer> fruiteMap) {
        System.out.println("Sorting Custom Object Usgin Stream API, Based on Fruite Name");
        fruiteMap.entrySet().stream().sorted(Map.Entry.comparingByKey(Comparator.comparing(Fruite :: getName))).forEach(System.out::println);
         
        System.out.println("Sorting Custom Object Usgin Stream API, Based on Fruite ID");
        fruiteMap.entrySet().stream().sorted(Map.Entry.comparingByKey(Comparator.comparing(Fruite:: getId))).forEach(System.out::println);
         
        System.out.println("Sorting Custom Object Usgin Stream API, Based on Fruite ID in reverse order");
        fruiteMap.entrySet().stream().sorted(Map.Entry.comparingByKey(Comparator.comparing(Fruite:: getId).reversed())).forEach(System.out::println);
    }
     
     
    private void printMap(List<Entry<String, Integer>> entryList, String description) {
        System.out.println(description);
        for(Entry<String, Integer> entry: entryList) {
            System.out.println("Key: " + entry.getKey() + "," + "Value: " + entry.getValue());;
        }
    }
}
 
 
class Fruite{
    String name;
    Integer id;
     
    public Fruite(String name, Integer id) {
        this.name = name;
        this.id  = id;
    }
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
     
    @Override
    public String toString() {
        return name + " " + id;
    }
}


Sorting Algorithm Complexity




Exception Handling in Overriding


We can have following two cases in method overriding.

Case 1: Super class method does not declares/throws an exception.


In this case method in subclass can have below two choices.

  • It may not declare/throw any exception.
  • It can overridden method cannot declare the checked exception but it can declare unchecked exception (Runtime Exception).

In this case method in subclass can have below three choices.
  • It may not declare/throw any exception.
  • It can declare/throw the same exception that super class method has.
  • It can declare/throw sub exception of the exception that super class method has.

Case 2: Super class declares/throws an exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

class SuperClass{
     
    public void noExceptionMethod() {
        System.out.println("This method does not throw any exception");
    }
     
    public void noExceptionMethod(Long orderNumber) {
        System.out.println("This method does not throw any exception");
    }
     
    public void excpetionMethod(String accountNumber) throws RuntimeException{
        if(accountNumber == null)
            throw new RuntimeException("invalid account");
    }
     
     
    public void excpetionMethod(Long orderNumber) throws Exception{
        if(orderNumber == null)
            throw new RuntimeException("invalid order");
    }
     
     
    public void superExceptionMethod() throws RuntimeException{
         
    }
}
 
 
class ChildClass extends SuperClass{
     
    //No Error
    @Override
    public void noExceptionMethod() {
        System.out.println("This method does not throw any exception");
    }
     
    //No error, it can throw runtime exception
    @Override
    public void noExceptionMethod(Long orderNumber)  throws RuntimeException{
        System.out.println("This method does not throw any exception");
    }
     
 
//  Below method gives "Exception Exception is not compatible with throws clause in SuperClass.noExceptionMethod(Long)"
     
    //Error, This is not allowed   
//  @Override
//  public void noExceptionMethod(Long orderNumber)  throws Exception{
//      System.out.println("This method does not throw any exception");
//  }
     
    //No Error
    @Override
    public void excpetionMethod(String accountNumber){
        if(accountNumber == null)
            throw new RuntimeException("invalid account");
        System.out.println("This method does not throw any exception");
    }
     
     
    @Override
    public void excpetionMethod(Long accountNumber) throws RuntimeException{
        if(accountNumber == null)
            throw new RuntimeException("invalid account");
        System.out.println("This method does not throw any exception");
    }
     
    //Error, not allowed
//  @Override
//  public void superExceptionMethod() throws Exception{
//     
//  }
     
    @Override
    public void superExceptionMethod() throws RuntimeException{
         
    }
}



Serilizable vs Externalizable


We implement java.io.Externalizable interface if we want to serialize/desrialize only some specific attributes from a class.
The main disadvantage of using java.io.Serializable interface is that it serialize/desrialize the complete class. That means if a class is having even thousand attributes then it will serialize/desrialize all thousand attributes. With Serializable we can not skip any attribute.

So to get control over serialize/desrialize, we need to implement java.io.Externalizable.

In the target class we override public void writeExternal(ObjectOutput out) and public void readExternal(ObjectInput in) method from java.io.Externalizable interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

/**
 *
 */
package practice.problem;
 
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
 
 
class Student implements Externalizable{
 
    String fName;
    String lName;
    int roll;
    boolean regular;
     
    public Student(){
         
    }
     
    public Student(String fName, String lName, int roll, boolean r){
        this.fName = fName;
        this.lName = lName;
        this.roll = roll;
        this.regular = r;  
    }
     
     
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeChars(fName);
        out.writeChars(lName);
        out.writeInt(roll);
        out.writeBoolean(regular);
         
    }
 
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        fName = in.readLine();
        lName = in.readLine();
        roll = in.readInt();
        regular = in.readBoolean();
         
    }
 
    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Student [fName=" + fName + ", lName=" + lName + ", roll=" + roll + ", regular=" + regular + "]";
    }
}
 
public class ExternizableClient {
     
     
    private static void writeStudent(Student student) throws IOException {
        FileOutputStream fos = new FileOutputStream("student.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(student);
        oos.close();
         
    }
     
     
    private static Student readStudent() {
        try(FileInputStream fis = new FileInputStream("student.txt")){
            ObjectInputStream ois = new ObjectInputStream(fis);
            Student student = (Student)ois.readObject();
            return student;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
     
     
    public static void main(String args[]) throws IOException {
        Student newStudent = new Student("chandan", "Singh", 3088, true);
        writeStudent(newStudent);
        System.out.println(readStudent());
         
         
    }
 
}


Visitor