Core Java Interview Questions

Core Java Interview Questions | Also see:- Java Collection Interview Questions, Java 8 Stream Operations with Examples

Table of Contents

Java OOP Interview Questions

1. Why Java is not 100% Object-oriented?

Because of primitive data types:- boolean, byte, char, int, float, double, long, short. To make them object-oriented we have wrapper classes that wrap the primitive data type into an object of that class.

2. Why pointers are not used in Java?

  • Since JVM is responsible for implicit memory allocation, thus to avoid direct access to memory by the user, pointers are discouraged in Java.
  • Increases the complexity of the program and since Java is known for its simplicity of code, adding the concept of printers will be contradicting.

In Java, JVM is responsible for allocating and deallocating the memories. The pointer points to the memory locations and the user can perform any faulty operations on those memory locations or they can manipulate the things present at that memory locations. Therefore to avoid direct access to the memory location by the user, Pointers are discouraged in Java.

3. What is the JIT compiler in Java?

The JIT (Just-In-Time) compiler in Java is a component of the Java Virtual Machine (JVM) that improves the performance of Java applications by compiling bytecode into native machine code at runtime. When a Java program is executed, the JVM initially interprets the bytecode instructions. However, to enhance execution speed, the JIT compiler identifies portions of the bytecode that are frequently executed and compiles them into native machine code, which can be executed directly by the CPU.

The JIT compiler employs various optimization techniques to generate efficient native code, such as method inlining, loop unrolling, and dead code elimination. This approach combines the benefits of both interpretation (which allows for platform independence and rapid startup) and compilation (which provides optimized execution speed).

Overall, the JIT compiler plays a crucial role in improving the performance of Java applications by dynamically optimizing the execution of bytecode instructions.

4. What is the marker interface and use of it & example?

An interface having no data member and member functions. In simpler terms, an empty interface is called the marker interface. E.x:- Serializable, Cloneable.

public interface Cloneable { }

Its sole purpose is to mark or tag classes that implement it with certain characteristics or capabilities. These interfaces are often used to provide metadata about the class implementing them. They serve as a way to categorize or identify classes at runtime.

One common use of marker interfaces is in Java’s serialization mechanism, where marker interfaces like Serializable are used to indicate that objects of a class can be serialized. When an object is serialized, the Java runtime checks whether the class of the object implements the Serializable interface. If it does, the object can be serialized; otherwise, a NotSerializableException is thrown.

Here’s an example of a marker interface in Java:-

import java.io.Serializable;

// Marker interface
interface MarkerInterface extends Serializable {
    // This marker interface doesn't declare any methods
}

// Class implementing the marker interface
class MyClass implements MarkerInterface {
    // Class implementation
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();

        // Check if the object implements the marker interface
        if (obj instanceof MarkerInterface) {
            System.out.println("Object belongs to a class implementing MarkerInterface.");
        } else {
            System.out.println("Object does not belong to a class implementing MarkerInterface.");
        }
    }
}

6. Can we override a private or static method in Java?

No, we can’t override a private or static method in Java. 

We can’t override a private method in the subclass because it is not accessible there. If we try to override them then the created method in subclass is just another private method of that class with the same name.

For static methods, if we create a similar method with the same return type and the same method arguments in the child class then it will hide the superclass methods, this is known as method hiding. In case of method hiding, if we want to call superclass methods then we can call them using ClassName.staticMethodName.

7. What will be the output of the below program, is it valid Java code?

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test.get());
    }

    // static method
    public static int get() {
        return 1;
    }
}

Yes, it is a valid Java code and it will give output 1. We can also call a static method on object reference but it is not a best practice to call the static method.

8. Does the “finally” block always execute in Java?

Not in the following cases:-

  1. System.exit(0) function in the try-catch block.
  2. System crash 

Note:- There is no method like System.crash().

9. How can we make a Class immutable?

Perform 6 steps:-

  1. Declare the class as final so it can’t be extended.
  2. Make all fields private so that direct access is not allowed.
  3. Do not provide setter methods for variables.
  4. Make all mutable fields final so that their value can be assigned only once.
  5. Initialize all the fields via a constructor performing a deep copy.
  6. Perform cloning of objects in the getter methods to return a copy rather than returning the actual object reference.

Example:- String is an immutable class in Java.



// Step 1: Declare the class as final so it can't be extended.
public final class Person {

    // Step 2: Make all fields private so that direct access is not allowed.
    private final String name;
    private final int age;
    private final Address address; 
    // Assuming Address is another immutable class.

    // Step 4: Make all mutable fields final so that 
    // their value can be assigned only once.
    // Assuming the Address class is also immutable, 
    // so we can directly assign it in the constructor.

    // Step 5: Initialize all the fields via a constructor performing a deep copy.
    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = new Address(address); 
        // Perform deep copy if Address is mutable.
    }

    // Step 3: Do not provide setter methods for variables.

    // Step 6: Perform cloning of objects in the getter methods 
    // to return a copy rather than returning the actual object reference.

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Address getAddress() {
        return new Address(address); 
        // Perform cloning or return immutable Address object.
    }
}

10. Is Java Pass by value or pass by reference?

Java is => pass by value.

11. Can parents have high visibility and children have less visibility?

No, we can not change the visibility of the inherited method. We will get a compile-time error. The inherited method can have either the same or more visibility but reducing visibility is not allowed.

12. Shadowing of Static Methods/Variable | Find the output of the below program.

public class Animal {
    int legs = 4;
    public void specialProperty() {
        System.out.println("Animal.specialProperty()");
    }

}
public class Dog extends Animal {
    int legs = 2;
    public void specialProperty() {
        System.out.println("Dog.specialProperty()");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.specialProperty(); // Dog.specialProperty()
        System.out.println(animal.legs); // 4

        Animal animal1 = new Animal();
        animal1.specialProperty(); // Animal.specialProperty()
        System.out.println(animal1.legs); // 4
    }
}

Since specialProperty() is an instance method therefore the runtime object is used while calling the method. Therefore animal.specialProperty(); returns “Dog.specialProperty()”. 

Instance MethodsRuntime Object
Instance VariableReference Object (Compile time Object)
Static Method ShadowingReference Object (Compile time Object)
Static Variable ShadowingReference Object (Compile time Object)

In Java, fields are not overridden like methods; they are hidden in subclasses if they are redefined. In Dog class, we have a field legs that hides the legs field from the Animal class. When we create an instance of Dog and assign it to a variable of type Animal, we are still accessing the legs field from the Animal class because the reference type is Animal. So, in the case of Animal animal = new Dog();, the compiler looks at the type of the reference (Animal) when resolving animal.legs, so it accesses the legs field from the Animal class, which is 4.

Dog animal = new Dog();
System.out.println(animal.legs); // 2

If you want polymorphic behavior for fields, you would need to access them through methods rather than directly accessing the fields because instance methods work on runtime objects whereas instance variables work on reference objects (compile time objects).

public class Animal {
    int legs = 4;
    public int getLegs() {
        return legs;
    }
}
public class Dog extends Animal {
    int legs = 2;
    @Override
    public int getLegs() {
        return legs;
    }
}
Animal animal = new Dog();
System.out.println(animal.getLegs()); // 2

