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()); } }\ninterface 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");\nString b = new String("hello");\nSystem.out.println(a == b); // false (different references)\nSystem.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<>();\nMap<String, Integer> map = new HashMap<>();\nSet<Integer> set = new HashSet<>();
// Comparable\nclass Student implements Comparable<Student> {\n public int compareTo(Student s) { return this.name.compareTo(s.name); }\n}\n\n// Comparator\nstudents.sort(Comparator.comparing(Student::getAge).thenComparing(Student::getName));
public void readFile(String path) throws IOException {\n if (path == null) throw new IllegalArgumentException("Path cannot be null");\n // ...\n}
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 {\n // risky code\n} catch (Exception e) {\n // handle\n} finally {\n connection.close(); // always runs\n}
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();\n PreparedStatement ps = conn.prepareStatement(sql)) {\n ps.executeQuery();\n} // conn and ps are automatically closed
// Overloading\nvoid print(int x) {}\nvoid print(String s) {}\n\n// Overriding\n@Override\npublic 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\nRunnable r = new Runnable() { public void run() { System.out.println("Hello"); } };\n\n// Lambda\nRunnable r = () -> System.out.println("Hello");\n\n// With parameters\nComparator<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()\n .filter(e -> e.getSalary() > 50000)\n .map(Employee::getName)\n .sorted()\n .collect(Collectors.toList());\n\n// Parallel stream\nlong count = list.parallelStream().filter(x -> x > 0).count();
// map: Stream<List<String>>\nlist.stream().map(s -> s.split(","));\n\n// flatMap: Stream<String>\nlist.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());\n\nString result = name\n .filter(n -> n.length() > 2)\n .map(String::toUpperCase)\n .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 {\n default String greet(String name) { return "Hello, " + name; }\n void farewell(String name); // still abstract\n}
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)\nExecutorService executor = Executors.newFixedThreadPool(4);\nexecutor.submit(() -> processTask());\nexecutor.shutdown();\n\n// Thread\nnew Thread(() -> System.out.println("Running")).start();
// Synchronized method\npublic synchronized void increment() { count++; }\n\n// Synchronized block (preferred - finer control)\npublic void increment() {\n synchronized(this) { count++; }\n}
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;\n\n// Thread 1\npublic void stop() { running = false; }\n\n// Thread 2\nwhile (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\nString b = "hello"; // same pool reference\nString c = new String("hello"); // new heap object\nSystem.out.println(a == b); // true\nSystem.out.println(a == c); // false\nSystem.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\npublic <T extends Comparable<T>> T max(T a, T b) {\n return a.compareTo(b) > 0 ? a : b;\n}\n\n// Bounded wildcard\npublic double sum(List<? extends Number> list) {\n return list.stream().mapToDouble(Number::doubleValue).sum();\n}
Callable<Integer> task = () -> {\n return computeResult(); // can return value and throw\n};\nFuture<Integer> future = executor.submit(task);\nint result = future.get(); // blocks until done
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();\nmap.put("key", 1);\nmap.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<>();\nstack.push(1); stack.push(2);\nstack.pop(); // 2\n\nDeque<Integer> queue = new ArrayDeque<>();\nqueue.offer(1); queue.offer(2);\nqueue.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) {}\n\nPoint p = new Point(3, 4);\nSystem.out.println(p.x()); // 3\nSystem.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 {}\nrecord Circle(double radius) implements Shape {}\nrecord Rectangle(double w, double h) implements Shape {}
class MyClass {\n static { System.out.println("Static init"); } // once\n { System.out.println("Instance init"); } // every new\n MyClass() { System.out.println("Constructor"); }\n}
String.valueOf(42); // "42"\nString.valueOf(null); // "null"\nString.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 {\n ACTIVE("Active"), INACTIVE("Inactive");\n private final String label;\n Status(String label) { this.label = label; }\n public String getLabel() { return label; }\n}
// Composition (preferred)\nclass Car {\n private Engine engine; // has-a\n Car(Engine e) { this.engine = e; }\n}
List<String> a = Arrays.asList("a", "b"); // can set, cannot add/remove\nList<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>\nvar map = new HashMap<String, Integer>();\nfor (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\nString json = "{\n \"name\": \"Alice\"\n}";\n\n// Text block\nString 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\nif (obj instanceof String) {\n String s = (String) obj;\n System.out.println(s.length());\n}\n\n// Pattern matching\nif (obj instanceof String s) {\n System.out.println(s.length()); // s is already cast\n}
CompletableFuture.supplyAsync(() -> fetchUser(id))\n .thenApply(user -> enrichUser(user))\n .thenAccept(user -> saveUser(user))\n .exceptionally(ex -> { log(ex); return null; });
Predicate<String> notEmpty = s -> !s.isEmpty();\nFunction<String, Integer> length = String::length;\nConsumer<String> print = System.out::println;\nSupplier<List<String>> newList = ArrayList::new;
Explore 500+ free tutorials across 20+ languages and frameworks.