Array vs Linked List
Locality of Reference
ArrayList vs LinkedList
Construct a singleton class in a multi-threaded environment
How to create immutable class
Abstract Class vs Interface
What is the use of volatile keyword
What is Encapsulation in Java
What is abstraction in java oops
Difference between Abstraction and Encapsulation
Why to use Abstract keyword
Polymorphism
Inner Class
Shallow Copy vs Deep Copy
Autoboxing & Unboxing
Generics

Array vs Linked List

Size

Since data can only be stored in contiguous blocks of memory in an array, its size cannot be altered at runtime due to the risk of overwriting other data. However, in a linked list, each node points to the next one such that data can exist at scattered (non-contiguous) addresses; this allows for a dynamic size that can change at runtime.

Memory allocation

For arrays at compile time and at runtime for linked lists. but, a dynamically allocated array also allocates memory at runtime.

Memory efficiency

For the same number of elements, linked lists use more memory as a reference to the next node is also stored along with the data. However, size flexibility in linked lists may make them use less memory overall; this is useful when there is uncertainty about size or there are large variations in the size of data elements; memory equivalent to the upper limit on the size has to be allocated (even if not all of it is being used) while using arrays, whereas linked lists can increase their sizes step-by-step proportionately to the amount of data.

Execution time

Any element in an array can be directly accessed with its index; however in the case of a linked list, all the previous elements must be traversed to reach any element. Also, better cache locality in arrays (due to contiguous memory allocation) can significantly improve performance. As a result, some operations (such as modifying a certain element) are faster in arrays, while some others (such as inserting/deleting an element in the data) are faster in linked lists.

Arrays are contiguous memory blocks, so large chunks of them will be loaded into the cache upon first access. This makes it comparatively quick to access future elements of the array.
Linked lists, on the other hand, aren't necessarily in contiguous blocks of memory, and could lead to more cache misses, which increases the time it takes to access them.

Locality of Reference

  • Refers to a phenomenon in which a computer program tends to access same set of memory locations for a particular time period.
  • In other words, refers to the tendency of the computer program to access instructions whose addresses are near one another.
  • The property of locality of reference is mainly shown by loops and subroutine calls in a program.

There are two ways with which data or instruction is fetched from main memory and get stored in cache memory:

Temporal Locality

  • It means current data or instruction that is being fetched may be needed soon. So we should store that data or instruction in the cache memory so that we can avoid again searching in main memory for the same data.
  • In other words, if some data is referenced, then there is a high probability that it will be referenced again in the near future.

Spatial Locality

  • It means instruction or data near to the current memory location that is being fetched, may be needed soon in the near future.

Cache Performance

  • The performance of the cache is measured in terms of hit ratio.
  • When CPU refers to memory and find the data or instruction within the Cache Memory, it is known as cache hit.
  • If the desired data or instruction is not found in the cache memory and CPU refers to the main memory to find that data or instruction, it is known as a cache miss.

ArrayList vs LinkedList

ArrayListLinkedList
Internally uses a dynamic array to store the elements.Internally uses a doubly linked list to store the elements.
If any element is removed from the array, all the other elements are shifted in memory.Faster than ArrayList because it uses a doubly linked list, so no bit shifting is required in memory.
An ArrayList class can act as a list only because it implements List only.LinkedList class can act as a list and queue both because it implements List and Deque interfaces.
Better for storing and accessing data.Better for manipulating data.
The memory location for the elements of an ArrayList is contiguous.The location for the elements of a linked list is not contiguous.
Generally, when an ArrayList is initialized, a default capacity of 10 is assigned to the ArrayList.There is no case of default capacity in a LinkedList. In LinkedList, an empty list is created when a LinkedList is initialized.

Construct a singleton class in a multi-threaded environment

Singleton design pattern is the:

  • solution proposed to return same instance every time
  • restrict instantiation of a class more than once
  • ensures only one instance is available

How to check whether 2 instances are same or different

  • Always check hash code of the returned instance
  • If it is same, then both instances are same and it is singleton

Eager Instantiation

  • Step 1: private static variable of the INSTANCE of the same class (this is only time instance of this class get created)
  • Step 2: Provide private constructor to restrict instatiation from outside class
  • Step 3: Provide public static getInstance() method returning same INSTANCE every time
