Skip to content

Java Common Questions

Core Java Concepts

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

JDK JRE JVM


2. Explain Java Memory Model

JVM Memory Areas


3. What is Garbage Collection?

// GC automatically reclaims memory from unreachable objects

// Object becomes eligible for GC when:
// 1. Nullifying reference
Object obj = new Object();
obj = null;  // Object eligible for GC

// 2. Reassigning reference
Object obj1 = new Object();
Object obj2 = new Object();
obj1 = obj2;  // First object eligible for GC

// 3. Objects created inside method
void method() {
    Object obj = new Object();
}  // obj eligible after method returns

// 4. Island of isolation
class Node {
    Node next;
}
Node a = new Node();
Node b = new Node();
a.next = b;
b.next = a;
a = b = null;  // Both eligible (circular reference)

// GC Types
// - Serial GC: Single-threaded, small heaps
// - Parallel GC: Multi-threaded, throughput-focused
// - G1 GC: Low latency, large heaps (default Java 9+)
// - ZGC: Ultra-low latency (Java 11+)
// - Shenandoah: Low latency alternative

4. Explain equals() and hashCode() contract

// Contract:
// 1. If a.equals(b) is true, then a.hashCode() == b.hashCode()
// 2. If hashCode differs, objects are NOT equal
// 3. If hashCode same, objects MAY OR MAY NOT be equal

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

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

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

// Why both must be overridden together:
Map<Person, String> map = new HashMap<>();
Person p1 = new Person("John", 30);
map.put(p1, "value");

Person p2 = new Person("John", 30);
map.get(p2);  // Returns null if only equals() overridden!
// HashMap uses hashCode first to find bucket, then equals()

5. What is the difference between == and equals()?

// == compares references (memory addresses)
// equals() compares content (if overridden)

String s1 = new String("hello");
String s2 = new String("hello");
String s3 = "hello";
String s4 = "hello";

s1 == s2;        // false (different objects)
s1.equals(s2);   // true (same content)

s3 == s4;        // true (String pool - same object)
s3.equals(s4);   // true

// For primitives, use ==
int a = 5, b = 5;
a == b;          // true

// For objects, use equals() for content comparison
Integer i1 = 128, i2 = 128;
i1 == i2;        // false (outside Integer cache -128 to 127)
i1.equals(i2);   // true

Integer i3 = 100, i4 = 100;
i3 == i4;        // true (inside Integer cache)

6. Explain String immutability

// Strings are immutable - content cannot be changed after creation

String s1 = "Hello";
s1.concat(" World");  // Creates new String, original unchanged
System.out.println(s1);  // "Hello"

s1 = s1.concat(" World");  // Reference now points to new String
System.out.println(s1);  // "Hello World"

// Benefits of immutability:
// 1. Thread-safe (can be shared without synchronization)
// 2. Can be cached (String pool)
// 3. Security (credentials, file paths can't be modified)
// 4. Hash code can be cached

// String Pool
String a = "hello";          // Goes to pool
String b = "hello";          // Reuses from pool
String c = new String("hello");  // New object in heap
String d = c.intern();       // Returns pooled "hello"

a == b;  // true (same pool object)
a == c;  // false (c is in heap)
a == d;  // true (intern returns pool object)

// For mutable strings, use StringBuilder (not thread-safe)
// or StringBuffer (thread-safe)
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");  // Modifies same object

7. What is the difference between final, finally, and finalize?

// FINAL - constant/immutable

// final variable - cannot be reassigned
final int MAX = 100;
// MAX = 200;  // Compile error

// final method - cannot be overridden
class Parent {
    final void cannotOverride() { }
}

// final class - cannot be extended
final class ImmutableClass { }
// class Child extends ImmutableClass { }  // Compile error

// final reference - reference cannot change, object can
final List<String> list = new ArrayList<>();
list.add("item");  // OK - modifying object
// list = new ArrayList<>();  // Compile error

// FINALLY - cleanup code that always executes
try {
    // risky code
} catch (Exception e) {
    // handle exception
} finally {
    // always executes (even if return in try/catch)
    // cleanup resources
}