But if they are declared as static methods, then instead of the runtime object, the reference of the variable will be used.

public class Animal {
    public static void specialProperty() {
        System.out.println("Animal.specialProperty()");
    }

}
public class Dog extends Animal {
    public static void specialProperty() {
        System.out.println("Dog.specialProperty()");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.specialProperty(); // Animal.specialProperty()

        Animal animal1 = new Animal();
        animal1.specialProperty(); // Animal.specialProperty()
    }
}

13. Can we achieve runtime polymorphism with instance variables?

No. Instance variables always work based on the reference object (compile time object). Example:-

public class Animal {
    public String color = "Animal Color";
}

public class Dog extends Animal {
    public String color = "Dog Color";
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Dog();
        System.out.println(animal.color); // Animal Color
    }
}

In Java, we can override methods only, not the variables(data members), so runtime polymorphism or dynamic polymorphism can not be achieved by instance variables or data members.

14. What if the child class does not override the method and we are calling:-

Animal animal = new Dog();
animal.methodName();

In that case, the parent class method will be executed.

Association depicts the relationship between two classes. It is of 2 types:-

  1. Aggregation
  2. Composition

Both Aggregation and Composition represent HAS-A relationships.

Aggregation:- Both Objects can exist independently. Example:- Driver, and Car. Team and Football player.

public class Driver {
   private Car car;
}

public class Team {
   List<Player> players;
}

Composition:- Both objects can not exist independently. One object can not exist without an owner object. Example: An engine can’t exist without a car.

public class Car {
   private Engine engine;
}

1. Association
Association represents a general relationship between two classes. It describes how one class is connected to another.

Characteristics:

  • Bidirectional or Unidirectional.
  • Lifetime Independence: Objects can exist independently.

Example: A Teacher and a Student have an association. A teacher can teach multiple students, and a student can be taught by multiple teachers.

import java.util.List;

class Teacher {
    String name;
    List<Student> students;

    public Teacher(String name, List<Student> students) {
        this.name = name;
        this.students = students;
    }
}

class Student {
    String name;
    List<Teacher> teachers;

    public Student(String name, List<Teacher> teachers) {
        this.name = name;
        this.teachers = teachers;
    }
}

2. Aggregation
Aggregation is a special form of association that represents a “whole-part” relationship.

Characteristics:

  • Weak Relationship: Parts and whole can exist independently.
  • Shared Ownership: Parts can belong to multiple wholes.

Example: A Department can have multiple Professors, but professors can exist independently of the department.

import java.util.List;

class Department {
    String name;
    List<Professor> professors;

    public Department(String name, List<Professor> professors) {
        this.name = name;
        this.professors = professors;
    }
}

class Professor {
    String name;

    public Professor(String name) {
        this.name = name;
    }
}

3. Composition
Composition is a form of aggregation with a strong “whole-part” relationship.

Characteristics:

  • Strong Relationship: Part cannot exist without the whole.
  • Exclusive Ownership: Parts cannot belong to multiple wholes.

Example: A House is composed of Rooms. If the house is destroyed, the rooms no longer exist.

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

class House {
    List<Room> rooms;

    public House() {
        this.rooms = new ArrayList<>();
    }

    public void addRoom(Room room) {
        this.rooms.add(room);
    }

    public void displayHouse() {
        System.out.println("House has the following rooms:");
        for (Room room : rooms) {
            System.out.println(room.name);
        }
    }
}

class Room {
    String name;

    public Room(String name) {
        this.name = name;
    }
}

public class Main {
    public static void main(String[] args) {
        House house = new House();
        Room livingRoom = new Room("Living Room");
        Room kitchen = new Room("Kitchen");

        house.addRoom(livingRoom);
        house.addRoom(kitchen);

        house.displayHouse();
    }
}

Summary:

  • Association: Represents a general relationship where objects can exist independently.
  • Aggregation: Represents a “whole-part” relationship where parts can exist independently.
  • Composition: Represents a strong “whole-part” relationship where parts cannot exist independently.

16. What is the covariant return type?

Covariant return type means return type may vary during overriding. 

Before Java 5 it was not allowed to override any method if the return type is changed in the child class. But now it’s possible only if the return type is subclass type. 

For example:- Assume the return type is Object. Since String is a child class of the Object class therefore while overriding the method we can return a String. Similarly, Dog is a child of the Animal class, therefore while overriding the method instead of the Animal return type, we can return the Dog object.

public class Animal {
    public Object specialProperty() {
        return null;
    }
}
public class Dog extends Animal {
      @Override
    public String specialProperty() {
        return "Covariant type";
    }
}
public class Animal {
    public Animal specialProperty() {
        System.out.println("Animal.specialProperty()");
        return new Animal();
    }
}
public class Dog extends Animal {
    @Override
    public Dog specialProperty() {
        System.out.println("Dog.specialProperty()");
        return new Dog();
    }
}

17. Type promotion | Find the output of the below program.

public class OverloadingExample {
    // public void add(int a, int b) {
    //     System.out.println("OverloadingExample.add() - int, int");
    // }

    public void add(int a, long b) {
        System.out.println("OverloadingExample.add() - int, long");
    }
}

public class Test {
    public static void main(String[] args) {
        OverloadingExample ex = new OverloadingExample();
        ex.add(10, 20); // OverloadingExample.add() - int, long
    }
}
public class OverloadingExample {
    public void add(long a, int b) {
        System.out.println("OverloadingExample.add() - long, int");
    }

    public void add(int a, long b) {
        System.out.println("OverloadingExample.add() - int, long");
    }
}

public class Test {
    public static void main(String[] args) {
        OverloadingExample ex = new OverloadingExample();
        ex.add(10, 20);
    }
}

Now, The below line will give a compile time error:- The method add(long, int) is ambiguous for the type OverloadingExample.

Object Class

1. What methods does the Object class have?

The java.lang.Object class, parent of all has the following methods:-

  1. protected Object clone() throws CloneNotSupportedException:- It creates and returns a shallow copy of this object. To perform a deep copy, we should override the clone() method in the subclass.
  2. public boolean equals(Object obj):- It indicates whether some other object is “equal to” this object. It performs reference checks. For content comparison, we should override it in the subclass.
  3. protected void finalize() throws Throwable:- It is called by the garbage collector on an object when garbage collection determines that there are no more references to the object. We should override this method if we want to perform anything before garbage collection of the object.
  4. public final Class getClass():- Returns the runtime class of an object.
  5. public int hashCode():- Returns the hash code value of the object.
  6. public String toString():- Returns a string representation of the object.
  7. public final void notify()
  8. public final void notifyAll()
  9. public final void wait()
  10. public final void wait(long timeout)
  11. public final void wait(long timeout, int nanons)

2. What is the Equals and Hash code Contract?

The contract between Equals and Hash code says that:-

  • If two Objects are equal according to the Equals(Object o) method then the hashcode for both objects must be the same (integer value).
  • It is not necessary that if we have the same hashcode for 2 objects then those two objects are equals. This is collision and a better hash function prevents this.
  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashcode method must consistently return the same integer.
