Blog Java

Most Frequently Asked: Top 9 Java Interview Questions – Prepare Now!

Java Interview Questions

Photo by Scott Graham on Unsplash

Question 1. Is it possible to override static methods in Java?

In Java, static methods belong to the class rather than a particular instance of the class. This means that static methods are not part of the object’s instance, but rather part of the class itself. When you declare a static method in a subclass that has the same signature as a static method in the superclass, this is known as method hiding, not overriding.

Overriding in Java is a concept that applies to instance methods, where a subclass provides a specific implementation of a method that is already defined in its superclass. The version of the method that gets executed is determined at runtime, based on the object’s runtime type.

However, with static methods, the method that gets called is determined at compile time, based on the reference type. Thus, when you declare a static method with the same signature in a subclass, you are not overriding it, you are hiding the one in the superclass.The version of the static method to be called depends solely on the class type through which it is called, not the object’s runtime type.

Here’s a quick illustration:

Java
class Superclass {
    static void staticMethod() {
        System.out.println("Static method in Superclass");
    }
}
class Subclass extends Superclass {
    static void staticMethod() {
        System.out.println("Static method in Subclass");
    }
}
public class Test {
    public static void main(String[] args) {
        Superclass obj = new Subclass();
        obj.staticMethod(); // This will call the static method from Superclass, not Subclass
    }
}

In this example, even though obj is of runtime type Subclass, the static method from Superclass is called, demonstrating that static methods are not overridden.

Question 2. Explain the difference between final, finally, and finalize?

In Java, final, finally, and finalize are three distinct keywords/concepts with different purposes:

final

    • Usage: The final keyword can be used with variables, methods, and classes.
    • Purpose:
      • Variables: When applied to a variable, final makes the variable a constant, meaning its value cannot be changed once it is initialized.
      • Methods: When applied to a method, final prevents the method from being overridden in any subclass.
      • Classes: When applied to a class, final prevents the class from being subclassed.
    • Example:
      • Variable: final int MAX_VALUE = 5;
      • Method: public final void display() {}
      • Class: public final class MyFinalClass {}

    finally

      • Usage: The finally block is used in exception handling, paired with try-catch blocks.
      • Purpose: It defines a block of code that is always executed after the try block and any catch blocks, regardless of whether an exception was thrown or caught. It’s typically used for cleanup activities like closing file streams or database connections.

        Example:
      Java
      try { 
      // code that might throw an exception 
      } catch (ExceptionType name) { 
      // exception handler 
      } finally { 
      // code to be executed after try block ends, regardless of exception occurrence 
      }

      finalize

        • Usage: The finalize() method is a protected method of the Object class.
        • Purpose: It is called by the garbage collector on an object when garbage collection determines that there are no more references to the object. It’s usually overridden to dispose of system resources or to perform cleanup activities before the object is garbage collected.
        • Important Note: As of Java 9 and later, the finalize() method is deprecated due to its unpredictable behavior and performance issues. It’s recommended to use other mechanisms like try-with-resources or Cleaner for resource management and cleanup.

        Example:

        Java
        protected void finalize() throws Throwable { 
        // cleanup code, e.g., releasing resources super.finalize(); 
        }

        These three concepts are fundamental to Java programming, each serving its specific role in variable declaration, exception handling, and object lifecycle management.

        Question 3. What is the difference between == and equals() in Java give Java example?

        In Java, == and equals() are used for comparison, but they serve different purposes and work in different ways.

        == Operator

        • Usage: The == operator is used for reference comparison when dealing with objects and for value comparison when dealing with primitive types.
        • For Primitive Types: It compares the values for equality.
        • For Object References: It compares the memory addresses or references of the objects to check if they refer to the same object in memory.

        Example

        Java
        int a = 10;
        int b = 10;
        System.out.println(a == b); // true, because the values are the same
        
        String str1 = new String("Hello");
        String str2 = new String("Hello");
        System.out.println(str1 == str2); // false, because they are different objects in memory
        

        equals() Method

        • Usage: The equals() method is intended for content comparison. It’s defined in the Object class and can be overridden in derived classes to check if the current object is “equal to” the other object based on their contents.
        • Default Behavior: In the Object class, the default implementation of equals() uses the == operator to compare memory addresses, so its behavior is identical to == unless it’s overridden.
        • Custom Behavior: Many classes in Java, like String, Integer, and other wrapper classes, override equals() to provide content-based comparison.
        • Example:
        Java
        String str1 = new String("Hello");
        String str2 = new String("Hello");
        System.out.println(str1.equals(str2)); // true, because the content of the objects is the same
        
        // For custom objects, you might need to override equals()
        class Person {
            String name;
            Person(String name) {
                this.name = name;
            }
            
            @Override
            public boolean equals(Object obj) {
                if (this == obj) return true;
                if (obj == null || getClass() != obj.getClass()) return false;
                Person person = (Person) obj;
                return name.equals(person.name);
            }
        }
        
        Person person1 = new Person("John");
        Person person2 = new Person("John");
        System.out.println(person1.equals(person2)); // true, if names are the same
        

        In summary, == checks if two references point to the same object in memory (for objects) or if two primitive values are the same. In contrast, equals() is used for checking if two objects have the same content, according to the logic defined in the equals() method, which can be overridden.

        Question 4. What distinguishes abstract classes from interfaces in object-oriented programming?

        Abstract Classes are partially implemented classes that cannot be instantiated directly. They provide a template for other classes to extend, allowing for shared structures and behaviours.

        Abstract classes can house both concrete (fully implemented) methods and abstract methods, which lack implementations and must be overridden by subclasses.

        Interfaces are contracts that delineate a set of methods without detailing their implementations. Classes that implement an interface commit to implementing all its methods. Traditionally, interfaces were purely abstract, but modern OOP languages like Java now permit default and static methods within interfaces, thus enriching their utility.

        Let’s dive deeper into the distinctions with practical examples:

        Example 1: Abstract Class

        Consider an abstract class Vehicle that outlines shared characteristics and functionalities of vehicles:

        Java
        public abstract class Vehicle {
            protected String name;
        
            public Vehicle(String name) {
                this.name = name;
            }
        
            // Concrete method
            public void display() {
                System.out.println("Vehicle name: " + name);
            }
        
            // Abstract method
            public abstract void accelerate();
        }

        A subclass Car can extend Vehicle, providing specific implementations for abstract methods:

        public class Car extends Vehicle {
            public Car(String name) {
                super(name);
            }
        
            @Override
            public void accelerate() {
                System.out.println(name + " is accelerating.");
            }
        }

        Example 2: Interface with Default and Static Methods

        Now, let’s define an interface Gadget with default and static methods:

        Java
        public interface Gadget {
            // Abstract method
            int BATTERY_LIFE_HOURS = 24; // public, static, and final are implicit
            void turnOn();
            
            // Default method
            default void reboot() {
                System.out.println("Gadget is rebooting.");
            }
        
            // Static method
            static void displayType() {
                System.out.println("This is a Gadget.");
            }
        }

        A class Smartphone implements Gadget, fulfilling the contract:

        Java
        public class Smartphone implements Gadget {
            @Override
            public void turnOn() {
                System.out.println("Smartphone is turning on.");
            }
        }
        
        public class Main {
            public static void main(String[] args) {
                Smartphone myPhone = new Smartphone();
                myPhone.turnOn(); // Outputs: Smartphone is turning on.
                myPhone.reboot(); // Outputs: Gadget is rebooting.
                Gadget.displayType(); // Outputs: This is a Gadget.
            }
        }

        When to Use Each

        • Use Abstract Classes when sharing code among closely related classes, leveraging both shared and unique behaviours. Abstract classes are ideal for situations where some methods should have a default implementation, and there’s a need for the class state.
        • Use Interfaces to define a common behaviour that can be adopted by any class, regardless of its place in the class hierarchy. With the addition of default and static methods, interfaces offer greater flexibility, allowing for method implementations without forcing the class inheritance structure.

        Please read more on Differene between Abstract Class Interface Here

        https://codetechsummit.com/difference-between-abstract-class-and-interface/

        Question 5: What is the distinction between String, StringBuilder, and StringBuffer in Java?

        In Java, String, StringBuilder, and StringBuffer https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html are classes used for working with text data, but they differ significantly in their properties and use cases:

        1. String

        • Immutability: Once a String object is created, its content cannot be changed. If you try to modify it, a new String instance is created instead.
        • Performance: Due to its immutable nature, operations like concatenation can be costly in performance-critical situations, as each operation results in creating a new String object.
        • Thread Safety: It is inherently thread-safe because its immutability means its state cannot be changed by multiple threads.

        2. StringBuffer

        • Mutability: Unlike String, StringBuffer is mutable. Its content can be changed using methods like append(), insert(), delete(), etc., without creating a new object each time.
        • Synchronization: It is thread-safe as most of its methods are synchronized, meaning multiple threads can use a StringBuffer object without corrupting its state.
        • Performance: While its thread safety is a benefit in multi-threaded contexts, the synchronization overhead makes it less performant compared to StringBuilder in single-threaded scenarios.

        3. StringBuilder

        • Mutability: Similar to StringBuffer, StringBuilder is also mutable, allowing modifications without creating new objects.
        • Thread Safety: Unlike StringBuffer, StringBuilder is not synchronized, which means it is not thread-safe. This lack of synchronization allows for better performance in single-threaded applications.
        • Performance: It offers better performance than StringBuffer in scenarios where thread safety is not a concern, due to the absence of synchronization overhead.

        Summary

        • Use String when you need immutability and thread safety without the need for modifying the text data.
        • Choose StringBuffer in multi-threaded scenarios where you need to modify text data safely across different threads.
        • Opt for StringBuilder in single-threaded or thread-confined scenarios where you need to modify text data without the overhead of synchronization.

        Question 6: What is the Volatile Keyword?

        The volatile keyword in Java is used to indicate that a variable’s value will be modified by different threads. When a field is declared as volatile, the Java Memory Model ensures that any thread that reads the field will see the most recent write by any thread.

        The keyword addresses memory visibility issues, ensuring that changes made in one thread are immediately visible to other threads.

        Please read more about volatile keyword in Java.

        https://codetechsummit.com/volatile-keyword-in-java/

        Question 7: Difference between a vector and array list collection?

        In Java, both Vector and ArrayList are resizable-array implementations of the List interface, but they have some differences in terms of their behavior, thread-safety, and performance characteristics.

        Vector:

        • Vector is synchronized, which means it is thread-safe. Methods like add(), remove(), get(), etc., are synchronized, making Vector suitable for multi-threaded environments.
        • Vector is slower than ArrayList because of the overhead of synchronization.
        • Vector has a legacy implementation and is considered to be a legacy class.
        • Vector uses a synchronized get() and set() method, which can cause performance issues when iterating over large data sets.
        • Vector allows null values to be inserted.
        • In the case of Vector, the capacity is increased by doubling the current capacity when the number of elements exceeds the current capacity. The capacity of a newly created Vector is 10 by default.

        Example:

        Java
        import java.util.Vector;
        
        public class VectorExample {
            public static void main(String[] args) {
                Vector<String> vector = new Vector<>();
                vector.add("Apple");
                vector.add("Banana");
                vector.add("Cherry");
        
                System.out.println("Vector elements: " + vector);
        
                vector.remove(1);
                System.out.println("Vector after removal: " + vector);
            }
        }

        ArrayList:

        • ArrayList is non-synchronized, which means it is not thread-safe. If multiple threads access an ArrayList concurrently, it must be manually synchronized.
        • ArrayList is generally faster than Vector because it doesn’t have the overhead of synchronization.
        • ArrayList is more widely used than Vector in modern Java programming.
        • ArrayList allows null values to be inserted.
        • In the case of ArrayList, the capacity is increased by roughly 50% when the number of elements exceeds the current capacity.

        Example:

        Java
        import java.util.ArrayList;
        
        public class ArrayListExample {
            public static void main(String[] args) {
                ArrayList<Integer> arrayList = new ArrayList<>();
                arrayList.add(10);
                arrayList.add(20);
                arrayList.add(30);
        
                System.out.println("ArrayList elements: " + arrayList);
        
                arrayList.remove(1);
                System.out.println("ArrayList after removal: " + arrayList);
            }
        }

        In summary, Vector is synchronized and thread-safe, making it suitable for multi-threaded environments, but slower than ArrayList due to the overhead of synchronization.

        ArrayList is non-synchronized and generally faster, but it is not thread-safe, so you need to manually synchronize it if you plan to use it in a multi-threaded environment. Modern Java programming tends to favor ArrayList over Vector due to its better performance and wider usage.

        If you don’t need thread-safety, it is generally recommended to use ArrayList as it provides better performance. However, if you need thread-safety and don’t want to handle synchronization manually, Vector can be a suitable choice, but with a potential performance trade-off.

        Question 8: Explain the synchronization keyword in java.

        Synchronization in Java is a mechanism that allows controlling the access of multiple threads to shared resources or critical sections of code. It is used to prevent race conditions and ensure thread safety, where multiple threads try to access and modify the same shared resource concurrently, leading to unpredictable results.

        In Java, synchronization is achieved using the synchronized keyword, which can be applied to methods or code blocks. When a thread acquires a lock on a synchronized method or block, it gains exclusive access to the associated code, and other threads attempting to enter the same synchronized section are forced to wait until the lock is released.

        Here’s an example that demonstrates synchronization in Java:

        Java
        class Counter {
            private int count = 0;
        
            public synchronized void increment() {
                count++;
            }
        
            public synchronized void decrement() {
                count--;
            }
        
            public synchronized int getCount() {
                return count;
            }
        }
        
        public class SynchronizationExample {
            public static void main(String[] args) {
                Counter counter = new Counter();
        
                // Thread 1
                Thread threadA = new Thread(() -> {
                    for (int i = 0; i < 10000; i++) {
                        counter.increment();
                    }
                });
        
                // Thread 2
                Thread threadB = new Thread(() -> {
                    for (int i = 0; i < 10000; i++) {
                        counter.decrement();
                    }
                });
        
                threadA.start();
                threadB.start();
        
                try {
                    threadA.join();
                    threadB.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        
                System.out.println("Final count: " + counter.getCount());
            }
        }

        In this example, we have a Counter class with three methods: increment(), decrement(), and getCount(). All three methods are declared as synchronized, which means that only one thread can execute these methods at a time on a given instance of the Counter class.

        In the main method, we create two threads: threadA and threadB. threadA increments the counter 10,000 times, while threadB decrements the counter 10,000 times. Without synchronization, these threads could interleave their execution, leading to a race condition and an incorrect final count.

        However, with the synchronized keyword applied to the increment(), decrement(), and getCount() methods, only one thread can access these methods at a time, ensuring that the counter is updated atomically and consistently.

        After starting both threads and waiting for them to finish using the join() method, we print the final count. Since the increment and decrement operations are synchronized, the final count should be 0, as the number of increments and decrements should be equal.

        Output:

        Java
        Final count: 0

        By using synchronization, we have prevented race conditions and ensured that the shared resource (the count variable) is accessed and modified in a thread-safe manner.

        It’s important to note that synchronization comes with a performance cost due to the overhead of acquiring and releasing locks. Therefore, it should be used judiciously and only when necessary to protect shared resources from concurrent access by multiple threads. In cases where synchronization is not required, it’s better to avoid it for better performance.

        Question 9: What is the importance or the significance of equals and hash code?

        In Java, the equals() and hashCode() methods play crucial roles in object comparison and efficient storage and retrieval of objects in data structures, respectively. Their significance lies in the following aspects:

        Equals Method (equals()):

          • The equals() method is used to determine whether two objects are logically equivalent or not.
          • It is essential for correctly implementing the contract of the equals() method, which states that if x.equals(y) returns true, then y.equals(x) must also return true.
          • The default implementation of equals() in the Object class compares the references (memory addresses) of the objects. However, in many cases, we need to override this method to compare the actual content or state of the objects.
          • Proper implementation of the equals() method is crucial for correct functioning of data structures like HashSet, HashMap, and other collections that rely on object equality.

          HashCode Method (hashCode()):

            • The hashCode() method is used to generate a unique integer value (hash code) for an object.
            • The hash code is typically used in hash-based data structures, such as HashMap, HashSet, and hash tables, for efficient storage and retrieval of objects.
            • The contract of the hashCode() method states that if x.equals(y) returns true, then x.hashCode() and y.hashCode() must produce the same hash code value.
            • Proper implementation of the hashCode() method is essential for the efficient performance of hash-based data structures, as it allows for better distribution of objects within the underlying data structure, reducing collisions and improving lookup times.

            The significance of these methods lies in the fact that they enable correct and efficient implementations of various data structures and algorithms in Java. Here are some specific scenarios where equals() and hashCode() are important:

            • Comparing objects: The equals() method allows for meaningful comparison of objects based on their content or state, rather than just their memory addresses.
            • Unique object representation: The hashCode() method provides a way to generate a unique integer representation of an object, which is crucial for efficient storage and retrieval in hash-based data structures.
            • Collections and data structures: Proper implementation of equals() and hashCode() is essential for the correct functioning of collections like HashSet, HashMap, and other data structures that rely on object equality and hashing.
            • Caching and indexing: Many caching and indexing mechanisms rely on efficient object comparison and hashing, making equals() and hashCode() crucial for their performance.
            • Database operations: When working with databases, the proper implementation of equals() and hashCode() is important for efficient querying, indexing, and object retrieval.

            It is generally recommended to override both equals() and hashCode() methods in a class if you need to define a custom notion of equality for objects of that class.

            Additionally, whenever you override equals(), you should also override hashCode() to maintain the contract between the two methods, ensuring consistent behavior and efficient performance of data structures and algorithms that rely on them.

            Avatar

            Neelabh

            About Author

            As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

            You may also like

            Blog Design Pattern

            Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

            Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
            Blog Tech Toolkit

            Base64 Decode

            Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over