Mostly Asked Java Interview Questions For 2 Yrs Experience

1. What is the difference between JDK, JRE, and JVM?

JDK (Java Development Kit): JDK stands for Java Development Kit, is a software development environment that is used to create Java applications and components. JDK contains various tools, libraries, and utilities that are required for developing, compiling, debugging, and documenting Java programs. It includes Java Runtime Environment (JRE), Java compiler (javac), debugger (jdb), and other utilities.

We can say the JDK has everything that a developer needs to write, compile, and run Java code. When you install the JDK, you also get the JRE.

JRE (Java Runtime Environment): JRE stands for Java Runtime Environment. It provides an execution environment to run the Java applications on the computer. It includes Java Virtual Machine (JVM), class libraries, and other necessary components to execute Java programs. JRE mainly focused on running Java applications and it does not contain any development tools like compilers and debuggers.

When you install the JRE, you can run Java applications on your computer. Also it does not contain compilers, you cannot develop or compile new Java programs. The JRE is intended for end-users who want to run Java applications on their machines without need to write or modify any code.

JVM (Java Virtual Machine): JVM stands for Java Virtual Machine. It is a crucial component of Java that provides an execution environment for Java programs. Whenever we compile our Java code, it converts into bytecode. This newly generated bytecode is platform-independent. The JVM interprets and executes this bytecode on the target machine.

We can say JVM works as an abstraction between the Java code, the hardware and operating system. It handles tasks such as memory management, garbage collection, bytecode verification, and runtime security. The JVM ensures that Java programs can run consistently and predictably across different platforms without requiring recompilation.

2. What are the basic data types in Java?

Eight basic data types are

Primitive Types:

  1. byte: Used to store whole numbers from -128 to 127. It is commonly used when working with raw binary data or when memory usage is crucial.
  2. short: short datatype is used to store whole numbers from -32,768 to 32,767. It is typically used in arrays and mathematical computations that don’t require a wider range.
  3. int: Used to store whole numbers from -2,147,483,648 to 2,147,483,647. It is the most commonly used data type in Java.
  4. long: Used to store larger whole numbers from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. It is appended with an “L” at the end of the value.
  5. float: Used to store decimal numbers with single-precision. It is appended with an “F” at the end of the value.
  6. double: Used to store decimal numbers with double-precision. It is the default choice for decimal values and is commonly used in most cases.
  7. char: Used to store a single character. It can represent any Unicode character and is enclosed in single quotes (‘ ‘).
  8. boolean: Used to store a boolean value, which can be either true or false. It is commonly used for logical conditions and flow control.

Reference Types:

  1. String: Although not a primitive type, the String class is widely used to represent a sequence of characters. It provides various methods for manipulating strings.

3. Difference between a Checked and Unchecked exception?

Differences between checked and unchecked exceptions in Java

Checked Exceptions
Unchecked Exceptions
HandlingMust be explicitly caught or declared using throws keyword.Optional to catch or declare.
Compiler CheckChecked at compile-time.Checked at run-time.
RequirementMust be handled or propagated by the calling code.Not required to be handled or declared.
ExamplesIOException, SQLException, ClassNotFoundExceptionNullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException
Typical Use CasesRecoverable errors or exceptional conditions that should be anticipated and handled.Programming errors or unrecoverable conditions.
Design ConsiderationEnsures explicit handling of potential exceptions.Indicates programming errors or unexpected conditions.

4. Explain the concept of method overloading and method overriding.

Method Overloading:

In Java, Method overloading is a feature that allows methods can have the same name but different parameters in class. Or we can say that it enables you to define several methods with the same name but different input parameter lists within the same class.

Important points about method overloading:

  1. Method overloading is determined by the method’s name and its parameter.
  2. The overloaded methods must have a different number of parameters or different parameter types.
  3. Method overloading is resolved at compile-time based on the arguments passed to the method.
  4. The return type of the method does not play a role in method overloading.
  5. Overloaded methods may have different access modifiers or throw different exceptions.
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
  
    public double add(double a, double b) {
        return a + b;
    }
  
    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

In the above example, the add method is overloaded three times with different parameter lists, allowing it to perform addition for different types and numbers of inputs.

Method Overriding:

In Java, Method overriding allows different classes can have the same method name and parameters. Here classes have parent-and-child relationships or we can also say sub-class and super-class. When a subclass provides a different implementation of a method that is already defined in its superclass. It means we can change the method implementation in the subclass as per the requirement without altering the superclass method. And We Keep the method signature name, return type, and parameters intact.

Important points about method overriding:

  1. The method name, return type, and parameter list of the overridden method must be the same as that of the superclass method.
  2. The access modifier of the overriding method cannot be more restrictive than the access modifier of the superclass method.
  3. The @Override annotation can be used to indicate that a method is intended to override a superclass method (optional but recommended).
  4. Method overriding is resolved at runtime based on the actual type of the object.
class Animal {
    public void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("Cat meows");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = new Dog();
        Cat cat = new Cat();

        animal.sound(); 
        dog.sound();
        cat.sound();
    }
}

5. What is the Difference between Abstract Classes and Interfaces?

Abstract ClassesInterfaces
InheritanceSupports single inheritanceSupports multiple inheritance.
ConstructorCan have constructorsCannot have constructors
FieldsCan have instance variables, constants, and non-constantsCan have only constants (static final) and static variables
Method ImplementationCan have implemented methodsCannot have implemented methods
Abstract MethodsCan have abstract methods (unimplemented)Can have abstract methods (unimplemented)
Method AccessibilityCan have methods with various access modifiersMethods are implicitly public
Default MethodCan have default and static methodsCan have default and static methods since Java 8
ExtensibilityProvides a partial implementation for subclassesAllows implementation by multiple unrelated classes
Interface ImplementationCan implement multiple interfacesClasses can implement multiple interfaces