public class SingletonWithEagerInitialization {

    // Step 1: private static variable of INSTANCE variable
    private static SingletonWithEagerInitialization INSTANCE = new SingletonWithEagerInitialization();

    // Step 2: private constructor
    private SingletonWithEagerInitialization() { }

    // Step 3: Provide public static getInstance() method
    // returning same INSTANCE same time
    public static SingletonWithEagerInitialization getInstance() {
        return INSTANCE;
    }
}

Issues with above approach:

  • It instantiates and keeps instance ready to be available even before asking to return.

Lazy Instantiation

  • Step 1: Just declare private static variable of the same class (beware don’t instantiate)
  • Step 2: Provide private constructor to restrict instatiation from outside class
  • Step 3: Provide public static getInstance() method and check

    • If INSTANCE variable is null, then only instantiate
    • Otherwise, return already instantiated INSTANCE variable
public class SingletonWithLazyInitialization {

    // Step 1: private static variable of INSTANCE variable
    private static SingletonWithLazyInitialization INSTANCE;

    // Step 2: private constructor
    private SingletonWithLazyInitialization() { }

    // Step 3: Provide public static getInstance() method
    // returning INSTANCE after checking
    public static SingletonWithLazyInitialization getInstance() {

        if(null == INSTANCE){
            INSTANCE = new SingletonWithLazyInitialization();
        }
        return INSTANCE;
    }
}

Singleton design pattern in a multi-threaded environment

Issues with Lazy-Initialization approach:

In the above example for Lazy Initialization, suppose 2 or more threads execute in parallel or concurrent, then there may be a issue with multiple instances being instantiated.

  • Thread-1 got the chance and it is put into execution
  • It finds the INSTANCE to be null and therefore Thread-1 instantiates
  • Concurrently, if any other thread got chance and if it tries to executes, then there may be a possibility of new instance is getting created, although it is 50 % chance
  • Because, new Thread-1 haven’t completed with creation of singleton INSTANCE and another thread at the same time finds singleton INSTANCE to be null and tries to creates another one

To overcome this situation, we need to execute lazy instance creation inside synchronized block.

Solution for lazy initialization

  • In getInstance() method, put both checks inside synchronized block.
  • Also, make INSTANCE variable as volatile. This will help getting latest updated copy every time, as it will read from main memory rather than in its own CPU-cache area.
public class SingletonInMultiThreadedEnv {

    // Step 1: private static variable of INSTANCE variable
    private static volatile SingletonInMultiThreadedEnv INSTANCE;

    // Step 2: private constructor
    private SingletonInMultiThreadedEnv() { }

    // Step 3: Provide public static getInstance() method
    // returning INSTANCE after checking
    public static SingletonInMultiThreadedEnv getInstance() {

        // synchronized block
        synchronized (SingletonInMultiThreadedEnv.class){
          if(null == INSTANCE){
              INSTANCE = new SingletonInMultiThreadedEnv();
          }
          return INSTANCE;
        }
    }
}

This way, we can assure that every time single & same instance is returned

However, if the above example is like this:

public class SingletonInMultiThreadedEnv {

    // Step 1: private static variable of INSTANCE variable
    private static volatile SingletonInMultiThreadedEnv INSTANCE;

    // Step 2: private constructor
    private SingletonInMultiThreadedEnv() { }

    // Step 3: Provide public static getInstance() method
    // returning INSTANCE after checking
    public static SingletonInMultiThreadedEnv getInstance() {

        if(null == INSTANCE){
             synchronized (SingletonInMultiThreadedEnv.class){
                 INSTANCE = new SingletonInMultiThreadedEnv();
             }
             return INSTANCE;
        }
    }
}

If Thread-A found the INSATNCE is null and then entered the synchronized block, however, before the INSTANCE getting instantiated, Thread-B gets execution cycle and starts to run, then it will also find the INSTANCE is null. This means, the INSTANCE is going to be instantiated twice.

Double-checked locking – DCL

There is a performance issue with above program.

  • Assume that ONE instance is created and available for use
  • And with this single & same instance, Thread-A is executing in a multi-threaded environment
  • A new thread (Thread-B) got execution cycle and trying to get singleton instance
  • But Thread-B has to wait till Thread-A releases lock or comes out of synchronized block