import java.util.Objects;

public class Employee {
    private Integer id;
    private String name;

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

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Employee other = (Employee) obj;
        return Objects.equals(id, other.id) && Objects.equals(name, other.name);
    }
}

Garbage Collection

1. What is the Garbage Collection and What are its advantages?

  • Garbage collection in Java is an automatic process of looking at heap memory, identifying which objects are in use and which are not, and deleting the unused objects.
  • An in-use object or a referenced object means that some part of the program still maintains a pointer to that object.
  • An unused object, or unreferenced object, is no longer referenced by any part of the program. So the memory used by an unreferenced object can be reclaimed. 
  • The Main Advantage of automatic garbage collection in Java is that it removes the burden of manual memory allocation/deallocation from programmers so that they can focus on problem-solving.

2. Where are objects created in memory? On stack or heap?

Heap. The area where we get this object managed is the heap. Whenever an object is created, it’s always stored in the Heap space and stack memory contains the reference to it. Stack memory only contains local primitive variables and reference variables (pointing to objects) in heap space.

Thus all Java objects are always created on the heap in Java. Example:- 

CustomeObj s1 = new CustomeObj();

3. Which part of the memory is involved in Garbage Collection? Stack or Heap?

Garbage collection is always performed on the Heap memory. No other memory regions are involved.

Garbage Collection

4. Who manages the garbage collection process?

  • The JVM controls the garbage collection.
  • It is the one who decides when to run the Garbage Collector.
  • JVM runs the Garbage Collector when it realizes that the memory is running low.
  • We can request the Garbage collection to happen from within the Java program by either using System.gc(); or Runtime.getRuntime().gc(); but there is no guarantee that this request will be taken care of by JVM.

5. How can the Garbage Collection be requested?

There are two ways in which we can request the JVM to execute the Garbage Collection:-

  • System.gc();
  • Runtime.getRuntime().gc(); Runtime class is a Singleton for each Java main program. The method getRuntime() returns a singleton instance of the Runtime class. The method gc() can be invoked using this instance of Runtime to request the garbage collection.
public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        test = null;

        // System.gc();
        // Or
        Runtime.getRuntime().gc();

        System.out.println("Main method is done");
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Test.finalize()");
    }
}

6. When does an object become eligible for garbage collection?

  • An object becomes eligible for Garbage Collection when no live thread can access it. 
  • Objects not in use (or unreferenced objects) are those objects that are not needed by the Java program anymore, no part of the Java program is pointing to that object. So, these unused objects can be cleaned in the GC (garbage collection) process, and memory used by an unreferenced object can be reclaimed.

7. What are the different ways to make an object eligible for garbage collection when it is no longer needed??

  • Set all available object references to null 
  • Make the reference variable refer to another object 
  • Creating Islands of Isolation
public class Test {
    public static void main(String[] args) {
        Test test1 = new Test();
        Test test2 = new Test();
        
        // Make the reference variable to refer to another object 
        test1 = test2;
        
        // System.gc();
        // Or
        Runtime.getRuntime().gc();
        
        System.out.println("Main method is done");
    }
    
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Test.finalize()");
    }
}

How to create Islands of Isolation?

public class Test {
    Test test3;

    public static void main(String[] args) {
        Test test1 = new Test();
        Test test2 = new Test();

        test1.test3 = test2;
        test2.test3 = test1;

        test1 = null;
        test2 = null;

        // System.gc();
        // Or
        Runtime.getRuntime().gc();

        System.out.println("Main method is done");
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Test.finalize()");
    }
}

8. What is the purpose of overriding the finalize() method?

  • Finalize method in Java also called finalizer, is a method defined in java.lang.Object
  • It’s called by a Garbage collector just before collecting any object that is eligible for garbage collection. 
  • Thus finalize() method provides a last chance to object to do cleanup and free any remaining resources.

9. How many times does the garbage collector call the finalize() method for an object?

Only once.

10. What is the responsibility of the Garbage Collector?

  • The garbage collector frees the memory occupied by the unreachable objects during the Java program by deleting these unreachable objects.
  • It ensures that the available memory will be used efficiently, but does not guarantee that there will be sufficient memory for the program to run. (When not it gives an out-of-memory exception).

11. Is the garbage collector a foreground or background thread?

Garbage Collector is a background/daemon thread.

12. What is a daemon thread?

  • A daemon thread runs behind the application.
  • It is started by JVM.
  • The daemon thread is stopped by JVM when all non-daemon / foreground threads stop. We don’t need to manually stop them.

13. How does garbage collection work?

Garbage collection works in 2 steps:-

  • Marking: unreferenced objects in the heap are identified and marked as ready for garbage collection.
  • Deletion (Normal deletion) / Deletion + Compaction: In this step objects marked previously are deleted.
    • Memory is compacted after garbage collection deletes the object so that the remaining objects are in contiguous blocks at the start of heap memory.
    • This compaction makes it easier to allocate memory sequentially after a chunk of the allocated memory area for new objects in the heap.

 14. Can you name commonly used Oracle’s JVM & which GC strategy is used by it?

HotSpot” is the most commonly used JVM by Oracle. All HotSpot’s GC implements a generational GC strategy ie. It categorizes the objects by age. Initially, the objects will have an age of 0, after 1 garbage collection their age will become 1, after 2nd garbage collection their age will become 2, and so on.

15. Why do Most GCs use the Generational GC Strategy?

The reason for most GC using the Generational GC Strategy is that most of the objects are short-lived and hence die shortly just after memory allocation. This is called the “DIE YOUNG” or Infant Mortality (infant refers to object here) strategy.

16. Explain the Generational GC strategy and Hotspot Heap structure used by these GCs.

The heap area is divided into 3 sections i.e. 

  • Young Generation: This is where new objects are allocated. It is divided into:
    • Eden Space: Most new objects are allocated here.
    • Survivor Spaces (S0 and S1): Objects that survive garbage collection in the Eden space are moved here.
  • Old Generation (Tenured Generation): Objects that have survived multiple garbage collection cycles in the young generation are moved here. These objects are typically long-lived.

Metaspace: From Java 8 onwards, the Permanent Generation (PermGen) was replaced by Metaspace. Metaspace stores class metadata and is not part of the heap.

Java Heap Memory Structure

The garbage collection process can be divided into the following steps:-

  1. New object allocation. It is always allocated to the young generation. The young generation is divided into the Eden, survivor-space-0 (s0), and survivor-space-1 (s1).
  2. When Eden space fills up then Minor GC is triggered by the JVM.
  3. In minor GC unreferenced objects are deleted from Eden space and referenced objects are moved to the first survivor space (s0). The age of survived objects increased to 1 from 0. Eden space becomes empty.
  4. When again Eden space is filled up then again minor GC is triggered:-
    1. Unreferenced objects from Eden space are deleted.
    2. Referenced objects are again moved to survivor space but this time to another / second space (S1) marking their age as 1.
    3. S0 (first survivor space) is again scanned in this cycle and unreferenced objects are deleted and referenced objects from S0 are also moved from S0 to S1 and age is incremented from 1 to 2.
    4. Now Eden and S0 are empty. S1 has objects with ages 1 or 2.