6. What is the purpose of the ‘final’ keyword in Java?

In Java, the ‘final‘ keyword is used with Variables, methods, and classes to restrict from changing its value and from overriding.

  1. Final Variables:
    • When final keywords are applied to a variable, it ensures that its value cannot be changed once it is assigned.
    • Final variables are considered constants and must be initialized at the time of declaration or in the constructor.
    • The value of a final variable remains constant throughout the program execution.
  2. Final Methods:
    • When a final keyword is applied to a method, that method cannot be overridden by any subclass.
    • It prevents subclasses from modifying the behavior of the final method defined in the superclass.
    • Final methods are commonly used in class design when a specific implementation of a method should not be altered by subclasses.
  3. Final Classes:
    • When a final keyword is applied to a class, that class cannot be extended by another class.
    • Final classes are typically used when a class’s design is complete and should not be modified or extended further.
    • Examples of final classes in Java are wrapper classes. For example <strong>java.lang.String</strong> and utility classes like <strong>java.util.Collections</strong>.

Benefits of the ‘final’ keyword

  • Security: By making a variable immutable (final), its value cannot be changed, reducing the risk of unintended modifications.
  • Optimization: The ‘final’ keyword allows the compiler to optimize code by making assumptions about the finality of variables or methods.

7. Explain the concept of Multithreading in Java.

Multithreading in Java allows concurrent execution of multiple threads within a single program. A thread is a lightweight unit of execution or can say that a process that runs independently and concurrently. It helps to improve the performance, responsiveness, and resource utilization with the help of the Program. Java provides built-in support for multithreading through the <strong>java.lang.Thread</strong> class and related APIs.

8. How does garbage collection work in Java?

Garbage collection in Java is an automatic memory management process. It helps to free up memory occupied by objects that are no longer in use. Instead of manually deallocating memory by developers, it is done automatically. It also helps to prevent memory leaks and other memory-related issues.

The Java Virtual Machine (JVM) is responsible for managing the garbage collection process. The JVM includes different garbage collection algorithms, such as the Serial, Parallel, Concurrent, and G1 garbage collectors, which employ various strategies for optimizing the garbage collection process based on factors like throughput, pause times, and memory footprint.

The frequency and effectiveness of garbage collection depend on factors like available memory, object allocation rate, and the garbage collector algorithm in use. Garbage collection is an essential part of Java’s memory management system, ensuring efficient memory utilization and relieving developers from manual memory deallocation tasks.

Example to demonstrate work of garbage collection in Java

public class GarbageCollectionExample {
    public static void main(String[] args) {
        // Creating objects
        Person person1 = new Person("John");
        Person person2 = new Person("Jane");

        // Setting person2 to null
        person2 = null;

        // Reassigning person1 to a new object
        person1 = new Person("Mike");

        // Requesting garbage collection
        System.gc();
    }
}

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
        System.out.println("Person created: " + name);
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Person finalized: " + name);
    }
}

In the above example, we have a Person class representing a person’s name. And In the main method, we created two Person objects (person1 and person2) with different names.

Next, we set person2 to null, meaning it no longer references the object it previously pointed to. This makes the object eligible for garbage collection.

Then, we reassign person1 to a new Person object with the name “Mike”. This means the previous Person object created for “John” is no longer referenced.

Finally, we explicitly request garbage collection using System.gc().

When the garbage collector runs, it identifies that the Person object initially created for “John” is no longer accessible from any active references in the program. Therefore, it determines that the object is eligible for garbage collection.

The garbage collector calls the <strong>finalize()</strong> method of the object before freeing its memory. In this case, the <strong>finalize()</strong> method prints a message indicating the name of the person being finalized.

The output of the program may vary depending on the JVM and garbage collector implementation. However, you should see something like:

Person created: John
Person created: Jane
Person created: Mike
Person finalized: Jane

This demonstrates that the Person object created for “John” is garbage collected and its memory is reclaimed by the garbage collector after calling the <strong>finalize()</strong> method.

9. What is the difference between String, StringBuilder, and StringBuffer?

String:

  • String objects are immutable in Java which means their values cannot be changed once they are created.
  • Any operation that appears to modify an String actually creates a new String object.
  • However, frequent string manipulations can lead to excessive memory usage and performance overhead due to object creation.
  • Immutable strings ensure safety and make them suitable for scenarios where the value should not be modified.
public class StringExample {
    public static void main(String[] args) {
        String str1 = "Hello";
        String str2 = str1.concat(" World");
        
        System.out.println(str1); // Output: Hello
        System.out.println(str2); // Output: Hello World
    }
}

StringBuilder:

  • StringBuilder is a mutable class introduced in Java 5 to efficiently construct strings by appending, inserting, or modifying characters.
  • Unlike String, StringBuilder objects can be modified in-place without creating new objects.
  • StringBuilder is not thread-safe, meaning it should not be shared among multiple threads concurrently without external synchronization.
  • It provides methods like append(), insert(), delete(), and replace() for efficient string manipulation.
  • StringBuilder is typically used when a mutable sequence of characters is required and thread safety is not a concern.
public class StringBuilderExample {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");
        sb.append(" World");
        
        System.out.println(sb); // Output: Hello World
    }
}

StringBuffer:

  • StringBuffer is similar to StringBuilder in functionality, but it predates it and has been present since the early versions of Java.
  • Like StringBuilder, StringBuffer allows for mutable string manipulation.
  • However, StringBuffer is thread-safe, meaning it provides synchronized methods to ensure safe concurrent access by multiple threads.
  • The thread safety of StringBuffer comes at the cost of some performance overhead compared to StringBuilder.
  • StringBuffer is commonly used in multi-threaded environments or situations where thread safety is a requirement.
