Showing posts with label Interview Questions. Show all posts
Showing posts with label Interview Questions. Show all posts

Programs asked in interviews

Find out square root of a given number without using any built-in functions

    public static void findSquareRoot(double number)
    {
        boolean isPositiveNumber = true;
        double g1;
        //if the number given is a 0
        if(number==0)
        {
            System.out.println("Square root of "+number+" = "+0);
        }
        //If the number given is a -ve number
        else if(number<0)
        {
            number=-number;
            isPositiveNumber = false;
        }
        //Proceeding to find out square root of the number
        double squareRoot = number/2;
        do
        {
            g1=squareRoot;
            squareRoot = (g1 + (number/g1))/2;
        }
        while((g1-squareRoot)!=0);
        //Displays square root in the case of a positive number
        if(isPositiveNumber)
        {
            System.out.println("Square roots of "+number+" are ");
            System.out.println("+"+squareRoot);
            System.out.println("-"+squareRoot);
        }
        //Displays square root in the case of a -ve number
        else
        {
            System.out.println("Square roots of -"+number+" are ");
            System.out.println("+"+squareRoot+" i");
            System.out.println("-"+squareRoot+" i");
        }
    }

Find out if a year is LEAP Year

//Flag to store the test result

        boolean isLeapYear = false;
        if(year % 400 == 0)
        {
            isLeapYear = true; //400 -> TRUE
        }
        else if (year % 100 == 0)
        {
            isLeapYear = false; //100 -> FALSE
        }
        else if(year % 4 == 0)
        {
            isLeapYear = true; //4 -> TRUE
        }
        else
        {
            isLeapYear = false;
        }

Find out 2^n

            int power = 1;
            int i = 0;
            System.out.println("Powers of 2 that are less than 2^"+n);
            while (i <= n)
            {
                System.out.println("2^"+i+" = " + power);
                power = power * 2;
                i++;
            }
        }

 Generate Fibonacci series

        //Assigning the first two elements
        long[] FibonacciSeries = new long[limit];
        FibonacciSeries[0] = 0;
        FibonacciSeries[1] = 1;
        //Generates the Fibonacci series and store it in the array FibonacciSeries
        for(int i=2; i < limit; i++)
        {
            FibonacciSeries[i] = FibonacciSeries[i-1] + FibonacciSeries[i-2];
        }

 

Click here to View more...

Mirror a B-Tree

public class BinarySearchTree {
 
 public class Node {
  private int data;
  private Node leftChild, rightChild;
 }

 public static Node mirrorBST(Node node) {
   
  if (node.equals(null)) return(node);
  
  // swap children of this node
  Node tmp;
  tmp = node.leftChild;
  node.leftChild = node.rightChild;
  node.rightChild = tmp;
   
  // do the subtrees - RECURSSION
  if (node.leftChild != null) {
   mirrorBST(node.leftChild);
  }
  if (node.rightChild != null) 
   mirrorBST(node.rightChild);
   
  return (node);
 }
}
Click here to View more...

Chain Comparators

class StudentFirstNameComparator implements Comparator{
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getFirstName().compareTo(o2.getFirstName());
    }
}




class StudentLastNameComparator implements Comparator{
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getLastName().compareTo(o2.getLastName());
    }
}


public class StudentChainedComparator implements Comparator{

    List> chainList;
   
    public StudentChainedComparator(Comparator... comparators) {
        this.chainList = Arrays.asList(comparators);
    }

    @Override
    public int compare(Student o1, Student o2) {
        for( Comparator comp : chainList) {
            int result = comp.compare(o1, o2);
            if (result != 0) {
                return result;
            }
        }
        return 0;
    }

}
 

Test Class

Collections.sort(list,
new StudentChainedComparator(new StudentLastNameComparator(),new StudentFirstNameComparator()));
Click here to View more...

ConcurrentHashMap implementation

As suggested earlier, the data structure used by ConcurrentHashMap is similar in implementation to that of Hashtable or HashMap, with a resizable array of hash buckets, each consisting of a chain of Map.Entry elements, as shown in Listing 1. Instead of a single collection lock, ConcurrentHashMap uses a fixed pool of locks that form a partition over the collection of buckets.
Listing 1. Map.Entry elements used by ConcurrentHashMap
protected static class Entry implements Map.Entry {
    protected final Object key;
    protected volatile Object value;
    protected final int hash;
    protected final Entry next;
    ...
}