Young Generation - Java Heap Memory
  1. When again eden space is filled up then again minor GC is triggered. This time S1 has differently aged objects but S0 is empty.:
    1. Unreferenced objects from Eden space are deleted.
    2. Referenced objects are moved from Eden to S0 and S1 to S0 and ages are increment from 0 to 1 (Eden space), 1 to 2, and 2 to 3.
    3. Now Eden and S1 are empty for the next cycle and S0 has objects age ranging from 1 to 3.
  2. Promotion to Old Generation:- When aged objects in S0 or S1 reach a certain threshold age suppose 8 then during minor GC these aged objects will move from the young generation to the old generation.
  3. When the old gen is full then Major GC (or Full GC) is performed on the old generation space only.

Key Point:- Minor GC happens only in the young generation and is relatively fast. The old generation always triggers Major GC when the old generation is filled up completely and it can be more time-consuming. 

This generational approach is based on the observation that most objects die young, so frequent collections in the young generation can reclaim memory quickly and efficiently.

17. Does the object move from old gen to metaspace?

No, objects do not move from the old generation to Metaspace. Objects in the old generation remain there until they are either collected by a Major GC or the application terminates. Metaspace is managed separately and is used exclusively for class metadata, not for regular object instances.

18. What is Metaspace and how it is different from the PermGen?

Metaspace is a non-heap memory area that was introduced in Java 8, replacing the Permanent Generation (PermGen). It is used to store metadata such as class definitions, method data, and field data.

Here are some key points about Metaspace:-

  • Dynamic Resizing: Unlike PermGen, which had a fixed maximum size, Metaspace can dynamically resize its size depending on the application’s demands. This reduces the chance of encountering an OutOfMemoryError.
  • Garbage Collection: The garbage collector triggers the cleaning of dead classes once the class metadata usage reaches its maximum metaspace size. This makes the garbage collection process more efficient compared to PermGen.
  • Native Memory: Metaspace uses native memory provided by the underlying operating system. This is different from PermGen, which used contiguous Java heap memory.
  • JVM Options: There are new JVM options for tuning Metaspace, such as MetaspaceSize, MaxMetaspaceSize, MinMetaspaceFreeRatio, and MaxMetaspaceFreeRatio.

19. What is the “Stop the world” concept in GC?

The “stop-the-world” concept in garbage collection refers to a pause in the execution of an application while the garbage collector runs. During this pause, all application threads are stopped, and no new objects are allocated on the heap. This ensures that the garbage collector can safely identify and reclaim unused objects without interference from the running application.

While “stop-the-world” pauses can ensure efficient garbage collection, they can also impact application performance, especially if they occur frequently or take a long time to complete. Modern JVMs, like HotSpot, use various techniques to minimize these pauses and improve overall performance.

Serialization Deserialization and Externalization Interview Questions

1. What is Serialization?

  • Serialization is the conversion of a Java object into a static stream (sequence) of bytes, which we can then save to a database or transfer over a network.
  • Classes that are eligible for serialization need to implement a special marker interface, Serializable. The JVM allows special privileges to the class that implements the Serializable Interface.
  • Byte stream is platform-independent. This means that once you have a stream of bytes you can convert it into an object and run it on any kind of environment.
Serialization

A class to be serialized successfully, two conditions must be met: −

  • The class must implement the java.io.Serializable interface.
  • All of the fields in the class must be serializable. If a field is not serializable, it must be marked transient. The static fields belong to a class (as opposed to an object) and are not serialized.

Way to convert an object into a stream of Bytes – “ObjectOutputStream” which consists of a method “writeObject“.
Way to convert a stream of Bytes into an object – “ObjectInputStream” which consists of a method readObject().

FileOutputStream fileOut = new FileOutputStream("/tmp/employee.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();

2. What is Deserialization?

Deserialization is precisely the opposite of serialization. With deserialization, you start with a byte stream and re-create the object you previously serialized in its original state. However, you must have the definition of the object to successfully re-create it.

FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
Employee e = (Employee) in.readObject();
in.close();
fileIn.close();
public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;

    private Integer id;
    private String name;

      // setter, getter, tostring
      // don’t declare constructor
}
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Employee emp = new Employee();
        emp.setId(1);
        emp.setName("Jerry");

        // serialization
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("file.ser"));
        objectOutputStream.writeObject(emp);
        objectOutputStream.close();

        // de-serialization
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("file.ser"));
        Employee e = (Employee) objectInputStream.readObject();
        objectInputStream.close();

        System.out.println(e);
    }
}

Using try-with-resources the same can be written as:-

public class Test {
    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.setId(1);
        emp.setName("Jerry");

        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("emp.ser"))) {
            oos.writeObject(emp);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("emp.ser"))) {
            Employee e = (Employee) ois.readObject();
            System.out.println(e);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

In case the file contains multiple objects:-

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("emp.ser"))) {
    while (true) {
        try {
            Employee e = (Employee) ois.readObject();
            System.out.println(e);
        } catch (EOFException e) {
            // EOFException is thrown when the end of the file is reached
            break;
        }
    }
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}

3. What is Serial Version UID?

The JVM associates a version (long) number with each serializable class. we use the serialVersionUID attribute to remember versions of a Serializable class to verify that a loaded class and the serialized object are compatible.

Most IDEs can generate this number automatically, and it’s based on the class name, attributes, and associated access modifiers. Any changes result in a different number and can cause an InvalidClassException.

If a serializable class doesn’t declare a serialVersionUID, the JVM (which is platform-dependent) will generate one automatically at run-time. However, it’s highly recommended that each class declares its serialVersionUID, as the generated one is compiler-dependent and thus may result in unexpected InvalidClassExceptions.

4. How does Java serialization work?

Java serialization uses reflection to scrape all necessary data from the object’s fields, including private and final fields. If a field contains an object, that object is serialized recursively. Even though you might have getters and setters, these functions are not used when serializing an object in Java.

5. What is the use of a transient variable?

If any variable is transient then JVM will ignore that value and not store them during the serialization process. Hence after deserialization, we will get null for that variable.

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;

    private Integer id;
    private transient String name;

      // setter, getter, tostring
      // don’t declare constructor
}

Now rerun the same code. This time value for the “name” variable will not be stored in the file, the JVM will ignore it.

6. Which fields are allowed and which are not allowed during serialization?

Allowed: private, and final
Not allowed: static and transient are not allowed completely.

7. How does Java deserialization work?

When deserializing a byte stream back to an object it does not use the constructor. It simply creates an empty object and uses reflection to write the data to the fields. Just like with serialization, private and final fields are also included.

8. What is Externalization?

Externalization in Java is used whenever you need to customize the serialization mechanism.

In serialization, the Java Virtual Machine is completely responsible for the process of writing and reading objects. This is useful in most cases, as the programmers do not have to care about the underlying details of the serialization process. However, the default serialization does not protect sensitive information such as passwords and credentials, or what if the programmers want to secure some information during the serialization process?