public class StringBufferExample {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("Hello");
        sb.append(" World");
        
        System.out.println(sb); // Output: Hello World
    }
}

10. Difference between a Static and non-static variable.

Static VariableNon-Static Variable
DeclarationDeclared using the static keywordDeclared without the static keyword
Memory AllocationSingle copy shared by all instancesSeparate copy for each instance
InitializationInitialized once, usually at class loadingInitialized separately for each instance
AccessAccessed using the class nameAccessed through an object instance
Object DependencyNot dependent on object instancesDependent on object instances
ScopeAvailable throughout the classAvailable within the specific instance
VisibilityCan be public, protected, private, or package-privateCan be public, protected, private, or package-private

11. Difference between a static and non-static method.

Static MethodNon-Static Method
DeclarationDeclared using the static keywordDeclared without the static keyword
AccessAccessed using the class nameAccessed through an object instance
Object DependencyNot dependent on object instancesDependent on object instances
VisibilityCan have any visibility (public, protected, private, or package-private)Can have any visibility (public, protected, private, or package-private)
Memory AllocationNo memory allocation for this referenceMemory allocation for this reference
Access to VariablesCan only directly access static variables and methodsCan access both static and non-static variables and methods
Method OverridingCannot be overriddenCan be overridden in subclasses
PolymorphismDoes not participate in polymorphismParticipates in polymorphism

12. What are the access modifiers in Java?

Access modifier in Java with examples

1). Public: The public access modifier allows access to the class, method, or variable from anywhere with any restriction. Here Anywhere mean including other classes, packages, and even different projects we can access the public class, method, and variable

Here’s an example:

public class Main{
    public int publicVariable = 10;

    public void publicMethod() {
        System.out.println("This is a public method.");
    }
}

In the above example, the publicVariable and publicMethod() can be accessed from any other class.

2). Private: The private access modifier does not allow to access class, method, and variable from anywhere. Here the restriction is private access modifier can accessible only within the same class. It means that private methods or variables cannot be accessed or modified directly by other classes, including subclasses.

Here’s an example:

public class Main{
    private int privateVariable = 20;

    private void privateMethod() {
        System.out.println("This is a private method.");
    }
}

In this example, the privateVariable and privateMethod() can only be accessed within the Main class itself.

3). Protected: The protected access modifier allows access within the same class, same package, or subclasses. Protected members are not accessible from unrelated classes in different packages.

Here’s an example:

public class Main{
    protected int protectedVariable = 30;

    protected void protectedMethod() {
        System.out.println("This is a protected method.");
    }
}

In this example, the protectedVariable and protectedMethod() can be accessed within the Main class, subclasses of Main class, and other classes in the same package.

4). Default (No Modifier): If no access modifier is specified, it is considered as the default access modifier. The default access allows access within the same package but not from unrelated classes in different packages.

Here’s an example:

class Main{
    int defaultVariable = 40;

    void defaultMethod() {
        System.out.println("This is a default method.");
    }
}

In this example, the defaultVariable and defaultMethod() can be accessed within the Main and other classes in the same package.

Remember, access modifiers help control the visibility and accessibility of classes, methods, and variables, ensuring proper encapsulation and data hiding in Java.

13. What is the purpose of the ‘transient’ keyword in Java?

In Java, the transient keyword is used to indicate that a variable should not be serialized when an object of a class is converted into a byte stream. We can also say that transient keyword is used to exclude specific variables from the serialization process. Basically, Serialization is performed for the purpose of storing or transmitting the object. The transient keyword is applicable only to variables and not to methods or classes.

Reasons for using the transient keyword may vary, but some common scenarios include:

  1. Security: You might have sensitive data in a class that should not be persisted or transmitted. By marking such variables as transient, you can prevent their values from being stored or transmitted.
  2. Derived or Temporary Data: Variables that are derived from other data or have temporary values might not need to be serialized. By marking them as transient, you can exclude them from the serialization process, reducing the size of the serialized object.
  3. Performance: Excluding certain variables from serialization can improve the performance of serialization and deserialization operations, especially if the excluded variables are large or time-consuming to serialize.
import java.io.Serializable;

public class Main implements Serializable {
    private transient int transientVariable;
    private int nonTransientVariable;

    // ...
}

In this example, the transientVariable is marked as transient, while the nonTransientVariable is not. When an instance of Main class is serialized, the value of transientVariable will not be included in the serialized form.

It’s important to note that when an object is deserialized, transient variables will be assigned default values (e.g., 0 for numeric types, null for objects) since their values were not stored during serialization.

14. How does Java support multiple inheritance?

Basically, Java does not supports multiple inheritances of the classes means a class can extend only one class, not multiple classes.

But a class can implement multiple interfaces. This means that a Java class can implement multiple interfaces, but it can only extend a single class.

This design has been made by the creators of Java to avoid certain complications and ambiguities that can arise from multiple inheritance.

interface InterfaceA {
    void methodA();
}

interface InterfaceB {
    void methodB();
}

class MyClass implements InterfaceA, InterfaceB {
    public void methodA() {
        System.out.println("Implementing methodA");
    }

    public void methodB() {
        System.out.println("Implementing methodB");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass myObj = new MyClass();
        myObj.methodA(); 
        myObj.methodB(); 
    }
}

Output

Implementing methodA
Implementing methodB

In this example, the MyClass implements both InterfaceA and InterfaceB. As a result, it must provide implementations for the methodA() and methodB() defined in both interfaces.