// FINALIZE - deprecated, called by GC before reclaiming object
@Override
protected void finalize() throws Throwable {
    // cleanup (DON'T USE - unpredictable, deprecated)
    super.finalize();
}
// Use try-with-resources or Cleaner instead

8. What is the difference between throw and throws?

// throw - actually throws an exception
void method() {
    if (condition) {
        throw new IllegalArgumentException("Bad input");
    }
}

// throws - declares that method might throw exception
void riskyMethod() throws IOException, SQLException {
    // method body
}

// Checked vs Unchecked exceptions
// Checked: Must be declared or caught (IOException, SQLException)
// Unchecked: RuntimeException and its subclasses (NullPointerException)

class CheckedExample {
    void method() throws IOException {  // Must declare
        throw new IOException();
    }
}

class UncheckedExample {
    void method() {  // No declaration needed
        throw new RuntimeException();
    }
}

9. Explain abstract class vs interface

// ABSTRACT CLASS
abstract class Animal {
    private String name;  // Can have state

    public Animal(String name) {  // Can have constructor
        this.name = name;
    }

    public void eat() {  // Concrete method
        System.out.println("Eating");
    }

    abstract void makeSound();  // Abstract method
}

// INTERFACE
interface Flyable {
    // All fields are public static final
    int MAX_ALTITUDE = 10000;

    // Abstract method (public abstract implicit)
    void fly();

    // Default method (Java 8+)
    default void land() {
        System.out.println("Landing");
    }

    // Static method (Java 8+)
    static void info() {
        System.out.println("Flyable interface");
    }

    // Private method (Java 9+)
    private void helper() { }
}

// KEY DIFFERENCES
// 1. State: Abstract class can have instance variables
// 2. Constructor: Abstract class can have constructors
// 3. Inheritance: Class extends one class, implements many interfaces
// 4. Access: Interface methods are public, abstract class can have any
// 5. Fields: Interface fields are public static final only

// WHEN TO USE
// Abstract class: IS-A relationship, share code among related classes
// Interface: CAN-DO relationship, define capability contract

10. Explain method overloading vs overriding

// OVERLOADING - Same name, different parameters (compile-time polymorphism)
class Calculator {
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; }
    int add(int a, int b, int c) { return a + b + c; }
}

// OVERRIDING - Same signature in subclass (runtime polymorphism)
class Animal {
    void speak() {
        System.out.println("Animal speaks");
    }
}

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

// RULES FOR OVERRIDING
// 1. Same method signature
// 2. Return type must be same or covariant (subtype)
// 3. Access cannot be more restrictive
// 4. Cannot throw broader checked exceptions
// 5. Cannot override static, final, or private methods

class Parent {
    protected Number getValue() throws IOException {
        return 1;
    }
}

class Child extends Parent {
    @Override
    public Integer getValue() throws FileNotFoundException {  // Valid
        // public (wider) - OK
        // Integer (covariant) - OK
        // FileNotFoundException (narrower) - OK
        return 2;
    }
}

11. What is the Java ClassLoader?

// ClassLoaders load classes into JVM

// Bootstrap ClassLoader - loads core Java classes (rt.jar)
// Extension ClassLoader - loads extension classes (ext folder)
// Application ClassLoader - loads application classes (classpath)

// Delegation model (Parent-first)
// 1. Request goes to Application ClassLoader
// 2. Delegates to Extension ClassLoader
// 3. Delegates to Bootstrap ClassLoader
// 4. If Bootstrap can't find, Extension tries
// 5. If Extension can't find, Application tries
// 6. If none find, ClassNotFoundException

// Check which classloader loaded a class
Class<?> clazz = String.class;
ClassLoader loader = clazz.getClassLoader();
// null for Bootstrap ClassLoader

ClassLoader appLoader = MyClass.class.getClassLoader();
ClassLoader parent = appLoader.getParent();  // Extension ClassLoader

12. Explain the Singleton pattern and its thread-safety

// Eager initialization (thread-safe, simple)
public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

// Lazy initialization with double-checked locking
public class LazySingleton {
    private static volatile LazySingleton instance;
    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

// Bill Pugh Singleton (recommended)
public class BillPughSingleton {
    private BillPughSingleton() {}