Thus externalization comes to give the programmers full control in reading and writing objects during serialization. JVM has no control over it. The complete serialization control goes to the application.

Based on our requirements, we can serialize either the whole data field or a piece of the data field using the Externalizable interface which can help to improve the performance of the application.

Externalizable interface internally extends Serializable interface:- public interface Externalizable extends java.io.Serializable

The Externalizable interface is not a marker interface like the Serializable interface. It provides two abstract methods:-

  • void readExternal(ObjectInput inStream):- We call the readExternal() method when we want to read an object’s fields from a stream. We need to write logic to read an object’s fields inside the readExternal() method. The readExternal() method throws an IOException when an I/O error occurs. If the class of the object being restored is not found, ClassNotException will be thrown.
  • void writeExternal(ObjectOutput outStream):- The writeExternal() method is used when we want to write an object’s fields to a stream. We need to write the logic to write data fields inside the writeExternal() method. This method can throw an IOException when an I/O error occurs.
public class Employee implements Externalizable {
    private static final long serialVersionUID = 1L;

    private Integer id;
    private String name;

      // no-arg constructor is required for externalization

      // getter, setter, and toString

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        // write only those fields which you want to write
        out.writeInt(id);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        id = in.readInt();
    }
}
// no change in this code
public class Test {
    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.setId(10);
        emp.setName("ABC");

        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("emp.ser"))) {
            oos.writeObject(emp);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("emp.ser"))) {
            Employee e = (Employee) ois.readObject();
            System.out.println(e);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

In Java, when using the Externalizable interface, it is necessary to provide a public no-arg constructor. This is because the Externalizable interface requires the class to implement the methods writeExternal(ObjectOutput out) and readExternal(ObjectInput in). During deserialization, the readExternal method relies on creating an instance of the class using the no-arg constructor.

9. How serialization works?

  • In the serialization process, JVM first checks for the Externalizable interface. If objects support the Externalizable interface, JVM serializes objects using the writeExternal() method.
  • If objects do not support Externalizable but implement Serializable, objects are stored using ObjectOutputStream.
  • For serializable objects, JVM serializes the only instance variables (not static variables) that are not declared with the transient keyword.
  • For externalizable objects, we have full control over what pieces of data have to serialize and what to not serialize.
  • The order of reads must be the same as the order of writes.

10. Serializable vs Externalizable?

PropertiesSerializableExternalizable
Interfaces UsedA serializable interface is used to implement serialization.An externalizable interface is used to implement Externalization. 
Marker Interface?Serializable is a marker interface i.e. it does not contain any method.The externalizable interface is not a marker interface and thus it defines two abstract methods writeExternal() and readExternal().
Who Controls Serialization?The Serializable interface passes the responsibility of serialization to JVM and the programmer has no control over serialization, and it is a default algorithm.The externalizable interface provides all serialization responsibilities to the programmer and hence JVM has no control over serialization.
PerformanceSerialization using a serializable interface has bad performance.Serialization using an externalizable interface has better performance.
No-arg Constructor Required?Default serialization does not require any no-arg constructor.A public no-arg constructor is required while using an Externalizable interface.
Incorporate changes with ease.It is hard to analyze and modify class structure because any change in structure may break Serialization.It is relatively easy to analyze and modify class structure because of complete control over serialization logic.
Transient required?Transient keywords play an important role here.Transient keywords won’t play any role.
Is the partial saving of an object possible?Using a serializable interface we save the complete object to a file, and it is not possible to save part of the object without using the transient keyword.Yes. Based on our requirements we can save either the total object or part of the object.

11. What is a Java deserialize vulnerability?

A Java deserialize vulnerability is a security vulnerability that occurs when a malicious user tries to insert a modified serialized object into the system in order to compromise the system or its data. Think of an arbitrary code execution vulnerability that can be triggered when deserializing a serialized object.

A serialized object in Java is a byte array with state information. If you look at a stored serialized object with a hex editor, you can enclose and manipulate the information quickly.

Thus if an application accepts serialized objects, it is relatively easy to tamper with the values. By altering the serialized objects, we can create invalid objects, alter the data’s integrity, or worse.

12. How to prevent a Java deserialize vulnerability?

The best way to prevent a Java deserialize vulnerability is to prevent Java serialization overall. If your application doesn’t accept serialized objects, it can’t hurt you.

However, if you do need to implement the `serializable` interface due to inheritance, you can override the readExternal()/readObject(), as seen below, to prevent actual deserialization.

@Override
public void readExternal(ObjectInput in) throws IOException {
    throw new java.io.IOException("This app does not allowed deserialization");
}
  • Do Not Accept Serialized Objects from Untrusted Sources.
  • Keep your .ser file in a secure location. Keep it restricted and only authorized persons can access that file.
  • Don’t store passwords like sensitive info as serialization is prone to vulnerabilities.

13. What inheritance rules apply with Serialization Deserialization of child or parent class?

  • If the superclass is serializable, then the subclass is automatically serializable. If the superclass is Serializable, then by default, every subclass is serializable. Hence, even though the subclass directly doesn’t implement the Serializable interface (and if its superclass implements Serializable), then we can serialize the subclass object.
  • If a superclass is not serializable, then the subclass can still be serialized. Even though the superclass doesn’t implement a Serializable interface, we can serialize subclass objects if the subclass itself implements a Serializable interface. So we can say that to serialize subclass objects, the superclass need not be serializable. But what happens with the instances of superclass during serialization in this case? The below question explains this.

14. What happens when a class is serializable, but its superclass is not?

  • Serialization: At the time of serialization, if any instance variable inherits from the non-serializable superclass then:-
    • The JVM ignores the original values of any instance variables inherited from the non-serializable superclass.
    • Instead, it saves the default values of these variables to the file.
  • Deserialization: At the time of de-serialization, if any non-serializable superclass is present then:-
    • The JVM executes the instance control flow in the non-serializable superclass.
    • To do this, the JVM invokes the default (no-argument) constructor of the non-serializable superclass.
    • This means that any initialization logic in the superclass’s constructor will be executed, and the instance variables will be set to their default values or initialized as per the constructor logic.

Multithreading Interview Questions

1. What is MultiTasking and Its types?

Performing multiple tasks at one time. There are 2 types of multitasking:-

  • Process-based multitasking
  • Thread based multitasking

2. What is Multi threading & how is it different from multitasking?

  • Multithreading is a specialized form of multitasking.
  • Process-based multitasking is executing several tasks simultaneously where each task is a separate independent process is Process-based multitasking. For example, process-based multitasking enables you to run the Java IDE at the same time that you are using a text editor and visiting a website using Chrome.
  • Thread-based multitasking is executing several tasks simultaneously where each task is a separate independent part of the same program (called Thread). For instance, JUnit uses threads to run test cases in parallel. As an application, you can have computer games. You see objects in games like cars, motorbikes, etc. They are just threads that run in the game application.
  • Thus, process-based multitasking deals with the “big picture,” and thread-based multitasking handles the details.

3. Which is better process-based multitasking or thread-based multitasking and why?

  • Thread-based multitasking is better.
  • Multitasking threads require less overhead than multitasking processes. 
  • Processes are heavyweight tasks that require their own separate address spaces. 
  • Threads, on the other hand, are lighter weight. They share the same address space and cooperatively share the same heavyweight process. 
  • Interprocess communication is expensive and limited. Context switching from one process to another is also costly.
  • Interthread communication is inexpensive, and context switching from one thread to the next is lower in cost.
  • While Java programs make use of process-based multitasking environments, process-based multitasking is not under Java’s direct control. However, multithreaded multitasking is. 

4. What is a Thread?

  • Threads are lightweight processes within a process.
  • Java creates threads by using a “Thread Class”.
  • All Java programs have at least one thread, known as the main thread, which is created by the Java Virtual Machine (JVM) at the program’s start, where the main() method is invoked by the main thread.
public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()); // main
    }
}