By supporting multiple inheritance of interfaces, Java allows classes to exhibit behavior from multiple sources without the complications and ambiguities associated with multiple inheritance of classes.

15. What is the purpose of the ‘volatile’ keyword in Java?

Will Cover soon

16. Explain the concept of exception handling in Java.

An exception is a situation in Java Program execution when the program terminates abnormally when some error occurs.

And to handle such types of exceptions and abnormal termination of the program, we have Exception handling. It is a mechanism in Java that allows you to handle and manage exceptional situations, also known as exceptions.

The basic idea behind exception handling is to separate the normal program flow from the error-handling code. When an exceptional condition occurs, an exception object is created and “thrown” by the code that encounters the error. The exception is then “caught” and handled by appropriate code elsewhere in the program.

Example of exception handling in Java

public class Main {
    public static void main(String[] args) {
        try {
            int result = 10/0;
            System.out.println("Result: " + result);
        } catch (ArithmeticException ex) {
            System.out.println("An arithmetic exception occurred: " + ex.getMessage());
        } finally {
            System.out.println("Finally block executed.");
        }
    }
}

Output

An arithmetic exception occurred: / by zero
Finally block executed.

In the above example, we have divided two numbers. As we know that we can’t divide any number by zero, so our above example will throw an exception called ArithmeticException. The exception is caught by the catch block, which prints an error message. The finally block is then executed, regardless of whether an exception occurred or not.

17. What are the different types of inner classes in Java?

There are four types of inner classes in Java. It is also known as nested classes.

1). Member Inner Class:

A member inner class is also known as a regular inner class. It is a non-static class that is defined within a class and can access all members (fields and methods) of the outer class. This inner class can access the private members of the Outer class. An instance of the member inner class can only be created within an instance of the Outer class.

For Example:

public class OuterClass {
    private int outerVariable;
    public class InnerClass {
        public void innerMethod() {
            outerVariable = 20; // Accessing outer class variable
        }
    }
}

2). Local Inner Class:

A local inner class is defined within a method or a block of code of an outer class. It has access to the variables and parameters of the enclosing method or block, but only if they are declared as final or effectively final. Local inner classes are not accessible outside the method or block in which they are defined.

For Example:

public class OuterClass {
    private int outerVariable = 30;
    public void outerMethod() {
        class LocalInnerClass {
            public void innerMethod() {
                System.out.println("Outer variable: " + outerVariable); // Accessing outer class variable
            }
        }
        LocalInnerClass inner = new LocalInnerClass();
        inner.innerMethod();
    }
}

3). Static Nested Class:

It is a static class that is defined at the member level of another class. It does not have access to the instance members of the enclosing class, but it can access static members. It can be instantiated without an instance of the outer class.

For Example

public class OuterClass {
    private static int outerStaticVariable;
    public static class NestedClass {
        public void nestedMethod() {
            outerStaticVariable = 20; // Accessing outer class static variable
        }
    }
}

4). Anonymous Inner Class:

An anonymous inner class is a class that does not have a name and is declared and instantiated at the same time or we can say in a single expression. It is typically used when you need to define a class that implements an interface or extends a class for a specific, limited purpose. Anonymous inner classes are often used in event handling and threading. Also, it is defined inline and is typically used for one-time use cases.

For Example

