← Back to Advanced Java Course | Chapter 11: Advanced & Security | Lesson 6 of 19

Java Advanced Interview Questions

Checked vs Unchecked Exceptions

Checked exceptions are enforced by the compiler: a method that can throw one must declare it or catch it, forcing callers to handle the failure explicitly. Unchecked exceptions (RuntimeException and its subclasses) skip that compile-time enforcement entirely, which is why they're typically reserved for programming errors rather than expected failure conditions.

Example: Checked vs Unchecked Exceptions

java
public class Main {
	static void readFile() throws java.io.IOException { // checked -- must be declared or caught
		throw new java.io.IOException("file missing");
	}
	static void process() { // no throws clause needed for unchecked
		throw new RuntimeException("unchecked -- skips compile-time enforcement");
	}
	public static void main(String[] args) {
		try { readFile(); } catch (java.io.IOException e) { System.out.println("Checked: " + e.getMessage()); }
		try { process(); } catch (RuntimeException e) { System.out.println("Unchecked: " + e.getMessage()); }
	}
}

HashMap Collisions and Re-indexing

HashMap collisions happen when two different keys hash to the same bucket; Java handles this by chaining colliding entries into a linked list within that bucket, and in Java 8+ will convert a sufficiently dense bucket's list into a small red-black tree to keep worst-case lookup time from degrading to O(n).

Example: HashMap Collisions and Re-indexing

java
import java.util.*;
public class Main {
	public static void main(String[] args) {
		Map<Integer, String> map = new HashMap<>();
		map.put(1, "a"); map.put(17, "b"); // both may land in the same bucket depending on capacity
		System.out.println(map.get(1) + " " + map.get(17)); // chained within the bucket, still found correctly
	}
}

Synchronized Methods vs Blocks

Synchronizing an entire method locks on the whole object (or class, if static), which is simple but can create unnecessary contention. Synchronized blocks let you lock only the specific lines that actually touch shared state, reducing how long other threads have to wait for unrelated work in the same method.

Example: Synchronized Methods vs Blocks

java
public class Main {
	static int counter = 0;
	static final Object lock = new Object();
	static synchronized void incrementWholeMethod() { counter++; } // locks the whole object
	static void incrementBlockOnly() {
		synchronized (lock) { counter++; } // locks only these lines
	}
	public static void main(String[] args) {
		incrementWholeMethod();
		incrementBlockOnly();
		System.out.println(counter);
	}
}

Comparable vs Comparator

Comparable defines a class's single, natural sort order via compareTo(), baked directly into the class itself. Comparator lives outside the class and lets you define one or more alternative, situational orderings without modifying the original class at all.

Example: Comparable vs Comparator

java
import java.util.*;
public class Main {
	static class Person implements Comparable<Person> {
		String name; int age;
		Person(String name, int age) { this.name = name; this.age = age; }
		public int compareTo(Person o) { return Integer.compare(age, o.age); } // natural order, baked in
	}
	public static void main(String[] args) {
		List<Person> people = new ArrayList<>(List.of(new Person("Bo", 30), new Person("Al", 20)));
		Collections.sort(people); // uses Comparable
		people.sort(Comparator.comparing(p -> p.name)); // Comparator, external alternative order
		System.out.println(people.get(0).name);
	}
}

Shallow Copy vs Deep Copy

A shallow copy duplicates only the top-level object and its direct field references, so nested objects are still shared between the original and the copy. A deep copy recursively constructs entirely new nested objects too, so changes to one copy's inner state never leak into the other.

Example: Shallow Copy vs Deep Copy

java
public class Main {
	static class Address { String city; Address(String city) { this.city = city; } }
	static class Person {
		Address address;
		Person(Address address) { this.address = address; }
		Person shallowCopy() { return new Person(this.address); } // shares the same Address
		Person deepCopy() { return new Person(new Address(this.address.city)); } // new nested object
	}
	public static void main(String[] args) {
		Person original = new Person(new Address("Delhi"));
		Person shallow = original.shallowCopy();
		Person deep = original.deepCopy();
		System.out.println(original.address == shallow.address);
		System.out.println(original.address == deep.address);
	}
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.