12.1 File Handling
The java.io.File class represents file metadata and disk paths (checking existence, creating directories, querying sizes), but does not read or write file contents directly. Stream classes handle actual I/O operations.
Efficient Buffered Stream Wrapping
Raw character streams (FileReader and FileWriter) read one character at a time, resulting in thousands of expensive operating system disk accesses. Wrapping them inside a BufferedReader / BufferedWriter creates an internal memory buffer, reading large blocks in a single disk hit:
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} // br is automatically closed here even if an exception occurs!
💡 Placement & Interview Deep-Dive
The Beauty of Try-With-Resources (Java 7+): Any class implementing java.lang.AutoCloseable can be declared inside the try(...) parenthesis. When the block completes (normally or via exception), the JVM automatically calls close(), completely preventing native resource leaks.
12.2 Interfaces and Packages
Packages group related classes and interfaces into modular namespaces (e.g. java.util or com.company.project), preventing class-naming collisions between third-party libraries.
Interfaces allow completely unrelated classes to be treated polymorphically through a shared capability contract:
interface Payable {
void pay(double amt);
}
class Invoice implements Payable {
public void pay(double amt) {
System.out.println("Processing invoice: $" + amt);
}
}
12.3 Generics
Introduced in Java 5, Generics enable classes, interfaces, and methods to operate on parameterized types. They provide compile-time type safety and eliminate the risk of runtime ClassCastException.
class Box<T> {
private T item;
void set(T item) { this.item = item; }
T get() { return item; }
}
Box<String> b = new Box<>();
b.set("hello"); // Type checked at compile time; only Strings allowed!
💡 Placement & Interview Deep-Dive
- What is Type Erasure in Java? To maintain backward compatibility with pre-Java 5 bytecode, the Java compiler uses Type Erasure: it strips out all generic type information at compile-time, replacing type variables with their bound (or
Object), and inserts automatic casts. At runtime, the JVM has no knowledge of generic types! - Wildcards & The PECS Principle:
<? extends T>: Upper-bounded wildcard. Use when you only Produce items from a collection (Read-Only).<? super T>: Lower-bounded wildcard. Use when you only Consume items into a collection (Write-Only).- PECS: Producer Extends, Consumer Super!
12.4 Lambda Expressions and Streams (Java 8+)
Java 8 revolutionized the language by introducing functional programming constructs: Lambda Expressions and the Stream API.
Lambda Expressions
A lambda expression is an anonymous (unnamed) function that provides an inline implementation of a Functional Interface (an interface with exactly one abstract method, such as Runnable or Comparator):
// Traditional anonymous class:
Runnable r1 = new Runnable() {
public void run() { System.out.println("Hello"); }
};
// Modern Lambda expression:
Runnable r2 = () -> System.out.println("Hello");
The Stream API
The Stream API allows processing collections in a declarative, pipeline style. A stream pipeline chains lazy intermediate operations (filter, map, sorted) followed by an eager terminal operation (collect, count, reduce):
List<String> names = List.of("Amit", "Neha", "Arun", "Pooja");
long count = names.stream()
.filter(name -> name.startsWith("A"))
.count();
System.out.println("Names starting with A: " + count); // 2
An interface with exactly one abstract method — the shape that makes it eligible to be implemented inline with a lambda expression instead of a full, separately-written class. Runnable and Comparator are two common built-in examples.