public class OuterClass {
    public void outerMethod() {
        Runnable runnable = new Runnable() {
            public void run() {
                System.out.println("Anonymous inner class");
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

18. How does Java handle runtime polymorphism?

In Java, we can achieve runtime polymorphism through a mechanism called method overriding. Method overriding allows a subclass to provide its own implementation of a method defined in its superclass. Based on the type of the object whether it is of subclass or superclass, the specific implementation of the method to be executed is determined at runtime.

Here’s how Java handles runtime polymorphism:

  1. Inheritance: Polymorphism in Java is achieved through inheritance. A subclass inherits the methods and fields of its superclass. If a subclass defines a method with the same signature as a method in its superclass, it is said to override the superclass method.
  2. Method Overriding: Method overriding allows a subclass to provide its own implementation of a method that is already defined in its superclass. To override a method, the subclass must declare a method with the same name, return type, and parameters as the superclass method.
  3. Dynamic Method Dispatch: The dynamic method dispatch is the mechanism by which the appropriate implementation of an overridden method is selected at runtime. When a method is invoked on an object, the JVM determines the actual type of the object at runtime and selects the appropriate implementation of the method based on that type.

Example to explain runtime polymorphism

class Animal {
    public void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Cat extends Animal {
    public void sound() {
        System.out.println("Cat meows");
    }
}

class Dog extends Animal {
    public void sound() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Cat();
        Animal animal2 = new Dog();

        animal1.sound(); // Output: Cat meows
        animal2.sound(); // Output: Dog barks
    }
}

Output

Cat meows
Dog barks

19. What is the purpose of the ‘super’ keyword in Java?

In Java, the super keyword is used to refer to the superclass of a subclass. It is used to access and invoke the members of the superclass which is methods, fields, and constructors within the subclass. The super keyword is particularly useful when there is a need to differentiate between the members of the subclass and the superclass that have the same name.

For Example

class Vehicle {
    int speed = 130;
}
 
class Car extends Vehicle {
    int speed = 200;
    void display()
    {
        System.out.println("Speed: "
                           + super.speed);
    }
}
 
class Main {
    public static void main(String[] args)
    {
        Car obj = new Car();
        obj.display();
    }
}

20. Explain the concept of serialization and deserialization in Java.

Serialization is the process of converting an object into a byte stream, which can be stored in a file, sent over a network, or saved in a database. Deserialization, on the other hand, is the process of recreating the object from the serialized byte stream. Serialization allows objects to be persisted or transmitted, while deserialization allows them to be restored to their original state.

21. What is the difference between an ArrayList and a LinkedList?

ArrayList LinkedList
Data StructureDynamic arrayDoubly linked list
Insertion/Deletion (at the beginning)Slower due to shifting all subsequent elementsFast, as it requires updating only the previous and next pointers
Insertion/Deletion (in the middle or end)Slower due to shifting elements after the insertion/deletion pointFast, as it requires updating only the previous and next pointers
Random AccessFast, O(1) time complexitySlower, O(n) time complexity since it needs to traverse the list from the beginning to reach the desired index
Memory OverheadLower memory overhead since it stores elements in a contiguous block of memoryHigher memory overhead due to additional memory required for maintaining links between elements
IterationFast iteration using indexed accessSlower iteration as it requires following the links from one element to another
PerformanceBetter suited for scenarios that involve frequent random access, such as accessing elements by indexBetter suited for scenarios that involve frequent insertion/deletion in the middle or end of the list
UsageSuitable when the size of the list is known in advance or when random access is more commonSuitable when frequent insertion/deletion or traversal is required, and random access is less common

22. How are HashMap and HashTable different?

HashMapHashTable
Thread SafetyNot thread-safeThread-safe
Null Keys/ValuesAllows null keys and valuesDoesn’t allow null keys or values
InheritanceInherits from AbstractMap classInherits from Dictionary class
PerformanceGenerally faster and more efficientSlower and less efficient due to synchronization
Iterator Fail-FastYesYes
Iteration OrderNo guarantee of iteration order (unordered)No guarantee of iteration order (unordered)
LegacyIntroduced in Java 1.2Introduced in Java 1.0
ImplementationHash-based using an array of buckets and hash codesHash-based using an array of buckets and hash codes

23. Explain the Concept of autoboxing and unboxing in Java.

Autoboxing and unboxing are automatic conversions between primitive types and their corresponding wrapper classes(e.g., int to Integer, double to Double) in Java.

Autoboxing:

Autoboxing is the process of automatically converting a primitive type to its corresponding wrapper class object. It allows you to use primitive types as if they were objects. Autoboxing is performed implicitly by the Java compiler.

For example

int number = 10;        // primitive int
Integer wrapper = number;  // autoboxing: int to Integer

In the above example, the primitive int value number is automatically converted to an Integer object through autoboxing. The assignment Integer wrapper = number; is possible because the Java compiler automatically converts the int value to an Integer object.

Unboxing:

Unboxing is the process of automatically converting a wrapper class object to its corresponding primitive type. It allows you to extract the value stored in the wrapper object as a primitive type. Unboxing is also performed implicitly by the Java compiler.

For example

Integer wrapper = 20;     // Integer object
int number = wrapper;     // unboxing: Integer to int

In the example, the Integer object wrapper is automatically converted to a primitive int through unboxing. The assignment int number = wrapper; is possible because the Java compiler automatically extracts the value from the Integer object and assigns it to the int variable.

24. What is the purpose of the ‘synchronized’ keyword in Java?

In Java, the synchronized keyword is used to achieve synchronization and mutual exclusion in multi-threaded environments. It means one thread can access a block of code or an object’s method at a time. It helps to control the accessibility of shared resources or critical sections of code to ensure only one thread can access/execute it at a time.

The synchronized keyword can be applied to methods and code blocks.

For Example

1). Synchronized Instance Methods

With the help of Synchronized keywords, we can declare a method Synchronized. When a method is declared as Synchronized, it acquires an intrinsic lock, also known as a monitor lock, on the object instance. This lock ensures that only one thread can execute the synchronized method at a time for a particular instance of the class.

public synchronized void synchronizedMethod() {
    // Synchronized method code
}

2). Synchronized Blocks

A block can be synchronized with the help of a synchronized keyword. Synchronized blocks can be executed by one thread at a time. Here before access, the block, thread will check for the lock. If not available, the thread will wait until the lock is released.

Object lock = new Object();
// ...
synchronized (lock) {
    // Synchronized block code
}

25. How does Java support generics?

In Java, Generics allows you to create classes, interfaces, and methods that can work with different data types with compile-time type safety. The main benefits of using generics in Java are type safety and Code Reusability.

Java generics are implemented with the help parameters, which are specified within angle brackets (<>).

Below is an example of a generic:

class Pair<T, U> {
    private T first;
    private U second;

    public Pair(T first, U second) {
        this.first = first;
        this.second = second;
    }

    public T getFirst() {
        return first;
    }

    public U getSecond() {
        return second;
    }

    public void setFirst(T first) {
        this.first = first;
    }

    public void setSecond(U second) {
        this.second = second;
    }
}

class Main{
    public static void main(String[] args) {
        Pair<String, Integer> pair = new Pair<>("John", 25);
        String name = pair.getFirst();
        Integer age = pair.getSecond();

        System.out.println("Name: " + name);
        System.out.println("Age: " + age);
    }
}

Output

Name: John
Age: 25

In the above example, we define a Pair class with two type parameters T and U. The class represents a simple pair of values, where the first value is of type T and the second value is of type U. The class has a constructor to initialize the pair and getter/setter methods to access and modify the values.

There is also a Main class where we create an instance of Pair with String as the first type parameter and Integer as the second type parameter. We pass “John” and 25 as arguments to the constructor to create a pair representing a person’s name and age. We then retrieve the values using the getter methods and print them to the console.

Using generics allows us to create a flexible Pair class that can hold different types of values. By specifying the types at compile-time, we get type safety and avoid the need for explicit type casting.

26. What is the difference between a Stack and a Queue?

StackQueue
OrderFollows the Last-In-First-Out (LIFO) orderFollows the First-In-First-Out (FIFO) order
OperationsSupports two main operations: push and popSupports two main operations: enqueue and dequeue
InsertionInsertion happens at the top of the stackInsertion happens at the rear/end of the queue
RemovalRemoval happens from the top of the stackRemoval happens from the front of the queue
Example Use CasesFunction call stack, undo/redo functionalityTask scheduling, message queues, breadth-first search

27. Explain the Concept of Anonymous classes in Java.

In Java, an anonymous class is a class that is defined without a name in a single expression. Generally, we use Anonymous classes when we need to create a class that is simple, one-time use, and you don’t want to define a separate class for it. They are particularly useful when working with event listeners, callbacks, or providing implementations for abstract methods.

Syntax to create an anonymous class

interface SomeInterface {
    void doSomething();
}

SomeInterface obj = new SomeInterface() {
    @Override
    public void doSomething() {
        // Implementation
    }
};

28. How can you handle file I/O operations in Java?

In Java, java.io package provides the various classes and methods to handle the I/O operations.

1). Import the necessary classes:

Import the required classes from the java.io package in your class. Some commonly used classes for file I/O operations are File, FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, etc. You can import them individually or using wildcard import statement (import java.io.*;) to import all classes from the package.

2). Create a File object:

After importing the required classes we need to create file to perform file operations like read from or write to. You can create a File object by providing the file’s path and name.

File file = new File("path/to/file.txt");

3). Reading from a file:

a). To read text from a file, you can use a combination of FileReader, BufferedReader, and File objects. Below is an example:

try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // Process the line of text
    }
} catch (IOException e) {
    e.printStackTrace();
}

b). To read binary data from a file, you can use a combination of FileInputStream and File objects. Below is an example:

try (FileInputStream fis = new FileInputStream(file)) {
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = fis.read(buffer)) != -1) {
        // Process the bytes
    }
} catch (IOException e) {
    e.printStackTrace();
}

4). Writing to a file:

a). To write text into a file, you can use a combination of FileWriter, BufferedWriter, and File objects. Below is an example:

try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
    writer.write("Hello, World!");
    writer.newLine(); // Write a new line
    // Write more text
} catch (IOException e) {
    e.printStackTrace();
}

b). To Write binary data to a file, you can use a combination of FileOutputStream and File objects. Below is an example:

try (FileOutputStream fos = new FileOutputStream(file)) {
    byte[] data = { 0x48, 0x65, 0x6C, 0x6C, 0x6F }; // Example data
    fos.write(data);
    // Write more bytes
} catch (IOException e) {
    e.printStackTrace();
}

While working with the file, it is necessary to handle exceptions appropriately. You can do exception handling using try-catch blocks or throwing exceptions. Also, it’s good practice to close the file-related objects using the close() the method at the end.

29. What is the purpose of the ‘this’ keyword in Java?

In Java, the this keyword refers to the current instance of a class. We can also say that this keyword is a reference to the current object within an instance method or constructor.

Here some uses of the this keyword:

1). Distinguishing instance variables from local variables

When the name of a local variable of a method or a constructor is the same as the name of an instance variable, the this keyword can be used to differentiate between the two. It allows you to refer explicitly to the instance variable.

For Example:

public class Main{
    private int value;
    public void setValue(int value) {
        this.value = value; // 'this' refers to the instance variable
    }
}

2. Passing the current object as an argument:

The this keyword can be used to pass the current object as a parameter to other methods or constructors.

For Example:

public class Main{
    private int value;

    public void setValue(int value) {
        // Pass the current object as an argument to another method
        someMethod(this);
    }

    public Main() {
        this(0); // Invoke the parameterized constructor of the same class
    }

    public Main(int value) {
        this.value = value;
    }
}

3. Returning the current object

The this keyword can be used to return the current object from a method.

public class Main {
    private int value;

    public Main setValue(int value) {
        this.value = value;
        return this; // Return the current object
    }

    // Other methods...

}

// Method chaining
Main obj = new Main().setValue(10);

4. Passing the current object to constructors of inner classes:

When working with inner classes, the this keyword can be used to pass the reference of the current object to the constructor of the inner class.

public class OuterClass {
    private int value;

    public OuterClass(int value) {
        this.value = value;
    }

    public class InnerClass {
        public InnerClass() {
            // Pass the reference of the outer object to the inner class
            OuterClass.this.someMethod();
        }
    }
}

30. Explain the concept of method references in Java 8.

In Java 8, method reference is a way that refers to methods or constructors without invoking them. It allows to pass a method or constructor as a reference, rather than writing a lambda expression that replicates its functionality.

There are several types of method references:

1). Reference to a static method:

You can refer to a static method using the syntax ClassName::staticMethodName. It is similar to calling the method directly.

Function<String, Integer> parser = Integer::parseInt;
Integer result = parser.apply("20");

2). Reference to an instance method of a particular object:

You can refer to an instance method of a specific object using the syntax object::instanceMethodName. The method reference will execute the method on that object.

String str = "Hello, World!";
Function<String, Integer> lengthGetter = str::length;
Integer length = lengthGetter.apply(str);

3). Reference to an instance method of an arbitrary object of a particular type:

You can refer to an instance method of any object of a particular type using the syntax ClassName::instanceMethodName. It is useful when working with functional interfaces that take an object of a specific type as an argument.

import java.util.*;
public class Main
{
	public static void main(String[] args) {
	    List<String> strings = Arrays.asList( "Banana", "cherry","apple");
        strings.sort(String::compareToIgnoreCase);
		System.out.println(strings);
	}
}