5. Types of Thread in Java?

There are two types of thread – user thread and daemon thread.

  1. User Threads are created by developers. E.g. Main thread. All threads created inside the main method (child threads of the main thread) are non-daemon / user threads by default because the main thread is non-daemon.
  2. A daemon thread is a low-priority thread (in the context of JVM) that runs in the background to perform tasks such as garbage collection (GC) etc. They do not prevent the JVM from exiting even if the daemon thread itself is running. JVM terminates itself when all user threads (non-daemon threads) finish their execution, JVM does not care whether the Daemon thread is running or not, if JVM finds a running daemon thread (upon completion of user threads), it terminates the thread, and after that shutdown itself.

6. How to create a user thread in Java?

Thread implementation in Java can be achieved in two ways:-

  1. Extending the java.lang.Thread class.
  2. Implementing the java.lang.Runnable Interface.
// using Thread class
public class ThreadClass extends Thread {
    public void run() {
        System.out.println(Thread.currentThread().getName());
        for (int i = 0; i < 3; i++) {
            System.out.println("Child Thread");
        }
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        ThreadClass threadClass = new ThreadClass();
        threadClass.start();
    }
}

When the JVM starts, it creates a thread called “Main”. Your program will run on this thread unless you create additional threads yourself. The first thing the “Main” thread does is to look for public static void main (String args[]) method and invoke it. That is the entry point to your program. If you create additional threads in the main method those threads would be the child threads of the main thread.

If the class implements the Runnable interface and we call the run() method then it will be a normal method call and it won’t start another thread, instead, it will be executed by the main thread.

public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        RunnableDemo threadClass = new RunnableDemo();

        // wrong
        threadClass.run(); // It is normal method call
        // it won’t start another thread
    }
}
// using Runnable interface
public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
        for (int i = 0; i < 3; i++) {
            System.out.println("Child Thread");
        }
    }
}

To start the thread we have to use the Thread class and create its object by passing the Runnable implementation class.

public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        thread.start();
    }
}

7. Difference between creating a thread from the Thread class and Runnable Interface?

Thread class contains multiple pre-defined methods which can provide help during multithreading. Whereas the Runnable interface contains only the run() abstract method. If we want to perform multiple things on the thread then extending it from the Thread class will provide much help else we can implement the Runnable interface.

Since the Java class can’t extend more than one class therefore if we extend it from the Thread class then it can’t extend any other class. If the class needed to be extended from some other class in that case we can implement the Runnable interface.

8. Why is the output not in order?

If multiple Threads are waiting to execute then which Thread will execute 1st is decided by “Thread Scheduler” which is part of JVM hence it is vendor-dependent and that is why we can’t expect exact execution order/output So whenever situations come to multithreading the guarantee in behaviour is very less. We can tell the possible output but not the exact one.

9. How to make a user thread into a Daemon thread?

  • Make a user thread to Daemon by using the setDaemon() method of thread class.
  • This method is used for making a user thread into a Daemon thread or vice versa. For example, if I have a user thread t then t.setDaemon(true) would make it a daemon thread. On the other hand, if I have a daemon thread td then calling td.setDaemon(false) would make it a normal thread(user thread/non-daemon thread).
  • public boolean isDaemon(): This method is used for checking the status of a thread. It returns true if the thread is Daemon else it returns false.
  • The setDaemon() method can only be called before starting the thread. This method would throw IllegalThreadStateException if you call this method after the Thread.start() method.
  • The main difference between the daemon thread and user threads is that the JVM does not wait for the daemon thread before exiting while it waits for user threads, it does not exit until all the user threads finish their execution.
public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());

        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        System.out.println(thread.isDaemon());

        thread.setDaemon(true);
        System.out.println(thread.isDaemon());
        thread.start();
    }
}

10. Difference between t.start() and t.run()?

Thread thread = new Thread(runnableDemo);
thread.start();
thread.run();
  • In the case of t.start() a new Thread will be created which is responsible for the execution of the run() method.
  • But in the case of t.run() no new Thread will be created and the run() method will be executed just like a normal method by the main Thread.
  • If we are replacing thread.start() with thread.run() then the entire output is produced by only the main Thread.

11. What are the tasks of the start() method?

  • Register the thread with the thread scheduler. So you just tell what a child thread should do, and when and how it will be scheduled will be handled by the scheduler.
  • Call the run() method.

12. What if we do not override the run() method?

Thread class run method will be called and we won’t get any output. It is useless as we are saying please create a thread and that thread will do nothing for us.

13. Can we overload the run() method?

Yes, we can overload the run() method and it will be a simple/normal method for that class. The thread will not be called due to the overloaded run() method. Therefore overloading the run() method has no meaning because the start() method will always call run() without any params method only.

14. Can we override the start() method?

  • Yes, we can override the start() method.
  • If we override the start() method in our custom class then no initializations will be done by the thread class for us.
  • The run() method is also not called.
  • Even a new thread is not created.

15. Can we restart the same thread again?

No. We will get java.lang.IllegalThreadStateException. We can not do this indirectly too with super.start() in the run method.

public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()); // main
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
        });
        thread.start(); // thread-0
        thread.start(); // java.lang.IllegalThreadStateException
    }
}
public class Test {
   public static void main(String[] args) {
       Thread thread = new Thread(() -> {
           System.out.println(Thread.currentThread().getName());
           // java.lang.IllegalThreadStateException
           Thread.currentThread().start(); 
       });
       thread.start();
   }
}

16. How to set the name of a thread?

Using setName() method.

public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()); // main
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
        });
        thread.setName("Test-Thread");
        thread.start(); // Test-Thread

    }
}

We can change the name of the thread even after starting the thread.

thread.start(); 
thread.setName("new name");

17. What is a Thread Priority?

Every thread in Java has some priority from 1 (min) to 10 (max).

  • Who uses this priority? Thread schedulers use this while allocating CPU. The highest will be given CPU first.
  • What is the default priority of the main thread? 5
  • What will be the priority of its child thread? They inherit from their parents.
  • How to set the priority of a thread? Before calling start() use the setPriority() method:- thread.setPriority(7);
  • What will happen if we set priority above 10? java.lang.IllegalArgumentException
  • Will we get any exceptions if we set thread priority after starting of the thread? No. Setting thread priority or thread name after starting the thread won’t affect program execution. Since the thread is already started therefore these changes won’t be considered.