    private static class SingletonHelper {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

// Enum Singleton (best - handles serialization, reflection)
public enum EnumSingleton {
    INSTANCE;

    public void doSomething() {
        // singleton methods
    }
}

13. Explain serialization and its issues

// Serialization - converting object to byte stream
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient String password;  // Not serialized
    private static int count;  // Not serialized (static)
}

// Issues:
// 1. Security - sensitive data can be exposed
// 2. Version compatibility - serialVersionUID
// 3. Inheritance - parent must be Serializable or have no-arg constructor
// 4. Performance - slower than other formats (JSON, Protobuf)

// Prevent subclass serialization
private void writeObject(ObjectOutputStream out) throws IOException {
    throw new NotSerializableException();
}

// Custom serialization
private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject();
    out.writeObject(encrypt(password));
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    password = decrypt((String) in.readObject());
}

// Singleton serialization issue
// readResolve prevents new instance creation during deserialization
private Object readResolve() {
    return INSTANCE;
}

14. What is reflection and when to use it?

// Reflection - inspect and modify runtime behavior

Class<?> clazz = Person.class;
// or Class.forName("com.example.Person");
// or person.getClass();

// Get class info
clazz.getName();
clazz.getSimpleName();
clazz.getSuperclass();
clazz.getInterfaces();
clazz.getModifiers();

// Fields
Field[] fields = clazz.getDeclaredFields();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);  // Access private
Object value = field.get(instance);
field.set(instance, newValue);

// Methods
Method[] methods = clazz.getDeclaredMethods();
Method method = clazz.getDeclaredMethod("getName");
method.setAccessible(true);
Object result = method.invoke(instance, args);

// Constructors
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Object instance = constructor.newInstance("value");

// Use cases:
// - Frameworks (Spring, Hibernate)
// - Serialization/deserialization
// - Testing (accessing private members)
// - Dependency injection
// - Plugin systems

// Drawbacks:
// - Performance overhead
// - Security restrictions
// - Breaks encapsulation
// - No compile-time type checking

15. Explain Comparable vs Comparator

// Comparable - natural ordering (implemented by the class itself)
public class Person implements Comparable<Person> {
    private String name;
    private int age;

    @Override
    public int compareTo(Person other) {
        return this.name.compareTo(other.name);  // Natural order by name
    }
}

List<Person> people = new ArrayList<>();
Collections.sort(people);  // Uses compareTo

// Comparator - custom ordering (external)
Comparator<Person> byAge = new Comparator<>() {
    @Override
    public int compare(Person p1, Person p2) {
        return Integer.compare(p1.getAge(), p2.getAge());
    }
};

// Lambda
Comparator<Person> byAge = (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge());

// Method reference
Comparator<Person> byAge = Comparator.comparingInt(Person::getAge);

// Chaining
Comparator<Person> complex = Comparator
    .comparing(Person::getLastName)
    .thenComparing(Person::getFirstName)
    .thenComparingInt(Person::getAge);

// Reverse
Comparator<Person> reverseAge = Comparator.comparingInt(Person::getAge).reversed();

// Null handling
Comparator<Person> nullsFirst = Comparator.nullsFirst(Comparator.comparing(Person::getName));

Collections.sort(people, byAge);
people.sort(byAge);

Quick Reference Table

Concept Key Points
JVM Executes bytecode, manages memory, GC
Heap vs Stack Heap: objects (shared), Stack: primitives/references (per thread)
GC Automatic memory management, generational collection
equals/hashCode Must override together, used by HashMap/HashSet
String Pool Reuses string literals, immutable strings
final Variable: constant, Method: no override, Class: no inherit
Abstract vs Interface Abstract: state + behavior, Interface: contract
Overload vs Override Overload: compile-time, Override: runtime polymorphism
Checked vs Unchecked Checked: must handle, Unchecked: RuntimeException
Serialization Convert object to bytes, serialVersionUID
Reflection Runtime class inspection/modification
Comparable vs Comparator Natural vs custom ordering