Traversing data structures without locking

Unlike Hashtable or a typical lock-pool Map implementation, ConcurrentHashMap.get() operations do not necessarily entail acquiring the lock associated with the relevant bucket. In the absence of locking, the implementation must be prepared to deal with stale or inconsistent values of any variables it uses, such as the list head pointer and the fields of the Map.Entry elements (including the link pointers that comprise the linked list of entries for each hash bucket).
Most concurrent classes use synchronization to ensure exclusive access to -- and a consistent view of -- a data structure. Instead of assuming exclusivity and consistency, the linked list used by ConcurrentHashMap is designed carefully so that the implementation can detectthat its view of the list is inconsistent or stale. If it detects that its view is inconsistent or stale, or simply does not find the entry it is looking for, it then synchronizes on the appropriate bucket lock and searches the chain again. This optimizes lookup in the common case -- where most retrievals are successful and retrievals outnumber insertions and removals.

Exploiting immutability

One significant source of inconsistency is avoided by making the Entry elements nearly immutable -- all fields are final, except for the value field, which is volatile. This means that elements cannot be added to or removed from the middle or end of the hash chain -- elements can only be added at the beginning, and removal involves cloning all or part of the chain and updating the list head pointer. So once you have a reference into a hash chain, while you may not know whether you have a reference to the head of the list, you do know that the rest of the list will not change its structure. Also, since the value field is volatile, you will be able to see updates to the value field immediately, greatly simplifying the process of writing a Map implementation that can deal with a potentially stale view of memory.
While the new JMM provides initialization safety for final variables, the old JMM does not, which means that it is possible for another thread to see the default value for a final field, rather than the value placed there by the object's constructor. The implementation must be prepared to detect this as well, which it does by ensuring that the default value for each field of Entry is not a valid value. The list is constructed such that if any of the Entry fields appear to have their default value (zero or null), the search will fail, prompting the get() implementation to synchronize and traverse the chain again.

Retrieval operations

Retrieval operations proceed by first finding the head pointer for the desired bucket (which is done without locking, so it could be stale), and traversing the bucket chain without acquiring the lock for that bucket. If it doesn't find the value it is looking for, it synchronizes and tries to find the entry again, as shown in Listing 2:
Listing 2. ConcurrentHashMap.get() implementation
  public Object get(Object key) {
    int hash = hash(key);     // throws null pointer exception if key is null

    // Try first without locking...
    Entry[] tab = table;
    int index = hash & (tab.length - 1);
    Entry first = tab[index];
    Entry e;

    for (e = first; e != null; e = e.next) {
      if (e.hash == hash && eq(key, e.key)) {
        Object value = e.value;
        // null values means that the element has been removed
        if (value != null) 
          return value;
        else
          break;
      }
    }

    // Recheck under synch if key apparently not there or interference
    Segment seg = segments[hash & SEGMENT_MASK];
    synchronized(seg) { 
      tab = table;
      index = hash & (tab.length - 1);
      Entry newFirst = tab[index];
      if (e != null || first != newFirst) {
        for (e = newFirst; e != null; e = e.next) {
          if (e.hash == hash && eq(key, e.key)) 
            return e.value;
        }
      }
      return null;
    }
  }

Removal operations