18. What do you mean by synchronized?

In Java, synchronized is a keyword used to control access to shared resources in a multi-threaded environment. When a method or a block of code is marked as synchronized, it means that only one thread can execute that method or block at any given time. Other threads attempting to execute the same method or block will be blocked until the previous thread finishes its execution and releases the lock.

In the context of synchronization, a “lock” is associated with the synchronized method or block. When a thread enters a synchronized method or block, it acquires the lock, and no other thread can enter any other synchronized method or block associated with the same lock until the first thread releases it.

This ensures that critical sections of code, where shared resources are accessed or modified, are protected from concurrent access, preventing race conditions and maintaining data consistency.

Java String Interview Questions

1. Why string is immutable in Java?

  1. One string literal is used in multiple string objects, if it is not made immutable then modification at one place will change the string at other places. 
  2. The string pool requires the string to be immutable otherwise shared references can be changed from anywhere.
  3. Security, because the string is shared on different areas like file systems, networking connections, and database connections, having an immutable string, allows us to be secure and safe because no one can change the reference of the string once it gets created.

1. What are the different ways to create string objects?

String objects can be created in two ways:-

  • Using the new operator.
  • Using double-quotes.
String S2 = "program";

When the String is created with the double quotes, JVM searches it for in the string pool; if the same value is found, it returns the reference to that String else creates a new object with the new value provided.

String S1 = new String("code");

The following steps are involved in the above statement:-

  1. Step 1: The string literal “code” is checked in the string pool.
    1. If “code” is not already in the string pool, it is added to the pool.
    2. If “code” is already in the pool, no new object is created in the pool.
  2. Step 2: A new String object is created in the heap memory that references the string “code” in the pool.

2. What is a string constant pool?

The memory space allocated in the heap memory to store the string literals is called the string constant pool. No two string objects can have the same value in a string constant pool. 

3. Why is Java provided with String constant pool as we can store the objects in heap memory?

String constant pool provides the facility of reusability of the existing string objects. When a new string object is created using the string literals, then JVM first checks in the pool if this String already exists or not. If it exists, then it will reference the existing String rather than creating a new object. This will help in speeding up the application and also help in saving the memory as no two objects will have the same content.

4. How many objects are created in the following code snippet?

String S1 = new String("code");

Most of us say 1 but It’s wrong, it’s 2.
1 in heap, 1 in pool.
The following steps are involved in the above statement:-

  1. Step 1: The string literal “code” is checked in the string pool.
    1. If “code” is not already in the string pool, it is added to the pool.
    2. If “code” is already in the pool, no new object is created in the pool.
  2. Step 2: A new String object is created in the heap memory that references the string “code” in the pool.

5. How many objects are created in the following code snippet?

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

Two objects are created in the above code snippet:-

  • When String s1 = "code"; is executed, the string literal “code” is placed in the string pool if it is not already there. This creates one object in the string pool.
  • When String s2 = new String("code"); is executed, a new String object is created in the heap memory. This new object references the string “code” in the string pool because the string pool already contains “code” literal.

6. How many objects are created in the following code snippet?

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

Three objects will be created as follows:-

  • When String s1 = new String("code"); is executed, the string literal “code” is placed in the string pool if it is not already there. This creates one object in the string pool
  • When String s1 = new String("code"); is executed, a new String object is created in the heap memory. This new object references the string “code” in the string pool.
  • When String s2 = new String("code"); is executed, another new String object is created in the heap memory. This new object also references the string “code” in the string pool.

7. How to compare two strings in Java??

We can compare two strings using the equals() method. Another method is to use the equal operator (==) but we should try to avoid it to compare two strings.

The reason behind this:- the equals() method compares the values of the two strings, whereas the equal (==) operator compares the reference in the memory.

String S1 = "code";
String S2 = new String("code");
System.out.println(S1==S2); // returns false 
System.out.println(S1.equals(S2)); // return true

8. What does the string intern() method do?

The task of the intern() method is to put String (which is passed to the intern method) into the string constant pool.

When the intern method is invoked, if the String constant pool already contains a string equal to the String object as determined by the equals(Object) method, the String from the pool is returned.

Otherwise, the String object is added to the pool, and a reference to the String object is returned.

