Java Interview: Must-Prepare Questions For Beginner To Expert Level

Here is an exhaustive list of the top Java interview questions which you must prepare as a beginner, intermediate, or expert-level professional. There are easy-to-understand and remember answers also provided in each question to help you prepare well. Wish you all the best!

Core Java Interview Questions

What is the difference between Abstract Class and an Interface?

Abstract classes are used when there is some common functionality that multiple classes share, but there is also some functionality that is specific to each class. Interfaces are used when there is a set of requirements that a class must meet, but there is no specific implementation required. Here is a concise summary of the difference between abstract class and interface in Java.

FeatureAbstract classInterface
Can have abstract and non-abstract methodsYesNo
Supports multiple inheritanceNoYes
Can have final, non-final, static, and non-static variablesYesOnly static and final variables
Can provide the implementation of the interfaceYesNo
Keyword used to declareabstractinterface
Abstraction levelPartial (0 to 100%)Full (100%)

How to explain this concept or difference with an example?

To explain, use the following example of how abstract classes and interfaces can be used. Let’s say you are designing a game where there are different types of characters, such as humans, elves, and dwarves. All of these characters have some common characteristics, such as the ability to move, attack, and defend. However, each type of character also has some unique characteristics, such as the ability to cast spells or use special weapons.

In this case, you could use an abstract class to represent the common characteristics of all characters. This abstract class would define methods for moving, attacking, and defending. You could then create concrete classes for each type of character, such as a Human class, an Elf class, and a Dwarf class. These concrete classes would inherit from the abstract class and override the methods for moving, attacking, and defending to implement the specific behavior for each type of character.

You could also use an interface to represent the requirements that all characters must meet. This interface would define methods for moving, attacking, and defending. Any class that implements this interface would be guaranteed to have these methods, even if it does not inherit from the abstract class.

In this example, abstract classes and interfaces can be used together to create a flexible and extensible design for a game.

How do you write Abstract classes and Interfaces?

To write an abstract class in Java, you use the abstract keyword. For example:

abstract class Animal {
    public abstract void makeSound();
}

An abstract class can have both abstract and non-abstract methods. Abstract methods must be overridden by any class that extends the abstract class. Non-abstract methods can be implemented in the abstract class, or they can be overridden by a subclass.

To write an interface in Java, you use the interface keyword. For example:

interface Flyable {
    void fly();
}

An interface can only have abstract methods. The methods in an interface must be implemented by any class that implements the interface.

What are some benefits and drawbacks of using abstract classes and interfaces

Here are some of the benefits of using abstract classes and interfaces:

  • Abstract classes and interfaces can help to improve code reusability.
  • Abstract classes and interfaces can help to make code more maintainable.
  • Abstract classes and interfaces can help to improve code readability.

Here are some of the drawbacks of using abstract classes and interfaces:

  • Abstract classes and interfaces can make code more complex.
  • Abstract classes and interfaces can make code less flexible.
  • Abstract classes and interfaces can make code less efficient.

When deciding whether to use an abstract class or an interface, you should consider the following factors:

  • The amount of shared functionality between classes.
  • The need for code reusability.
  • The need for code maintainability.
  • The need for code readability.
  • The complexity of the code.
  • The flexibility of the code.
  • The efficiency of the code.
Can we overload the main method in Java? if yes, how?

Yes, we can overload the main method in Java. However, the Java Virtual Machine (JVM) will only execute the main method with the following signature:

public static void main(String[] args)

Any other overloaded main methods will not be executed.

Here is an example of a Java class with two overloaded main methods:

public class OverloadedMain {
 public static void main(String[] args) {
     System.out.println("This is the original main method.");
 }

 public static void main(int[] args) {
     System.out.println("Overloaded main method with an int[] array as its argument.");
 }
}

When this class is compiled and executed, the following output will be displayed:

This is the original main method.

The overloaded main method with the int[] array as its argument will not be executed.

It is important to note that overloading the main method is not a common practice in Java. The main method is typically used to initialize the application and start the execution of the program. Overloading the main method can make it difficult to understand how the application works.

What if I still want to call the overloaded method?

If you want the overloaded main method to be executed instead of the original one, you can call the overloaded main method from the original main method.

Does the Java constructor return any value? If yes, what?

No, a Java constructor does not return any value. It is a special type of method that is used to initialize an object when it is created. The constructor has the same name as the class it is defined in. It can be overloaded, just like a regular method. However, it does not have a return type. When an object is created, the constructor is implicitly invoked. It can be used to initialize the object’s instance variables.

Here is an example of a Java constructor:

class MyClass {

    private int id;
    private String name;

    public MyClass(int id, String name) {
        this.id = id;
        this.name = name;
    }

}

This constructor has two parameters: an int and a String. When an object of type MyClass is created, the constructor will be called with the specified values for the parameters. The constructor will then initialize the object’s instance variables with the values of the parameters.

If you try to use the return statement in a constructor, you will get a compiler error. This is because constructors do not return any value.

Can we execute a Java program without a main() method?

Yes, you can execute a Java program without a main() method by using a static block. A static block is a group of statements that gets executed only once when the class is loaded into the memory by Java ClassLoader. It is also known as a static initialization block.

Here is an example of a Java program without a main() method:

class Program {

    static {
        System.out.println("Hello, world!");
    }

}

This program will print the following output:

Hello, world!

However, it is important to note that this is not a recommended practice. The main() method is the entry point for all Java programs, and it is important to have a clear understanding of how it works. Using a static block to execute a Java program can lead to unexpected behavior and make it difficult to debug your code.

In Java 7 and later versions, the Java Virtual Machine (JVM) will not allow you to execute a Java program without a main() method. This is because the JVM checks for the presence of a main() method before initializing the class.

Just a Pro Tip: If you need to execute a Java program without a main() method, you can use a third-party tool such as JRebel or Eclipse Jetty. These tools allow you to hot-deploy changes to your Java code without restarting the JVM. This can be useful for debugging and testing your code.

Explain the “this” keyword in Java.

The “this” keyword in Java refers to the current object in a method or constructor. It can be used to:

  • Refer to instance variables of the current object.
  • Invoke the current class’s constructor.
  • Invoke the current object’s methods.
  • Return the current object from a method.
  • Pass the current object as an argument to another method.
  • Call another constructor in the same class.

The “this” keyword is a very important keyword in Java, and it is used in a variety of different contexts.

Here are some examples of how the “this” keyword can be used:

// Refer to instance variables
public class Example {
  int x;

  public Example(int x) {
    this.x = x;
  }

  public void printX() {
    System.out.println(this.x);
  }
}

// Invoke the current class's constructor
Example example = new Example(5);
example.printX();

// Return the current object
public Example createExample() {
  return new Example();
}

// Pass an argument to another method
this.printX();
What is the difference between String, StringBuffer, and StringBuilder?
  • A string is an immutable class, meaning that once it is created, its contents cannot be changed.
  • StringBuffer is a mutable class that is thread-safe, meaning that multiple threads can access it without interfering with each other.
  • StringBuilder is a mutable class that is not thread-safe.

In general, you should use StringBuffer if you need to manipulate strings in a multithreaded environment, and StringBuilder if you need to manipulate strings in a single-threaded environment.

Here is a table that summarizes the key differences between the three classes:

FeatureStringStringBufferStringBuilder
ImmutableYesNoNo
Thread-safeNoYesNo
PerformanceFasterSlowerFaster
Use casesSingle-threaded environmentsMultithreaded environmentsSingle-threaded environments

Similar and more details on this topic here – What is the difference between String and StringBuffer?

Why doesn’t Java allow multiple inheritances, but it allows implementing multiple interfaces?

There are a few reasons why Java doesn’t allow multiple inheritances of classes but does allow multiple implementations of interfaces.

  • The diamond problem: Multiple inheritances of classes can lead to the diamond problem, which is a situation where a subclass inherits from two classes that have the same method with the same signature. This can cause ambiguity when the subclass tries to call the method because the compiler doesn’t know which method to call.
  • Complexity: Multiple inheritance of classes can make code more complex and difficult to understand. This is because the compiler has to keep track of all the different classes that a subclass inherits from, and it has to make sure that the methods in those classes are compatible with each other.
  • Lack of need: In most cases, multiple inheritance of classes is not necessary. Interfaces can provide the same functionality, without the added complexity and risk of the diamond problem.

Multiple implementations of interfaces are allowed in Java because it does not have the same problems as multiple inheritance classes. When a class implements an interface, it is only required to provide an implementation for the methods that are declared in the interface. This means that the compiler does not have to worry about compatibility between the methods in different interfaces.

In addition, multiple implementations of interfaces can actually make code more concise and easier to understand. This is because interfaces can be used to group together related methods, and classes can implement multiple interfaces to inherit the methods from those interfaces.

Overall, Java’s decision to allow multiple implementations of interfaces but not multiple inheritances of classes is a sound one. It allows developers to get the benefits of multiple inheritance without the added complexity and risk.

How is inheritance different from Aggregation?