Because a thread could see stale values for the link pointers in a hash chain, simply removing an element from the chain would not be sufficient to ensure that other threads will not continue to see the removed value when performing a lookup. Instead, as Listing 3 illustrates, removal is a two-step process -- first the appropriate Entry object is found and its value field is set to null, and then the portion of the chain from the head to the removed element is cloned and joined to the remainder of the chain following the removed element. Since the value field is volatile, if another thread is traversing the stale chain looking for the removed element, it will see the null value field immediately, and know to retry the retrieval with synchronization. Eventually, the initial part of the hash chain ending in the removed element will be garbage collected.
Listing 3. ConcurrentHashMap.remove() implementation
  protected Object remove(Object key, Object value) {
    /*
      Find the entry, then 
        1. Set value field to null, to force get() to retry
        2. Rebuild the list without this entry.
           All entries following removed node can stay in list, but
           all preceding ones need to be cloned.  Traversals rely
           on this strategy to ensure that elements will not be
          repeated during iteration.
    */

    int hash = hash(key);
    Segment seg = segments[hash & SEGMENT_MASK];

    synchronized(seg) {
      Entry[] tab = table;
      int index = hash & (tab.length-1);
      Entry first = tab[index];
      Entry e = first;

      for (;;) {
        if (e == null)
          return null;
        if (e.hash == hash && eq(key, e.key)) 
          break;
        e = e.next;
      }

      Object oldValue = e.value;
      if (value != null && !value.equals(oldValue))
        return null;
     
      e.value = null;

      Entry head = e.next;
      for (Entry p = first; p != e; p = p.next) 
        head = new Entry(p.hash, p.key, p.value, head);
      tab[index] = head;
      seg.count--;
      return oldValue;
    }
  }
Figure 1 illustrates a hash chain before an element is removed:
Figure 1. Hash chain
Figure 1. Hash chain
Figure 2 illustrates the chain with element 3 removed:
Figure 2. Removal of an element
Figure 2. Removal of an element

Insertion and update operations

The implementation of put() is straightforward. Like remove(), put() holds the bucket lock for the duration of its execution, but because get() doesn't always need to acquire the lock, this doesn't necessarily block readers from executing (nor does it block other writers from accessing other buckets). First, put() searches the appropriate hash chain for the desired key. If it is found, then the value field (which is volatile) is simply updated. If it is not found, a new Entry object is created to describe the new mapping and is inserted at the head of the list for that bucket.

Weakly consistent iterators

The semantics of iterators returned by ConcurrentHashMap differ from those of java.util collections; rather than being fail-fast (throwing an exception if the underlying collection is modified while an iterator is being used), they are instead weakly consistent. When a user calls keySet().iterator() to retrieve an iterator for the set of hash keys, the implementation briefly synchronizes to make sure the head pointers for each chain are current. The next() and hasNext() operations are defined in the obvious way, traversing each hash chain and then moving on to the next chain until all the chains have been traversed. Weakly consistent iterators may or may not reflect insertions made during iteration, but they will definitely reflect updates or removals for keys that have not yet been reached by the iterator, and will not return any value more than once. The iterators returned by ConcurrentHashMap will not throw ConcurrentModificationException.

Dynamic resizing

As the number of elements in the map grows, the hash chains will grow longer and retrieval time will increase. At some point, it makes sense to increase the number of buckets and rehash the values. In classes like Hashtable, this is easy because it is possible to hold an exclusive lock on the entire map. In ConcurrentHashMap, each time an entry is inserted, if the length of that chain exceeds some threshold, that chain is marked as needing to be resized. When enough chains vote that resizing is needed, ConcurrentHashMap will use recursion to acquire the lock on each bucket and rehash the elements from each bucket into a new, larger hash table. Most of the time, this will occur automatically and transparently to the caller.

No locking?

To say that successful get() operations proceed without locking is a bit of an overstatement, as the value field of Entry is volatile, and this is used to detect updates and removals. At the machine level, volatile and synchronization often end up getting translated into the same cache coherency primitives, so there effectively is some locking going on here, albeit at a finer granularity and without the scheduling or JVM overhead of acquiring and releasing monitors. But, semantics aside, the concurrency achieved by ConcurrentHashMap in many common situations, where retrievals outnumber insertions and removals, is quite impressive.
Click here to View more...

ExecutorService vs ExecutorCompletionService

Suppose we have list of four tasks: Task A, Task B, Task C and Task D which perform some complex computation and result into an integer value. These tasks may take random time depending upon various parameters. We can submit these tasks to executor as:
ExecutorService executorService = Executors.newFixedThreadPool(4);
List futures = new ArrayList>();
futures.add(executorService.submit(A));
futures.add(executorService.submit(B));
futures.add(executorService.submit(C));
futures.add(executorService.submit(D));