31. How does Java handle handling ConcurrentModificationException?

In Java, ConcurrentModificationException occurs when we work with collections such as ArrayList, HashSet, or HashMap, and try to modify (e.g., adding or removing elements) while iterating over it using an iterator.

Various ways to handle this exception

1). Avoiding ConcurrentModificationException

Use appropriate methods provided by the collection to add or remove elements. For example, use List.add() to add elements to collections. Use List.remove() to remove elements, or use an Iterator with its remove() method.

2). Synchronized collections:

If you need to perform concurrent modifications on a collection while iterating, you can use synchronized collections from the java.util.concurrent package, such as ConcurrentHashMap or CopyOnWriteArrayList. These collections are designed to handle concurrent modifications gracefully, and you can modify them without causing a ConcurrentModificationException while iterating.

32. What are the different types of design patterns in Java?

In Java, there are several categories of design patterns. And each category has different types of design patterns. These are given below

1). Creational Patterns: Creational patterns focus on providing flexible and reusable ways to create objects. Examples are:

  • Factory Method Pattern
  • Abstract Factory Pattern
  • Singleton Pattern
  • Builder Pattern
  • Prototype Pattern

2). Structural Patterns: These patterns deal with the composition of classes and objects to form larger structures while keeping them flexible and efficient. Examples are:

  • Adapter Pattern
  • Composite Pattern
  • Proxy Pattern
  • Decorator Pattern
  • Bridge Pattern
  • Flyweight Pattern
  • Facade Pattern

3). Behavioral Patterns: These patterns are concerned with the interaction and communication between objects, focusing on the patterns of communication and responsibilities. Examples are:

  • Observer Pattern
  • Strategy Pattern
  • Command Pattern
  • Iterator Pattern
  • Template Method Pattern
  • Visitor Pattern
  • Chain of Responsibility Pattern
  • State Pattern
  • Mediator Pattern
  • Memento Pattern

4). Concurrency Patterns: These patterns deal with multi-threaded and concurrent programming, providing solutions for synchronization, communication, and coordination between threads. Examples are:

  • Producer-Consumer Pattern
  • Reader-Writer Pattern
  • Thread Pool Pattern
  • Immutable Pattern

5). Architectural Patterns: These patterns address the overall architecture and structure of the software system, focusing on high-level components and interactions. Examples are:

  • Model-View-Controller (MVC) Pattern
  • Model-View-ViewModel (MVVM) Pattern
  • Layered Architecture Pattern
  • Microservices Architecture Pattern
  • Event-Driven Architecture Pattern

33. How can you create and use custom annotations in Java?

In Java, custom annotations can be created using @interface keyword.

Here’s how you can create and use custom annotations in Java:

Custom annotation created with the @interface keyword. Annotations can have elements that represent the values associated with the annotation. These elements can be primitive types, String, Class, enums, arrays, or other annotations.

// Custom annotation with one element
public @interface CustomAnnotation {
    String value();
}

34. What is the purpose of the ‘equals’ and ‘hashCode’ methods in Java?

In Java, the equals() and hashCode() methods work on objects. equals() method is used to compare the object and hashCode() method is used to hashing objects.

1). equals() method

The equals() method is used to compare two objects for equality.

public class Person {
    private String name;
    private int age;

    // Constructor and other methods

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        Person otherPerson = (Person) obj;
        return age == otherPerson.age && Objects.equals(name, otherPerson.name);
    }
}

In this example, the equals() method is overridden in the Person class to compare two Person objects for equality based on their name and age fields. It first checks if the references are the same (this == obj), then checks if the object is null or of a different class (obj == null || getClass() != obj.getClass()). Finally, it compares the age field using == and the name field using Objects.equals().

2). hashCode() method example

The hashCode() method is used to turn an object into a hashcode. Hashcode is an integer value that represents the hash code of an object.

public class Person {
    private String name;
    private int age;

    // Constructor and other methods

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

In this example, the hashCode() method is overridden in the Person class to generate a hash code based on the name and age fields. It uses the Objects.hash() method, which internally generates a hash code by combining the hash codes of the specified fields using an efficient algorithm.

By implementing the equals() and hashCode() methods correctly, you ensure that objects of the Person class can be properly compared for equality using equals() and efficiently stored and retrieved from hash-based data structures using hashCode()

35. How does Java handle memory management?

In Java, memory management is handled automatically through a process called “garbage collection”. The Java Virtual Machine (JVM) takes care of allocating and deallocating memory for objects, relieving the developer from manual memory management tasks.

Here’s an overview of how Java handles memory management:

  1. Object Allocation: When you create an object using the new keyword, memory is allocated on the heap to store the object’s data and its instance variables.
  2. Automatic Garbage Collection: Java has this feature garbage collector. This Garbage collector automatically identifies the memory that is no longer in use and reclaims that memory from objects. This runs in the background and frees up memory by reclaiming objects that are no longer referenced.
  3. Mark and Sweep Algorithm: The garbage collector uses the Mark and Sweep algorithm to determine which objects are still reachable and which are garbage. The process involves marking all objects that are reachable from the root of the object graph (such as local variables, static variables, and method call stacks) and then sweeping through the heap to deallocate memory for unmarked (garbage) objects.
  4. Finalization: Before an object is reclaimed, the garbage collector allows it to run its finalize() method (if overridden). This method can be used for performing any necessary cleanup actions before the object is garbage collected. However, the use of finalize() is discouraged as it can introduce performance issues and is less predictable than explicit resource cleanup.

By using automatic memory management, Java helps developers avoid common memory-related issues like memory leaks, dangling pointers, and accessing deallocated memory. It simplifies the development process and enhances the stability and reliability of Java applications.

36. Explain the concept of immutable objects in Java.

Java has an immutable concept which means an object that is created immutable, state cannot be changed after it is created. Once an immutable object is created, its internal state remains constant throughout its lifetime. If we try to change or modify it, it will create a new object with the updated state, rather than modifying the existing object.

Examples of immutable objects in Java