Inheritance and aggregation are both object-oriented programming (OOP) concepts that are used to model relationships between classes. However, they differ in a few key ways.

  • Inheritance is an “is-a” relationship. This means that a child class inherits all of the properties and methods of its parent class. For example, a Dog class could inherit from an Animal class. This would mean that all Dog objects would also be Animal objects.
  • Aggregation is a “has-a” relationship. This means that a class contains another class as a member. For example, a Car class could have an Engine member. This would mean that every Car object would have an Engine object, but the Engine object could also be used by other classes, such as a Tractor class.

In general, inheritance should be used when there is a strong “is-a” relationship between two classes. For example, a Dog is a type of Animal, so it makes sense for Dog to inherit from an Animal. Aggregation should be used when there is a weaker “has-a” relationship between two classes. For example, a Car has an Engine, but a Car is not a type of Engine.

Here is a table that summarizes the key differences between inheritance and aggregation:

FeatureInheritanceAggregation
Relationship type“Is-a”“Has-a”
InheritanceThe child class inherits all properties and methods of the parent classA Class contains another class as a member
Tightness of couplingTightly coupledLoosely coupled
ReusabilityHighly reusableLess reusable

Inheritance and aggregation are both powerful tools that can be used to model complex relationships between classes. However, it is important to understand the differences between them so that you can choose the right tool for the job.

What is a covariant return type in Java?

Covariant return types are a feature of Java that allows the return type of a method to be a subtype of the return type of the overridden method. This means that a subclass method can return a more specific type than the return type of the superclass method.

For example, the following code is valid in Java:

class Animal {
    public abstract Animal getAnimal();
}

class Cat extends Animal {
    @Override
    public Cat getAnimal() {
        return this;
    }
}

Covariant return types can be used to make code more concise and readable. For example, the following code is more concise and readable than the code without covariant return types:

List<Animal> animals = new ArrayList<>();
animals.add(new Cat());

Animal animal = animals.get(0);
// animal is a Cat

Without covariant return types, the code would have to be written as follows:

List<Animal> animals = new ArrayList<>();
animals.add(new Cat());

Animal animal = (Animal) animals.get(0);
// animal is a Cat

Covariant return types are a powerful feature of Java that can be used to make code more concise, readable, and maintainable.

Here are some of the advantages of using covariant return types:

  • Conciseness: Covariant return types can make code more concise by eliminating the need for casts.
  • Readability: Covariant return types can make code more readable by making it easier to understand the intent of the code.
  • Maintainability: Covariant return types can make code more maintainable by making it easier to add new features to the code.

However, there are also some potential drawbacks to using covariant return types:

  • Potential for errors: Covariant return types can introduce the potential for errors if the code is not written carefully. For example, if a method with a covariant return type is called with an argument of a subtype of the return type, the code may not behave as expected.
  • Lack of support by some tools: Some tools, such as IDEs and debuggers, may not fully support covariant return types. This can make it difficult to use these tools to debug code that uses covariant return types.
Why do we override the hashCode() and equals() methods in Java?

The hashCode() and equals() methods are two of the most important methods in Java. They are used to determine equality and hash codes for objects.

The hashCode() method returns an integer value that represents the object. This value is used to quickly find objects in hash-based collections, such as HashMap and HashSet.

The equals() method is used to compare two objects for equality. If two objects have the same hash code, then the equals() method will be called to determine if they are actually equal.

If you override the equals() method, you should also override the hashCode() method. This is because the hashCode() method must return the same value for any two objects that are considered equal by the equals() method.

The following is a good example of how to override the hashCode() and equals() methods:

public class MyClass {

    private int id;
    private String name;

    public MyClass(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        MyClass myClass = (MyClass) o;

        return id == myClass.id &&
                name.equals(myClass.name);
    }

    @Override
    public int hashCode() {
        int result = id;
        result = 31 * result + name.hashCode();
        return result;
    }
}

In this example, the hashCode() method returns a hash code that is based on the id and name fields of the object. The equals() method compares two objects for equality by checking if their id and name fields are equal.

By overriding the hashCode() and equals() methods, you can ensure that your objects behave correctly in hash-based collections.

Briefly explain the difference between the Runnable and Callable interfaces in Java

Difference between the Runnable and Callable interfaces in Java:

  • Runnable is a functional interface that has a single run() method that doesn’t accept any parameters or return any values. This works for situations where we aren’t looking for a result of the thread execution, such as incoming events logging.
  • Callable is a generic interface containing a single call() method that returns a generic value V. This works for situations where we need to get a result from the thread execution, such as downloading a file or executing a query.