Then we can iterate over the list to get the computed result of each future:
for (Future future:futures) {
    Integer result = future.get();
    // rest of the code here.
}
Now the similar functionality can also be achieved using ExecutorCompletionService as:
ExecutorService executorService = Executors.newFixedThreadPool(4);
CompletionService executorCompletionService= new ExecutorCompletionService<>(executorService );
Then again we can submit the tasks and get the result like:
List futures = new ArrayList>();
futures.add(executorCompletionService.submit(A));
futures.add(executorCompletionService.submit(B));
futures.add(executorCompletionService.submit(C));
futures.add(executorCompletionService.submit(D));

for (int i=0; i
Now what is the difference between the two?  Suppose task B finished first followed by task C. But task A was still going on. In that case when using ExecutorService the for loop would be waiting for the result of task A to be available. So in case of ExecutorService tasks will be processed in the same order in which they were submitted. But in later case the tasks will be processed in order the result becomes available, the order tasks are completed. One interesting example is where we want to download some file which can be downloaded from various mirrors. In that case we can quickly get the response from the server which is located closest to us. In that case we can get the first available result and discard the others. Consider the following example from Java Doc:
void solve(Executor e, Collection> solvers) 
      throws InterruptedException {
        CompletionService ecs = new ExecutorCompletionService(e);
        int n = solvers.size();
        List> futures = new ArrayList>(n);
        Result result = null;
        try {
            for (Callable s : solvers)
                futures.add(ecs.submit(s));
            for (int i = 0; i < n; ++i) {
                try {
                    Result r = ecs.take().get();
                    if (r != null) {
                        result = r;
                        break;
                    }
                } catch(ExecutionException ignore) {}
            }
        }
        finally {
            for (Future f : futures)
                f.cancel(true);
        }

        if (result != null)
            use(result);
    }
In the above example the moment we get the result we break out of the loop and cancel out all the other futures. One important thing to note is that the implementation ofExecutorCompletionService contains a queue of results. We need to remember the number of tasks we have added to the service and then should use take or poll to drain the queue otherwise a memory leak will occur. Some people use the Future returned by submit to process results and this is NOT correct usage. There is one interesting solution provided by Dr. Heinz M, Kabutz here.
Peeking into ExecutorCompletionService  When we look inside the code for this we observe that it makes use of Executor,AbstractExecutorService (default implementations of ExecutorService execution methods) and BlockingQueue (actual instance is of class LinkedBlockingQueue>).
public class ExecutorCompletionService implements CompletionService {
    private final Executor executor;
    private final AbstractExecutorService aes;
    private final BlockingQueue> completionQueue;
    // Remaining code..
}
Another important thing to observe is class QueueingFuture which extends FutureTaskand in the method done() the result is pushed to queue.
private class QueueingFuture extends FutureTask {
        QueueingFuture(RunnableFuture task) {
            super(task, null);
            this.task = task;
        }
        protected void done() { completionQueue.add(task); }
        private final Future task;
    }
For the curios ones, class FutureTask is base implementation of Future interface with methods to start and cancel a computation, query to see if the computation is complete, and retrieve the result of the computation. And constructor takes RunnableFuture as parameter which is again an interface which extends Future interface and adds only one method run as:
public interface RunnableFuture extends Runnable, Future {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}
Click here to View more...

Interface Method Declarations

public interface MyInterface {

// In interface method can only be PUBLIC or DEFUALT
public void methpublic();
void methDefualt();

// In interface method CAN NOT BE private Static Protected
//private void methprivate();
//static void methstatic();
//protected void methprotected();
}

Click here to View more...

MergeSort - Collections.sort() uses Tim Sort



Click here to View more...

Why ConcurrentHashMap does NOT allow NULL keys and values?

The main reason that nulls aren't allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can't be accommodated. The main one is that if map.get(key) returns null, you can't detect whether the key explicitly maps to null vs the key isn't mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls.


if (m.containsKey(k)) {
   return m.get(k);
} else {
   throw new KeyNotPresentException();
}
Since m is a concurrent map, key k may be deleted between the containsKey and get calls, causing this snippet to return a null that was never in the table, rather than the desired KeyNotPresentException.

Normally you would solve that by synchronizing, but with a concurrent map that of course won't work. Hence the signature for get had to change, and the only way to do that in a backwards-compatible way was to prevent the user inserting null values in the first place, and continue using that as a placeholder for "key not found".

Click here to View more...

Singleton

Lazy initialization

public class SingletonDemo {
    private static volatile SingletonDemo instance;
    private SingletonDemo() { }

    public static SingletonDemo getInstance() {
        if (instance == null ) {
            synchronized (SingletonDemo.class) {
                if (instance == null) {
                    instance = new SingletonDemo();
                }
            }
        }

        return instance;
    }
} 
 

Eager initialization - using static factory method

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
 

Static block initialization

public class Singleton {
    private static final Singleton instance;

    static {
        try {
            instance = new Singleton();
        } catch (Exception e) {
            throw new RuntimeException("Darn, an error occurred!", e);
        }
    }

    public static Singleton getInstance() {
        return instance;
    }

    private Singleton() {
        // ...
    }
}

 

Initialization-on-demand holder 

public class Singleton {
        // Private constructor. Prevents instantiation from other classes.
        private Singleton() { }

        /**
         * Initializes singleton.
         *
         * {@link SingletonHolder} is loaded on the first execution of {@link Singleton#getInstance()} or the first access to
         * {@link SingletonHolder#INSTANCE}, not before.
         */
        private static class SingletonHolder {
                private static final Singleton INSTANCE = new Singleton();
        }

        public static Singleton getInstance() {
                return SingletonHolder.INSTANCE;
        }
}
 
 
 

Handle Serialization - readResolve()

 //readResolve to prevent another instance of Singleton

    private Object readResolve(){

        return INSTANCE;

    }

 

Uasing ENUM

 
public enum AnimalHelperSingleton {

    INSTANCE;

    private AnimalHelperSingleton(){

    }
 
   /**Add some behavior to the object. */
    public Animal[] buildAnimalList(){
        final Animal[] animals = new Animal[10];

        animals[0] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 
                "Dog", true, Color.GRAY);
        animals[1] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 
                "Cat", true, Color.YELLOW);
        animals[2] = new SimpleAnimal(Animal.AnimalClass.AMPHIBIAN,
                "Frog", true, Color.GREEN);
        animals[3] = new SimpleAnimal(Animal.AnimalClass.BIRD,
                "Crow", true, Color.BLACK);
        animals[4] = new SimpleAnimal(Animal.AnimalClass.BIRD,
                "Cardinal", true, Color.RED);
        animals[5] = new SimpleAnimal(Animal.AnimalClass.ARTHROPOD,
                "Mantis", false, Color.GREEN);
        animals[6] = new SimpleAnimal(Animal.AnimalClass.ARTHROPOD,
                "Spider", false, Color.ORANGE);
        animals[7] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 
                "Tiger", true, Color.ORANGE);
        animals[8] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 
                "Bear", true, Color.BLACK);
        animals[9] = new SimpleAnimal(Animal.AnimalClass.BIRD, 
                "Owl", true, Color.BLACK);

        return animals;
    }

}  
//Call singleton to build the animal list.
Animal[] animals = AnimalHelperSingleton.INSTANCE.buildAnimalList();