Ideally, if singleton instance is created & available for use, no threads needs to wait, it should to get singleton instance and continue with its execution.

public class SingletonWithDCL {

    // Step 1: private static variable of INSTANCE variable
    private static volatile SingletonWithDCL INSTANCE;

    // Step 2: private constructor
    private SingletonWithDCL() { }

    // Step 3: Provide public static getInstance() method
    // returning INSTANCE after checking
    public static SingletonWithDCL getInstance() {

        // double-checking lock
        if(null == INSTANCE){

            // synchronized block
            synchronized (SingletonWithDCL.class) {
                if(null == INSTANCE){
                    INSTANCE = new SingletonWithDCL();
                }
            }
        }
        return INSTANCE;
    }
}

Imlemented by Static Inner Class

class Singleton {

    private Singleton() {}

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

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

Making Singletons With Enum

public enum Singleton {
    INSTANCE;
}

Since enums are inherently serializable, we don't need to implement it with a serializable interface. The reflection problem is also not there. Therefore, it is 100% guaranteed that only one instance of the singleton is present within a JVM.

When serializing an enum, field variables are not getting serialized.

How to create immutable class

  • Make class field final and private.
    final, so that class itself will not be able to change it accidently once initialized.
    private, so that it cannot be accessible outside of the class.
  • Provide public getter method to access the data outside of the class.
  • Provide public setter method, and in setter method, create a new object of the class with modified data without touching the original data.
  • If we want the class not to be inherited like String class, then we can declare the class as final.
class ImmutableClass {
    final private int data;
    
    public ImmutableClass(int data) { this.data = data; }

    public int getData() { return data; }
    
    //Provide setter method to set data but
    //but return new object without touching the old object
    public ImmutableClass setData(int data) {
        return new ImmutableClass(data);
    }
}

Abstract Class vs Interface

What Is an Abstract Class

  • A class that contains an abstract keyword on the declaration is known as an abstract class.
  • It is necessary for an abstract class to have at least one abstract method.
  • It is possible in an abstract class to contain multiple concrete methods.
  • An abstract Class is just a structure or guideline created for other concrete classes.
  • Java does not support multiple inheritances for abstract classes.
  • This is because, from semantic perspective, an abstract class should only have those properties and methods, without which a concrete class can't exist, and one concrete class can take all properties of only one abstract class. For example, a Vehicle cannot exist without an engine.

What is an Interface

  • An interface is a behavioral contract. We define the behavior of a class, which will implement this interface.
  • One class can have a number of different behaviors, that is why Java allows us to “implement” multiple interfaces.
  • Interfaces are meant to provide only behaviors. This is why the Java naming convention suggests using adjectives as the name of an interface.
  • These behaviors should not be mandatory for your class. These behaviors should add more capabilities to your class. For example, a Car will be a Car, even if it cannot move.

What is the use of volatile keyword

  • Compiler does optimization internally and it may cache variables in register or something.
  • The volatile keyword is used to declare a variable, so the variable can be accessed directly from the memory not from intermediary cache.
  • Let’s say there is an int variable that is shared by two threads.
    One is updating the value and other is reading the value.
    If the int variable is not volatile then there may be a chance that other thread won’t get updated value.

What is Encapsulation

Encapsulation in Java is one of the OOP principles, that is the process of hiding complexities in programs.

  • Wrap class fields (data/variables) with public methods. – Using Getter and Setter
  • Hide internal methods of class using private access specifiers etc.

What is Abstraction

Abstraction in Java is just an OOP concept, is "provide necessary information which is only to users and hide unnecessary details".

e.g., consider a login window where only User ID and password is enough for users and we don’t need to give responsibilities to users to connect to database and validate if credentials are correct or not etc.

So, only User ID and password fields to users should be visible and database connection and validation should be hidden or abstracted from users.

Difference between Abstraction and Encapsulation

  • Abstraction: Provide only necessary information to client
  • Encapsulation: Whatever the way we apply to hide information

Why to use Abstract keyword