In addition to these differences, Runnable and Callable are also executed differently. Runnable tasks can be run using the Thread class or ExecutorService, whereas Callables can only be run using the latter.

Here is a table that summarizes the key differences between Runnable and Callable:

FeatureRunnableCallable
Return valueNoneGeneric value V
Checked exceptionsNot allowedAllowed
Execution mechanismThread class or ExecutorServiceExecutorService only
What is the use of serialVersionUID in Java?

serialVersionUID is a unique identifier for a serializable class. It is used to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization. If the serialVersionUIDs do not match, an InvalidClassException will be thrown.

It is automatically generated by the compiler if it is not specified by the developer. However, it is important to note that the compiler-generated serialVersionUID will change if the class is modified in any way, such as by adding or removing fields or methods.

It is recommended that developers specify their own serialVersionUIDs to ensure compatibility between different versions of their code. To do this, they must declare a private static final long field named serialVersionUID. The value of this field can be any long value, but it is recommended to use a randomly generated value.

By specifying their own serialVersionUIDs, developers can avoid InvalidClassExceptions caused by incompatible changes to their code. This can help to ensure that their code is reliable and that it can be used in a variety of environments.

Difference between final, finally, and finalize() in Java?
  • Final is an access modifier that can be applied to variables, methods, and classes. A final variable cannot be reassigned, a final method cannot be overridden, and a final class cannot be inherited.
  • Finally is a block of code that is always executed, regardless of whether an exception is thrown. This can be used to ensure that important cleanup code is always run, even if an exception occurs.
  • Finalize() is a method that is called by the garbage collector just before an object is destroyed. This method can be used to perform cleanup tasks, such as releasing resources or closing files.
Featurefinalfinallyfinalize()
Access modifierYesNoNo
ScopeVariable, method, or classBlock of codeMethod
ExecutionAt compile timeAt runtimeBefore object is destroyed
PurposeTo prevent modificationTo ensure cleanup code is always runTo perform cleanup tasks
What is the difference between equals() and == in Java ?
  • == is an operator that compares the references of two objects. It returns true if the two objects refer to the same memory location, and false otherwise.
  • equals() is a method that compares the values of two objects. It returns true if the two objects have the same values, and false otherwise.

The main difference between == and equals() is that == compares the references of the objects, while equals() compares the values of the objects. This means that two objects can be equal according to equals() even if they are not referring to the same memory location.

For example, the following code will return true:

String s1 = "Hello";
String s2 = "Hello";

System.out.println(s1 == s2); // true
System.out.println(s1.equals(s2)); // true

However, the following code will also return differently:

String s1 = new String("Hello");
String s2 = new String("Hello");

System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true

This is because s1 and s2 are two different String objects in the heap, even though they have the same value.

In general, you should use equals() to compare the values of objects, and == to compare the references of objects. However, there are some cases where you may need to use ==, such as when checking if two objects are the same instance of a class.

How hashing works internally in Java?

You must know the in and out answer to this question as an experienced developer.

Hashing is a technique for storing and retrieving data in a table called a hash table. A hash table consists of a collection of buckets, where each bucket can store one or more items. The items in a hash table are stored using a hash function, which converts the key of an item into an index for the bucket where the item should be stored.

Learn more in detail in our other blog: Hashing Techniques and Collision Handling: All You Need to Know

Hashing is a powerful tool that can be used to store and retrieve data quickly and efficiently. It is a fundamental part of many data structures and algorithms, and it is used in a wide variety of applications.

What is the use of the “synchronized” keyword?

Follow-up questions: How do you make a class, block, or method synchronized? What are the benefits and drawbacks of using the “synchronized” keyword?

This is a multithreading topic in Java. The synchronized keyword is used to control access to shared resources in Java. When a block of code is synchronized, only one thread can execute that block of code at a time. This prevents race conditions, which can occur when multiple threads try to access the same resource at the same time.

The synchronized keyword can be used on methods and blocks of code. When synchronized is used on a method, the lock is acquired on the object that the method is called on. When synchronized is used on a block of code, the lock is acquired on the object that is specified in the parentheses after the synchronized keyword.

Checkout more details on this topic in our other blog Deadlocks and How to Avoid Them with Synchronization

What are the other topics to be prepared for this core Java interview series?

Further Readings

Feel free to share your thoughts on this topic in the comments section below 👇 We would be happy to hear and discuss the same 🙂

Spread the word!
0Shares

Leave a comment

Your email address will not be published. Required fields are marked *

3 thoughts on “Java Interview: Must-Prepare Questions For Beginner To Expert Level”