OR

/** Preferred style for singletons. */
public enum SantaClaus {
  INSTANCE;
  
  /**Add some behavior to the object. */
  public void distributePresents(){
    //elided    
  }
  
  /** Demonstrate use of SantaClaus. */
  public static void main(String... aArgs){
    SantaClaus fatGuy = SantaClaus.INSTANCE;
    fatGuy.distributePresents();
    
    //doesn't compile :
    //SantaClaus fatGuy = new SantaClaus();
  }
} 

 

 

 

 
Click here to View more...

Handling Exceptions in Thread - Thread.UncaughtExceptionHandler

A task queued with execute() that generates some Throwable will cause the UncaughtExceptionHandler for the Thread running the task to be invoked. The default UncaughtExceptionHandler, which typically prints the Throwable stack trace to System.err, will be invoked if no custom handler has been installed.

On the other hand, a Throwable generated by a task queued with submit() will bind the Throwable to the Future that was produced from the call to submit(). Calling get() on that Future will throw an ExecutionException with the original Throwable as its cause (accessible by calling getCause() on the ExecutionException).


If you want to process exceptions thrown by the task, then it is generally better to use Callable rather than Runnable.
Callable.call() is permitted to throw checked exceptions, and these get propagated back to the calling thread:

Future future = executorService.submit(new Callable(){
    public Object call() throws Exception {
        System.out.println("Asynchronous Callable");
        return "Callable Result";
    }
});