  • Abstract class: Abstract class is used as a base class in inheritance relationship.
    It can have both non-abstract and abstract methods that must be overridden by sub classes.
  • Abstract method: Abstract keyword is used before un-implemented method in abstract class that will be implemented by sub classes. Sub classes have to implement the abstract methods.

Polymorphism

  • Polymorphism is a concept where one name can have many forms. It can be a method or a constructor.
  • In other words, having multiple methods or constructors with the same name in a class, or in a parent and child class.

overloading and overriding

  • Method overloading, and Constructor overloading are known as compile time polymorphism.
  • Method overriding is known as run time polymorphism.

Compile Time Polymorphism

  • Overloaded methods get resolved at compile time.
  • "get resolved" means compiler understands which method will be called, out of many overloaded methods during code compilation.

Run time Polymorphism

  • Compiler will not be able to decide from which object’s methods will be called until run time.

Inner Class

  • A class within another class is called a nested class or an inner class.

Need for Inner Class

  • It helps in the logical grouping of classes that belong together
    Suppose there is a class that is useful only to a single class, then we can logically embed it in that class and keep the two classes together.
  • It helps to increase the encapsulation
    By nesting class C2 within the class C1, private members of C1 can be accessed by C2.
    Also, we can protect C2 from the outside world.
    Eventually, this will lead to strong encapsulation and security.
  • It helps to increase the readability and maintainability of the code
    Placing inner classes within the top-level classes helps to put the code closer to where it is going to be used.

Types of Inner Classes

  • Nested Inner Class
  • Static Inner Class
  • Method Local Inner Class
  • Anonymous Inner Class

Nested Inner Class

An inner class that can access other instance variables of the outer class, even if they are declared as private.

Method Local Inner class

A class inside a method body that will be of a local type. The scope of the inner class is restricted within the method, similar to the local variables.
We can not declare it as private, protected, static and transient.
We can declare it as abstract and final, but not both at the same time.
Cannot access a local variable from the outer class. To access the local variable from the outer class, we must define it as final.

Static Inner class

Acts as a static member of an outer class.
As it is a static member, we can access it without initializing the outer class with the help of a static method.
Similar to static members, a static nested class cannot access the instance variables and methods of the outer class.

Anonymous Inner class

An inner class that is declared without a name, to make a more concise code.
Generally, they are used when there is a need to override the method of a class or an interface.
We can also use them if we need to use a local class only once.

Shallow Copy vs Deep Copy

Shallow Copy

Create a new object, and all fields of the original objects are copied exactly. But, if it contains any objects as fields, then only references to those objects are copied not the complete objects.

This implies that, both the original and copied object points to the same reference internally and, if you do any changes to the data using the copied object, they are reflected in the original object too.

Deep Copy

Create a new object and then recursively populate it with copies of the child objects found in the original. The objects created through deep copy is not dependent upon the original objects.

  • Copy Constructor
  • Cloneable Interface

    • Make sure to override clone() as public
  • Serialization

    • Ensure that all classes in the object's graph are serializable.
    • Create input and output streams.
    • Use the input and output streams to create object input and object output streams.
    • Pass the object that you want to copy to the object output stream.
    • Read the new object from the object input stream and cast it back to the class of the object you sent.
  • External Libraries

    • Apache Commons Lang comes with SerializationUtils.clone() method for a deep copy of an object.
    • It expects all classes in the hierarchy to implement Serializable interface else SerializableException thrown by the system.

Autoboxing & Unboxing

Autoboxing

  • Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes.
  • The compiler creates a wrapper class object from the corresponding primitive type value.
  • The compiler invokes the method such as intValue() to convert an Integer to int at runtime.

Unboxing

  • Converting an object of a wrapper type to its corresponding primitive value is called unboxing.
  • The Java compiler applies unboxing when an object of a wrapper class is:

    • Passed as a parameter to a method that expects a value of the corresponding primitive type.
    • Assigned to a variable of the corresponding primitive type.

Autoboxing and unboxing lets developers write cleaner code, making it easier to read.

Generics

Why Use Generics

Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods.

Code that uses generics has many benefits over non-generic code:

  • Stronger type checks at compile time
    A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.
  • Elimination of casts
  • Enabling programmers to implement generic algorithms
    By using generics, programmers can implement generic algorithms that work on collections of different types, can be customized, and are type safe and easier to read.