  • String: The String class in Java is immutable. Once a string is created, its value cannot be changed.
  • java.lang.Integer: The Integer class represents an immutable integer value.
  • java.time.LocalDate: The LocalDate class from the Java 8 Date/Time API represents a date and is immutable.

By designing objects as immutable, you can ensure their state remains consistent, simplify concurrent programming, and improve code reliability. Immutable objects are widely used in functional programming, caching, and situations where shared access to objects is required without the risk of modification.

37. What is the difference between a shallow copy and a deep copy?

1). Shallow Copy

A shallow copy creates a new object that references the same memory locations as the original object. In other words, the copied object contains references to the same child objects as the original object, rather than creating new copies of those child objects. Therefore, changes made to the child objects through one reference will be reflected in both the original and copied objects.

class Person {
    private String name;
    private Address address;
    public Person shallowCopy() {
        Person copy = new Person();
        copy.setName(this.name);
        copy.setAddress(this.address);
        return copy;
    }
    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }
    public Person() {}
    public String getName() {
        return name;
    }
    public void setName(String s) {
        name = s;
    }
    public Address getAddress() {
        return address;
    }
    public void setAddress(Address s) {
        address = s;
    }
}

class Address {
    private String city;
    public Address(String city) {
        this.city = city;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String s) {
        city = s;
    }
}

public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("John", new Address("New York"));
        Person person2 = person1.shallowCopy();
        person1.getAddress().setCity("Chicago");
        System.out.println(person1.getAddress().getCity());
        System.out.println(person2.getAddress().getCity());
    }
}

In the example above, the shallowCopy() method creates a new Person object with a shallow copy. Both person1 and person2 refer to the same Address object. Modifying the Address object through one reference affects both objects.

2). Deep Copy: A deep copy creates a new object and recursively copies the entire object tree, including all child objects. It creates separate copies of all referenced objects, ensuring that changes made to the copied object or its child objects do not affect the original object.

class Person {
    private String name;
    private Address address;
    public Person deepCopy() {
        Person copy = new Person();
        copy.setName(this.name);
        copy.setAddress(new Address(this.address.getCity()));
        return copy;
    }
    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public Person() {}

    public String getName() {
        return name;
    }
    public void setName(String s) {
        name = s;
    }

    public Address getAddress() {
        return address;
    }
    public void setAddress(Address s) {
        address = s;
    }
}

class Address {
    private String city;
    public Address(String city) {
        this.city = city;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }
}

public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("John", new Address("New York"));
        Person person2 = person1.deepCopy();
        person1.getAddress().setCity("Chicago");
        System.out.println(person1.getAddress().getCity());
        System.out.println(person2.getAddress().getCity());
    }
}

In this example, the deepCopy() method creates a new Person object with a deep copy. The Address object is copied separately, ensuring that modifying the Address object through one reference does not affect the other.

38. How can you handle JSON data in Java?

In Java, We have a popular library Jackson to work with JSON. Jackson provides JSON parsing and manipulation capabilities.

To use Jackson we need below dependency

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.4</version>
</dependency>

39. What is the purpose of the ‘default’ keyword in Java interfaces?

default keyword is introduced in Java 8 and it is used with interfaces to define default methods. The main purpose of the default keyword in interfaces is to allow the declaration of a default method implementation within the interface itself. This means that class that implements that interface is not required to provide an implementation for the default method.

Example of default keyword

public interface Person{
    void name();

    default void speak() {
        System.out.println("men is speaking");
    }
}

Interview clearing guide to help you prepare effectively:

  • Review the Basics: Ensure you have a good understanding of core Java concepts Some of the important concepts are data types, control flow, object-oriented programming, exception handling, and collections.
  • Data Structures and Algorithms: Brush up on fundamental data structures like arrays, linked lists, stacks, queues, trees, and graphs. Also, Please go through the common algorithms like searching, sorting, and recursion.
  • Multithreading and Concurrency: Never miss revising the concepts of threads, synchronization, locks, and concurrent programming. Familiarize yourself with Java’s concurrency utilities and mechanisms.
  • Java Libraries and Frameworks: Be familiar with commonly used Java libraries and frameworks such as JDBC, Servlets, JSP, Spring, Hibernate, and JavaFX.
  • Object-Oriented Design: Learn about various Java design principles, patterns, and best practices for writing code and for creating modular, maintainable, and scalable Java applications.
  • Exception Handling: Understand how Java handles exceptions and how to write clean and robust exception-handling code.
  • Database Concepts: Review database concepts, SQL queries, and understand how to interact with databases using Java.
  • Java I/O and Networking: Familiarize yourself with input/output operations in Java, including file handling, streams, serialization, and socket programming.
  • Practice Coding: Solve coding problems related to Java on platforms like LeetCode or HackerRank. Practice implementing data structures, and algorithms, and solving real-world programming challenges.
  • Mock Interviews: Arrange mock interviews with friends or colleagues to simulate the interview environment. Receive feedback on your performance and work on improving weak areas.
  • Stay Updated: Keep up with the latest trends and updates in the Java ecosystem. Stay informed about new Java versions, features, frameworks, and libraries.

Remember, interview success not only depends on your technical knowledge but also on effective communication, problem-solving skills, and the ability to articulate your thoughts clearly. Practice presenting your ideas confidently and be prepared to explain your thought process during technical discussions.