try {
   future.get();
} catch (ExecutionException ex) {
   ex.getCause().printStackTrace();
}
 


If Callable.call() throws an exception, this will be wrapped in an ExecutionException and thrown by Future.get().
This is likely to be much preferable to subclassing ThreadPoolExecutor. It also gives you the opportunity to re-submit the task if the exception is a recoverable one.



With normal Thread

public class Task implements Runnable {
    @Override
    public void run() {
        System.out.println("Inside Task >> run() - throw RuntimeException");
        throw new RuntimeException();
    }
}


Test Class

public class TestUncaughtExceptionHandler {

    public static void main(String[] args) {
        Thread thread = new Thread(new Task());
        thread.start();
       
        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            public void uncaughtException(Thread myThread, Throwable e) {
                System.out.println(myThread.getName() + " throws exception: " + e);
            }
        });

    }
}


Output
Inside Task >> run() - throw RuntimeException
Thread-0 throws exception: java.lang.RuntimeException
Click here to View more...

Remove Duplicate in ArrayList using Comparator in TreeSet



public class StudentRemoveDuplicates {

    public static void main(String[] args) {
        List list1 = new ArrayList();
        list1.add(new Student("Amit", "100"));
        list1.add(new Student("Anindita", "101"));
        list1.add(new Student("Abcd", "102"));
        list1.add(new Student("Xyz", "103"));

        List list2 = new ArrayList();
        list2.add(new Student("Abcd", "102"));
        list2.add(new Student("Xyz", "104"));

        list1.addAll(list2);

        System.out.println("\n***** Merging 2 lists with duplicates *******\n");
        System.out.println(list1);

        Set setOfStudents = new TreeSet<>(new Comparator() {

            @Override
            public int compare(Student o1, Student o2) {
                if (o1.getId().equalsIgnoreCase(o2.getId())) {
                    return 0;
                }
                return 1;
            }
        });
       
        setOfStudents.addAll(list1);

       
        System.out.println("\n***** After removing duplicates *******\n");
        final ArrayList newList = new ArrayList(setOfStudents);
        System.out.println(newList);
    }
}


 Output

***** Merging 2 lists with duplicates *******

[
Name=Amit   Id=100,
Name=Anindita   Id=101,
Name=Abcd   Id=102,
Name=Xyz   Id=103,
Name=Abcd   Id=102,
Name=Xyz   Id=104]

***** After removing duplicates *******

[
Name=Amit   Id=100,
Name=Anindita   Id=101,
Name=Abcd   Id=102,
Name=Xyz   Id=103,
Name=Xyz   Id=104]

 
Click here to View more...

2 ArrayList - Remove duplicates without using Set


First remove duplicates:
      arrayList1.removeAll(arrayList2);
Then merge two arrayList:
      arrayList1.addAll(arrayList2);
Lastly, sort your arrayList if you wish:
       collections.sort(arrayList1);
 

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ArrayListDuplicates {

    public static void main(String[] args) {
        List list1 = new ArrayList();
        list1.add(1);
        list1.add(2);
        list1.add(3);
        list1.add(4);
       
        List list2 = new ArrayList();
        list2.add(1);
        list2.add(2);
        list2.add(5);
        list2.add(6);
       
        System.out.println("Remove duplicates from list1 by calling removeAll");
        list1.removeAll(list2);
        System.out.println("After removeAll is called " + list1);
       
        System.out.println("Merge the 2 lists");
        list1.addAll(list2);
        System.out.println("After Merge "+ list1);
       
        System.out.println("Sorting");
        Collections.sort(list1);
        System.out.println("Final after Sort "+ list1);
       

    }
 

Output
Remove duplicates from list1 by calling removeAll
After removeAll is called [3, 4]
Merge the 2 lists
After Merge [3, 4, 1, 2, 5, 6]
Sorting
Final after Sort [1, 2, 3, 4, 5, 6]
Click here to View more...