Curated questions covering OOP, collections, multithreading, exception handling, Java 8+ features, streams, JVM internals, and design patterns.
abstract class Shape { abstract double area(); void print() { System.out.println(area()); } }
interface Drawable { void draw(); default void show() { System.out.println("Showing"); } }
== compares object references (memory addresses) for objects, and values for primitives. .equals() compares the content/value of objects. Always use .equals() for String and object comparison.
String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false (different references)
System.out.println(a.equals(b)); // true (same content)
The Collections Framework provides interfaces and implementations for storing and manipulating groups of objects.
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();
Set<Integer> set = new HashSet<>();
// Comparable
class Student implements Comparable<Student> {
public int compareTo(Student s) { return this.name.compareTo(s.name); }
}
// Comparator
students.sort(Comparator.comparing(Student::getAge).thenComparing(Student::getName));
public void readFile(String path) throws IOException {
if (path == null) throw new IllegalArgumentException("Path cannot be null");
// ...
}
The finally block always executes after try/catch, whether or not an exception occurred. It is used for cleanup (closing resources). It does NOT execute if: System.exit() is called, the JVM crashes, or the thread is killed.
try {
// risky code
} catch (Exception e) {
// handle
} finally {
connection.close(); // always runs
}
Try-with-resources automatically closes resources that implement AutoCloseable when the try block exits. Eliminates the need for finally blocks for resource cleanup.
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.executeQuery();
} // conn and ps are automatically closed
// Overloading
void print(int x) {}
void print(String s) {}
// Overriding
@Override
public String toString() { return "MyClass"; }
Lambda expressions provide a concise way to implement functional interfaces (interfaces with a single abstract method). They enable functional programming style in Java.
// Before Java 8
Runnable r = new Runnable() { public void run() { System.out.println("Hello"); } };
// Lambda
Runnable r = () -> System.out.println("Hello");
// With parameters
Comparator<String> c = (a, b) -> a.compareTo(b);
Streams provide a functional approach to processing collections. They support lazy evaluation, parallel processing, and a rich set of operations.
List<String> names = employees.stream()
.filter(e -> e.getSalary() > 50000)
.map(Employee::getName)
.sorted()
.collect(Collectors.toList());
// Parallel stream
long count = list.parallelStream().filter(x -> x > 0).count();
// map: Stream<List<String>>
list.stream().map(s -> s.split(","));
// flatMap: Stream<String>
list.stream().flatMap(s -> Arrays.stream(s.split(",")));
Optional is a tl-container that may or may not contain a non-null value. It avoids NullPointerException and makes null handling explicit.
Optional<String> name = Optional.ofNullable(getName());
String result = name
.filter(n -> n.length() > 2)
.map(String::toUpperCase)
.orElse("DEFAULT");
Java 8 default methods allow interfaces to have method implementations. Difference: abstract classes can have state (instance variables) and constructors; interfaces cannot. A class can implement multiple interfaces with default methods but extend only one abstract class.
interface Greeter {
default String greet(String name) { return "Hello, " + name; }
void farewell(String name); // still abstract
}
Multithreading allows concurrent execution of two or more threads. Create threads by extending Thread or implementing Runnable. Use ExecutorService for thread pool management.
// Runnable (preferred)
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> processTask());
executor.shutdown();
// Thread
new Thread(() -> System.out.println("Running")).start();
// Synchronized method
public synchronized void increment() { count++; }
// Synchronized block (preferred - finer control)
public void increment() {
synchronized(this) { count++; }
}
volatile ensures that a variable is read from and written to main memory, not a thread-local cache. It guarantees visibility of changes across threads but does NOT guarantee atomicity.
private volatile boolean running = true;
// Thread 1
public void stop() { running = false; }
// Thread 2
while (running) { doWork(); } // sees updated value
Garbage collection automatically reclaims memory occupied by objects that are no longer reachable. The JVM uses generational GC: Young Generation (Eden + Survivor spaces) for short-lived objects, Old Generation for long-lived objects. GC algorithms: Serial, Parallel, G1 (default), ZGC.
String literals are stored in the String pool (part of heap). When you create a String literal, Java checks the pool first and reuses existing instances. new String() always creates a new object on the heap outside the pool.
String a = "hello"; // pool
String b = "hello"; // same pool reference
String c = new String("hello"); // new heap object
System.out.println(a == b); // true
System.out.println(a == c); // false
System.out.println(a.equals(c)); // true
Generics enable type-safe collections and methods. Type erasure removes generic type information at compile time, replacing it with Object or bounds. This means generic type info is not available at runtime.
// Generic method
public <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
// Bounded wildcard
public double sum(List<? extends Number> list) {
return list.stream().mapToDouble(Number::doubleValue).sum();
}
Callable<Integer> task = () -> {
return computeResult(); // can return value and throw
};
Future<Integer> future = executor.submit(task);
int result = future.get(); // blocks until done
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
map.computeIfAbsent("key2", k -> expensiveCompute(k));
Stack is a legacy class extending Vector (synchronized, slow). Deque (ArrayDeque) is the modern replacement - faster, not synchronized, supports both stack (push/pop) and queue (offer/poll) operations.
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); stack.push(2);
stack.pop(); // 2
Deque<Integer> queue = new ArrayDeque<>();
queue.offer(1); queue.offer(2);
queue.poll(); // 1
Records (Java 16+) are immutable data classes that automatically generate constructor, getters, equals(), hashCode(), and toString(). Ideal for DTOs and value objects.
record Point(int x, int y) {}
Point p = new Point(3, 4);
System.out.println(p.x()); // 3
System.out.println(p); // Point[x=3, y=4]
Sealed classes (Java 17+) restrict which classes can extend or implement them. Used with permits keyword. Enables exhaustive pattern matching.
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
class MyClass {
static { System.out.println("Static init"); } // once
{ System.out.println("Instance init"); } // every new
MyClass() { System.out.println("Constructor"); }
}
String.valueOf(42); // "42"
String.valueOf(null); // "null"
String.format("Hi %s, you are %d", "Alice", 30); // "Hi Alice, you are 30"
Enums are type-safe, can have methods and fields, support switch statements, and are singletons. Constants (static final) are just values with no type safety or behavior.
enum Status {
ACTIVE("Active"), INACTIVE("Inactive");
private final String label;
Status(String label) { this.label = label; }
public String getLabel() { return label; }
}
// Composition (preferred)
class Car {
private Engine engine; // has-a
Car(Engine e) { this.engine = e; }
}
List<String> a = Arrays.asList("a", "b"); // can set, cannot add/remove
List<String> b = List.of("a", "b"); // fully immutable
var (Java 10+) lets the compiler infer the type of local variables. It is not dynamic typing - the type is still fixed at compile time. Cannot be used for fields, method parameters, or return types.
var list = new ArrayList<String>(); // inferred as ArrayList<String>
var map = new HashMap<String, Integer>();
for (var entry : map.entrySet()) { ... }
Text blocks (Java 15+) are multi-line string literals that preserve formatting and reduce escape sequences. Ideal for JSON, SQL, and HTML strings.
// Regular string
String json = "{\n \"name\": \"Alice\"\n}";
// Text block
String json = """\n {\n "name": "Alice"\n }\n """;
Pattern matching instanceof (Java 16+) combines the type check and cast into one expression, eliminating the explicit cast.
// Traditional
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// Pattern matching
if (obj instanceof String s) {
System.out.println(s.length()); // s is already cast
}
CompletableFuture.supplyAsync(() -> fetchUser(id))
.thenApply(user -> enrichUser(user))
.thenAccept(user -> saveUser(user))
.exceptionally(ex -> { log(ex); return null; });
Predicate<String> notEmpty = s -> !s.isEmpty();
Function<String, Integer> length = String::length;
Consumer<String> print = System.out::println;
Supplier<List<String>> newList = ArrayList::new;
Explore 500+ free tutorials across 20+ languages and frameworks.