public class Test {
    public static void main(String[] args) {
        String s1 = "Code";
        String s2 = new String("Code");
        String s3 = s2.intern(); // it is reference of s1

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

9. Why is the string made immutable in Java?

Immutable means unmodifiable or unchangeable.

Security is the major reason why strings in Java are made to be immutable. Strings in Java can be used to access data sources like files, databases, or even objects found across networks. Even sometimes, strings store passwords and usernames, which can’t be modified once created.

10. Write a code in Java to prove that String objects are immutable.

public class Test {
    public static void main(String[] args) {
        String s1 = "Code";
        System.out.println(s1.hashCode());

        String s2 = s1 + "Java";
        System.out.println(s2.hashCode());
    }
}

11. How does substring work in Java or how does substring create memory leaks in Java?

String string = "java"; // ["j","a","v","a"]; count=4, offset=0
String subString = string.substring(1,3);
// startIndex inclusive, endIndex exclusive
System.out.println(subString); // av

We all know that String in Java is a sequence of characters. The string is internally represented by an array of characters, when a new String object is created, it has the following fields.

  • char value[ ] – Array of characters
  • int count – Total characters in the String
  • int offset – Starting index offset in the character array

When we take a substring from the original string, a new string object will be created in the constant pool or in the heap. The value[ ] array will be shared among two String objects.

Till Java 6:-

String bigString = new String(new byte[100000])

The above String already occupies a lot of memory in the heap. Now consider the scenario where we need the first 2 characters from bigString.

String substr = bigString.substring(0, 2)

Now we don’t need the original String.

bigString = null

We might think that bigString object will be Garbage collected as we made it null but our assumption is wrong. When we call substring(), a new String object is created in memory. But still, it refers to the char[ ] array value from the original String. This prevents bigString from the Garbage collection process and we are unnecessarily storing 100000 bytes in memory (just for 2 characters).

If the original string is very long and has an array of size 1 GB then no matter how small a substring is, it will hold a 1 GB array. This will also stop the original string from being garbage collected, in case it doesn’t have any live reference. This is a clear case of memory leak in Java, where memory is retained even if it is not required. That’s how the substring method creates memory leaks.

Every time substring() returns a new String which is not exactly true since it is backed by the same character array.

To prevent this we can use the intern() method of the substring, which will then fetch an existing string from the pool and add it if necessary. Since the string in the pool is a real string it only takes space as much as it requires.

String substr = bigString.substring(0, 2).intern()

Changes After Java 6

Starting from Java 7, the implementation of the substring method was changed to avoid this memory leak issue. In Java 7 and later versions, the substring method creates a new char[ ] array containing only the characters of the substring, rather than sharing the original array. This ensures that the original large array can be garbage collected if there are no other references to it.

In Summary:-

  • Java 6 and Earlier: substring shares the original char[ ] array, potentially causing memory leaks.
  • Java 7 and Later: substring creates a new char[ ] array, preventing memory leaks.
String s1 = "Java"; 
s1.concat("Code"); 
System.out.println(s1);

It prints “Java” instead of “JavaCode” because the concat method in Java does not modify the original string. Instead, it creates a new string. Strings in Java are immutable, meaning their value cannot be changed once created.

To get the result “JavaCode”, you need to assign the result of concat back to s1:

String s1 = "Java";
s1 = s1.concat("Code");
System.out.println(s1);  // This will print "JavaCode"

All methods in the String class that modify the string content actually creates a new string rather than modifying the existing one, because strings in Java are immutable. Here are some commonly used methods that exhibit this behavior:

  1. concat(String str): Concatenates the specified string to the end of this string.
String s1 = "Hello";
String s2 = s1.concat(" World");
// s2 is "Hello World", s1 is still "Hello"
  1. replace(char oldChar, char newChar): Returns a new string resulting from replacing all occurrences of oldChar with newChar.
String s = "Java";
s = s.replace('a', 'o');
// s is now "Jovo"
  1. replaceAll(String regex, String replacement): Replaces each substring of this string that matches the given regular expression with the given replacement.
String s = "ababab";
s = s.replaceAll("ab", "cd");
// s is now "cdcdcd"
  1. substring(int beginIndex): Returns a new string that is a substring of this string.
String s = "Hello World";
String sub = s.substring(6);
// sub is "World", s is still "Hello World"
  1. toUpperCase(): Converts all of the characters in this string to uppercase and returns a new string.
String s = "hello";
s = s.toUpperCase();
// s is now "HELLO"
  1. toLowerCase(): Converts all of the characters in this string to lowercase and returns a new string.
String s = "HELLO";
s = s.toLowerCase();
// s is now "hello"
  1. trim(): Returns a new string with all leading and trailing whitespace removed.
String s = "Hello";
s = s.trim();
// s is now "Hello"
  1. substring(int beginIndex, int endIndex): Returns a new string that is a substring of this string.
String s = "Hello World";
String sub = s.substring(0, 5);
// sub is "Hello", s is still "Hello World"

These methods highlight the immutability of Java String objects and demonstrate how each method creates a new string rather than altering the original one.

Java Coding Questions

1. Check for String Rotation
Problem: 796. Rotate String

String 1 = program
String 2 = ogrampr

Left Rotation
Right Rotation

Given a string = “program”
The left rotation of the string with 2 characters is = “ogrampr”
The right rotation of string with 2 characters is “amprogr”

To find if one string s2 is the rotation of another string s1:- concatenate s1 with itself. s2 is a rotation of s1 if and only if it is a substring of the rotated string. In Java we can either use string contains or indexOf to check for substring.

If we concat “program” with itself then it becomes “programprogram”, now we can search for substring “ogrampr”. It exists therefore it is a rotation.

public class Test {
    public static void main(String[] args) {
        String originalString ="program";
        String toBeChecked1 = "ogrampr";
        String toBeChecked2 = "gramopr";
        System.out.println(checkForRotation(originalString, toBeChecked1));
        System.out.println(checkForRotation(originalString, toBeChecked2));
    }

    private static boolean checkForRotation(String originalString, String toBeChecked) {
        String concatenatedString = originalString + originalString;
        return concatenatedString.contains(originalString);
    }
}

2. Find String Rotation

Given a string size = 1, write a program to perform the following operations on a string.

Left (Or anticlockwise) rotate the given string by r elements (where r <= 1)
Right (Or clockwise) rotate the given string by r elements (where r <= 1).

Left Rotation
String = program
l = 2
output = ogrampr

Right Left Rotation
String = program
r = 3
output = ramprog

public class Test {
    public static void main(String[] args) {
        String string = "program";
        int rotateBy = 4;
        System.out.println("Original: " + string);
        System.out.println(rotateString(string, rotateBy, "left"));
        System.out.println(rotateString(string, rotateBy, "right"));
    }

    private static String rotateString(String string, int rotateBy, String rotationType) {
        if (rotationType.equalsIgnoreCase("right")) {
            int partition = string.length() - rotateBy;
            return string.substring(partition) + string.substring(0, partition);
        }
        return string.substring(rotateBy) + string.substring(0, rotateBy);
    }
}

Output:-

3. Find Duplicate Characters From String

Use Map. It gives O(n) time complexity and O(k) space complexity where k is the unique characters in the string because it store only count. If you use two sets then the space complexity will be O(n) in the worst case where all characters are unique.

public class Test {
    public static void main(String[] args) {
        String string = "Know Program";
        System.out.println(findDuplicateCharactersUsingMap(string));
    }

    private static Set<Character> findDuplicateCharactersUsingMap(String string) {
        Map<Character, Integer> frequency = new HashMap<>();
        string.chars().forEach(c -> {
            frequency.put((char) c, frequency.getOrDefault((char) c, 0) + 1);
        });
        Set<Character> duplicates = new LinkedHashSet<>();
        frequency.forEach((key, value) -> {
            if (value > 1) {
                duplicates.add(key);
            }
        });
        return duplicates;
    }
}

Using Collections.frequency():-

public class Test {
   public static void main(String[] args) {
       String string = "know program";
       List<String> chars = List.of(string.split(""));
       Set<String> duplicateCharacters = chars.stream()
               .filter(str -> Collections.frequency(chars, str) > 1)
               .collect(Collectors.toSet());
       System.out.println(duplicateCharacters); // [r, o]
   }
}

4. Check if a string is an isogram or not.

An isogram (also known as a “nonpattern word”) is a word or phrase without a repeating letter, meaning each letter appears only once. The problem is to write a program that checks whether a given string is an isogram or not.

Input: “machine”
Output: False (The letter ‘r’ appears more than once)

Input: “programming”
Output: True (All letters are unique)

public class Test {
   public static void main(String[] args) {
       String word = "KnowProgram";
       System.out.println(isIsogram(word));
   }
   private static boolean isIsogram(String word) {
       Set<Character> uniqueChars = new HashSet<>();
       for (int i = 0; i < word.length(); i++) {
           if (!uniqueChars.add(word.charAt(i))) {
               return false;
           }
       }
       return true;
   }
}

Using Java8:-

private static boolean isIsogram(String word) {
    Set<Character> uniqueChars = new HashSet<>();
    return word.chars()
               .mapToObj(c -> (char) c)
               .allMatch(uniqueChars::add);
}

5. Nth Elemenet in Fibonacci using Recursion

Input n = 9
Output = 34

public class Test {
   public static void main(String[] args) {
       int n = 9; 
       System.out.println(fibonacci(n)); // 34
   }
   private static int fibonacci(int n) {
       if (n <= 1) {
           return n; // base case
       } else {
           // general case
           return fibonacci(n - 1) + fibonacci(n - 2);
       }
   }
}

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

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