Java Handbook
Java is a statically typed, class-based, object-oriented language created by James Gosling at Sun Microsystems in 1995. It compiles to bytecode that runs on the JVM, giving it the "write once, run anywhere" property. The JVM's JIT compiler makes long-running Java services competitive in throughput benchmarks. Modern Java (17–21 LTS) has closed the feature gap with younger languages: records, sealed classes, pattern matching, text blocks, and virtual threads (Project Loom, Java 21) make concurrent programming dramatically simpler. Java powers Android, Spring/Spring Boot, Kafka, Hadoop, Elasticsearch, and a huge proportion of enterprise backend systems.
Pick Java when
- Enterprise backend systems — Spring Boot is the most widely deployed web framework in the enterprise world. Its ecosystem (Spring Security, Spring Data, Spring Cloud) covers virtually every enterprise pattern.
- Android development — Java (and Kotlin, its modern companion on the JVM) is the native Android language. Android Studio, Gradle, and the entire Android ecosystem target the JVM.
- Big data and streaming — Kafka, Spark, Flink, Hadoop, and Cassandra are all JVM-based. If you're in the data engineering space, Java and Scala are the ecosystem languages.
- Long-running services that benefit from JIT — the JVM's JIT compiler optimises hot paths over time. A Java service running for hours will outperform its startup-phase throughput significantly.
- Strong typing with a massive ecosystem — Maven Central has millions of libraries. Whatever you need, it exists, is mature, and has long-term support.
- Virtual threads (Java 21+) — Project Loom's virtual threads let you write blocking-style code that scales to millions of concurrent tasks, eliminating the callback/async complexity of Node.js or reactive frameworks.
Think twice before choosing Java when
- Startup time matters — JVM startup (classloading, JIT warm-up) takes hundreds of milliseconds to seconds. For short-lived CLI tools, lambdas, or serverless functions with tight cold-start budgets, Go, Rust, or native-compiled GraalVM images are better.
- Memory footprint is a constraint — a simple Spring Boot service easily uses 256–512 MB at idle. For resource-constrained environments, Go or Rust services use 10–50× less memory.
- Modern language ergonomics — Java is verbose compared to Kotlin, Go, or Python. Boilerplate (getters, setters, builders) has reduced with records, but Kotlin on the JVM is often a better choice for new JVM projects.
- Scripting and prototyping — spinning up a Spring Boot app for a one-off task is heavyweight. Use Python or Node for quick scripts.
Java vs. its closest alternatives
- Java vs Kotlin — Kotlin is 100% interoperable with Java, more concise, has null safety by default, coroutines, and data classes. For new JVM projects, Kotlin is often the better choice; Java wins on ecosystem maturity and corporate familiarity.
- Java vs C# — both are managed OOP languages with similar enterprise positioning. C# evolves faster (records, pattern matching, top-level statements came earlier). Java wins on cross-platform portability and Android; C# wins on developer experience and .NET ecosystem.
- Java vs Go — Go starts faster, uses less memory, and is simpler. Java has a much bigger ecosystem and JIT-optimised throughput for long-running services. Go for new cloud-native microservices; Java for enterprise systems with deep library requirements.
Resources
- oracle.com/java — official Java platform page
- Java SE documentation — guides, JDK tools, and release notes
- Java SE 21 API Javadoc — full standard library reference
- OpenJDK — open-source JDK implementation and JEP proposals
- Baeldung — practical Java tutorials and guides
Topics
Variables & Types
java
// Primitive types
int age = 30;
long big = 9_000_000_000L;
double pi = 3.14159265;
float f = 3.14f;
boolean ok = true;
char c = 'A';
byte b = 127;
short s = 32767;
// Reference types
String name = 'Alice';
Object obj = 42; // autoboxing: int -> Integer -> Object
// var — local type inference (Java 10+)
var list = new java.util.ArrayList<String>();
var map = new java.util.HashMap<String, Integer>();
// final — constant reference (cannot reassign)
final double PI = 3.14159;
final String PREFIX = 'app_';
// Type casting
double d = 9.99;
int i = (int) d; // narrowing: 9
long l = i; // widening: implicit
// Wrapper types / autoboxing
Integer boxed = 42; // autoboxing
int unboxed = boxed; // unboxing
Integer.parseInt('42'); // String -> int
String.valueOf(42); // int -> String
// Array
int[] nums = { 1, 2, 3, 4 };
String[] words = new String[5];
int[][] grid = new int[3][3];Operators
java
// Arithmetic
int a = 10, b = 3;
a + b; // 13
a - b; // 7
a * b; // 30
a / b; // 3 (integer division)
a % b; // 1 (remainder)
// Compound assignment
a += 5; a -= 2; a *= 3; a /= 4; a %= 7;
// Increment / decrement
int x = 5;
x++; // post-increment: use then add
++x; // pre-increment: add then use
// Comparison & logical
a == b; a != b; a > b; a < b; a >= b; a <= b;
true && false; // false
true || false; // true
!true; // false
// Bitwise
a & b; // AND
a | b; // OR
a ^ b; // XOR
~a; // NOT (bitwise complement)
a << 2; // left shift (multiply by 4)
a >> 1; // right shift (divide by 2, sign-preserving)
a >>> 1; // unsigned right shift (zero-fills)
// Ternary
String label = a > 5 ? 'big' : 'small';
// instanceof (Java 14+ pattern form also below)
Object obj = 'hello';
if (obj instanceof String s) { // pattern instanceof (Java 16+)
System.out.println(s.length());
}Control Flow
java
int x = 42;
// if / else if / else
if (x > 100) System.out.println('big');
else if (x > 10) System.out.println('medium');
else System.out.println('small');
// Traditional switch statement
switch (x) {
case 1: System.out.println('one'); break;
case 2: System.out.println('two'); break;
default: System.out.println('other');
}
// Switch expression (Java 14+) — arrow form, no fallthrough
String size = switch (x) {
case 1, 2, 3 -> 'small';
case 4, 5 -> 'medium';
default -> 'large';
};
// Switch expression with yield
int result = switch (x % 3) {
case 0 -> 0;
case 1 -> { int v = x * 2; yield v + 1; }
default -> -1;
};
// for loop
for (int i = 0; i < 5; i++) { /* ... */ }
// enhanced for (for-each)
int[] nums = { 1, 2, 3 };
for (int n : nums) System.out.println(n);
// while / do-while
int count = 5;
while (count-- > 0) { /* ... */ }
int n = 0;
do { n++; } while (n < 3);
// break / continue with labels
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) break outer;
}
}Methods
java
// Basic method
public int add(int a, int b) {
return a + b;
}
// Static method
public static double circleArea(double radius) {
return Math.PI * radius * radius;
}
// Void method
public void greet(String name) {
System.out.println('Hello, ' + name + '!');
}
// Overloading — same name, different parameter types/count
public int max(int a, int b) { return a > b ? a : b; }
public double max(double a, double b) { return a > b ? a : b; }
// Varargs — zero or more arguments
public int sum(int... nums) {
int total = 0;
for (int n : nums) total += n;
return total;
}
sum(1, 2, 3); // 6
sum(); // 0
// Returning multiple values via record
public record Pair<A, B>(A first, B second) {}
public Pair<Integer, String> getInfo() {
return new Pair<>(42, 'hello');
}
// Method references (used with functional interfaces)
// ClassName::staticMethod
// instance::instanceMethod
// ClassName::new (constructor ref)
java.util.function.Function<String, Integer> parse = Integer::parseInt;
parse.apply('42'); // 42Classes
java
public class Person {
// Fields
private final String name; // final = set once
private int age;
private static int count = 0; // shared across all instances
// Constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
count++;
}
// Overloaded constructor
public Person(String name) {
this(name, 0); // delegate to above
}
// Getters / setters
public String getName() { return name; }
public int getAge() { return age; }
public void setAge(int age) {
if (age < 0) throw new IllegalArgumentException('Age cannot be negative');
this.age = age;
}
// Static method
public static int getCount() { return count; }
// Instance method
public String greet() {
return 'Hi, I am ' + name + ' (' + age + ')';
}
@Override
public String toString() {
return 'Person{name=' + name + ', age=' + age + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person p)) return false;
return age == p.age && name.equals(p.name);
}
@Override
public int hashCode() {
return java.util.Objects.hash(name, age);
}
}
// Static nested class
public class Outer {
static class Inner { void hello() { System.out.println('inner'); } }
}
// Anonymous class
Runnable r = new Runnable() {
@Override public void run() { System.out.println('running'); }
};Inheritance
java
// Base class
public abstract class Shape {
private String color;
public Shape(String color) { this.color = color; }
public String getColor() { return color; }
// Abstract method — subclasses MUST implement
public abstract double area();
// Concrete method — subclasses may override
public String describe() {
return color + ' shape with area ' + String.format('%.2f', area());
}
}
// Subclass
public class Circle extends Shape {
private final double radius;
public Circle(String color, double radius) {
super(color); // call parent constructor
this.radius = radius;
}
@Override
public double area() { return Math.PI * radius * radius; }
@Override
public String describe() {
return super.describe() + ' (circle r=' + radius + ')';
}
}
// final class — cannot be subclassed
public final class Immutable {
private final int value;
public Immutable(int v) { this.value = v; }
public int getValue() { return value; }
}
// Polymorphism
Shape s = new Circle('red', 5.0);
System.out.println(s.area()); // Circle.area() called
System.out.println(s.describe()); // Circle.describe() called
// Casting
if (s instanceof Circle c) {
System.out.println(c.radius); // pattern variable
}Interfaces
java
// Interface — all methods public abstract by default
public interface Drawable {
void draw(); // abstract
default String getDescription() { // default method (Java 8+)
return 'A drawable object';
}
static Drawable noop() { // static factory (Java 8+)
return () -> {};
}
}
// Interface with multiple defaults
public interface Resizable {
void resize(double factor);
default void doubleSize() { resize(2.0); }
default void halveSize() { resize(0.5); }
}
// A class can implement multiple interfaces
public class Widget implements Drawable, Resizable {
private double size = 1.0;
@Override public void draw() { System.out.println('Drawing widget size=' + size); }
@Override public void resize(double f) { size *= f; }
}
// Functional interface — exactly one abstract method (SAM)
@FunctionalInterface
public interface Transformer<T, R> {
R transform(T input);
// can have default/static methods too
default <V> Transformer<T, V> andThen(Transformer<R, V> after) {
return input -> after.transform(this.transform(input));
}
}
// Using a functional interface with a lambda
Transformer<String, Integer> strlen = s -> s.length();
strlen.transform('hello'); // 5
// Built-in functional interfaces (java.util.function)
// Function<T,R> T -> R
// Consumer<T> T -> void
// Supplier<T> ()-> T
// Predicate<T> T -> boolean
// BiFunction<T,U,R> T,U -> R
// UnaryOperator<T> T -> T
// BinaryOperator<T> T,T -> TGenerics
java
// Generic class
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T getValue() { return value; }
public void set(T v) { this.value = v; }
@Override public String toString() { return 'Box[' + value + ']'; }
}
Box<String> s = new Box<>('hello'); // diamond inference
Box<Integer> i = new Box<>(42);
// Generic method
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
max(3, 7); // 7
max('apple', 'banana'); // 'banana'
// Bounded type parameter
public static <T extends Number> double sum(java.util.List<T> list) {
return list.stream().mapToDouble(Number::doubleValue).sum();
}
// Wildcards
// ? extends T — upper bounded (read-only producer)
public static double total(java.util.List<? extends Number> nums) {
return nums.stream().mapToDouble(Number::doubleValue).sum();
}
// ? super T — lower bounded (write-only consumer)
public static void addNumbers(java.util.List<? super Integer> list) {
list.add(1); list.add(2);
}
// ? — unbounded (only Object methods usable)
public static void printAll(java.util.List<?> list) {
list.forEach(System.out::println);
}
// Type erasure — generics are compile-time only; at runtime List<String> == List<Integer>
// Cannot: new T(), T.class, instanceof List<String>
// Can: instanceof List<?>Enums
java
public enum Planet {
MERCURY(3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6);
private final double mass; // kg
private final double radius; // meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
static final double G = 6.67300E-11;
public double surfaceGravity() {
return G * mass / (radius * radius);
}
public double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
}
// Enum basics
Planet p = Planet.EARTH;
p.name(); // 'EARTH'
p.ordinal(); // 2
Planet.values(); // Planet[]
Planet.valueOf('MARS'); // Planet.MARS
// Enum in switch expression
String desc = switch (p) {
case MERCURY -> 'closest to sun';
case VENUS -> 'hottest';
case EARTH -> 'home';
};
// EnumSet / EnumMap — efficient bit-field implementations
java.util.EnumSet<Planet> inner = java.util.EnumSet.of(Planet.MERCURY, Planet.VENUS, Planet.EARTH);
java.util.EnumMap<Planet, String> names = new java.util.EnumMap<>(Planet.class);
names.put(Planet.EARTH, 'Terra');
// Abstract method in enum
public enum Operation {
PLUS { @Override public double apply(double x, double y) { return x + y; } },
MINUS { @Override public double apply(double x, double y) { return x - y; } };
public abstract double apply(double x, double y);
}Records (Java 16+)
java
// record — immutable data class (Java 16+)
// Compiler generates: constructor, getters, equals, hashCode, toString
public record Point(double x, double y) {}
Point p = new Point(3.0, 4.0);
p.x(); // 3.0 (accessor, not getX())
p.y(); // 4.0
p.toString(); // 'Point[x=3.0, y=4.0]'
// Compact canonical constructor — validate without repeating assignments
public record Range(int min, int max) {
Range { // compact constructor — assignments happen after
if (min > max) throw new IllegalArgumentException('min > max');
}
public int size() { return max - min; }
}
// Records can implement interfaces
public record Name(String first, String last) implements Comparable<Name> {
@Override public int compareTo(Name other) {
int cmp = last.compareTo(other.last);
return cmp != 0 ? cmp : first.compareTo(other.first);
}
public String full() { return first + ' ' + last; }
}
// Generic record
public record Pair<A, B>(A first, B second) {
public Pair<B, A> swap() { return new Pair<>(second, first); }
}
// Records are final — cannot be extended
// Fields are private final — no setters
// Use when: DTOs, value objects, API responses, coordinates, ranges
// Record pattern in switch (Java 21)
Object obj = new Point(1.0, 2.0);
String desc = switch (obj) {
case Point(double x, double y) when x == y -> 'diagonal';
case Point(double x, double y) -> 'point at ' + x + ',' + y;
default -> 'other';
};Exceptions
java
// Checked exception — must declare or catch
public String readFile(String path) throws java.io.IOException {
return java.nio.file.Files.readString(java.nio.file.Path.of(path));
}
// try / catch / finally
try {
String content = readFile('data.txt');
System.out.println(content);
} catch (java.io.FileNotFoundException e) {
System.err.println('File not found: ' + e.getMessage());
} catch (java.io.IOException e) {
System.err.println('IO error: ' + e.getMessage());
throw new RuntimeException('Failed to read', e); // wrap and rethrow
} finally {
System.out.println('Always runs — cleanup here');
}
// Multi-catch (Java 7+)
try { /* ... */ }
catch (java.io.IOException | IllegalArgumentException e) {
System.err.println('Caught: ' + e);
}
// try-with-resources — auto-closes AutoCloseable
try (var reader = new java.io.BufferedReader(new java.io.FileReader('f.txt'));
var writer = new java.io.FileWriter('out.txt')) {
writer.write(reader.readLine());
} // reader and writer closed automatically
// Custom checked exception
public class OrderNotFoundException extends Exception {
private final int orderId;
public OrderNotFoundException(int id) {
super('Order not found: ' + id);
this.orderId = id;
}
public int getOrderId() { return orderId; }
}
// Custom unchecked exception
public class InsufficientStockException extends RuntimeException {
public InsufficientStockException(String sku, int requested, int available) {
super('SKU ' + sku + ': requested ' + requested + ', available ' + available);
}
}
// Checked vs unchecked:
// Checked (extends Exception) — recoverable, caller must handle
// Unchecked (extends RuntimeException)— programming error, caller needn't declareCollections
java
import java.util.*;
// List — ordered, duplicates allowed
List<String> list = new ArrayList<>(); // dynamic array, O(1) get
list.add('apple'); list.add('banana'); list.add('cherry');
list.get(0); // 'apple'
list.set(1, 'blueberry');
list.remove('cherry');
list.size(); // 2
Collections.sort(list);
Collections.unmodifiableList(list);
// LinkedList — O(1) head/tail insert; implements Deque
Deque<String> deque = new LinkedList<>();
deque.addFirst('a'); deque.addLast('b');
deque.peekFirst(); deque.pollLast();
// Set — no duplicates
Set<String> hash = new HashSet<>(); // O(1) add/contains, no order
Set<String> linked = new LinkedHashSet<>(); // insertion order
Set<String> tree = new TreeSet<>(); // sorted, O(log n)
// Map
Map<String, Integer> map = new HashMap<>();
map.put('one', 1); map.put('two', 2);
map.get('one'); // 1
map.getOrDefault('missing', 0); // 0
map.putIfAbsent('three', 3);
map.computeIfAbsent('four', k -> k.length());
map.merge('one', 10, Integer::sum); // 1+10 = 11
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + '=' + e.getValue());
}
Map<String, Integer> sorted = new TreeMap<>(map); // sorted by key
// Queue / Deque
Queue<Integer> pq = new PriorityQueue<>(); // min-heap
pq.offer(3); pq.offer(1); pq.offer(2);
pq.poll(); // 1 (smallest)
// Immutable collections (Java 9+)
List<String> names = List.of('Alice', 'Bob', 'Carol');
Set<Integer> ids = Set.of(1, 2, 3);
Map<String, Integer> scores = Map.of('Alice', 95, 'Bob', 87);
Map<String, Integer> big = Map.ofEntries(
Map.entry('key1', 1), Map.entry('key2', 2));
// Collections utility methods
Collections.shuffle(list);
Collections.reverse(list);
Collections.frequency(list, 'apple');
Collections.min(list); Collections.max(list);Streams
java
import java.util.*;
import java.util.stream.*;
List<String> names = List.of('Alice', 'Bob', 'Carol', 'Dave', 'Eve');
// filter / map / collect
List<String> long_names = names.stream()
.filter(n -> n.length() > 3)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList()); // or toList() Java 16+
// reduce
int total = IntStream.rangeClosed(1, 10).reduce(0, Integer::sum); // 55
// collect to map
Map<Integer, List<String>> byLength = names.stream()
.collect(Collectors.groupingBy(String::length));
// joining
String csv = names.stream().collect(Collectors.joining(', ', '[', ']'));
// statistics
IntSummaryStatistics stats = names.stream()
.mapToInt(String::length)
.summaryStatistics();
stats.getMin(); stats.getMax(); stats.getAverage(); stats.getCount();
// flatMap — flatten nested lists
List<List<Integer>> nested = List.of(List.of(1,2), List.of(3,4));
List<Integer> flat = nested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
// anyMatch / allMatch / noneMatch / findFirst
boolean hasLong = names.stream().anyMatch(n -> n.length() > 4);
Optional<String> first = names.stream().filter(n -> n.startsWith('A')).findFirst();
// distinct / limit / skip
List<Integer> unique = Stream.of(1,2,2,3,3,3).distinct().collect(Collectors.toList());
// Primitive streams avoid boxing
double avg = IntStream.of(1,2,3,4,5).average().orElse(0.0);
// Parallel stream — be careful with shared state
long count = names.parallelStream().filter(n -> n.length() > 3).count();
// Stream.of / Stream.iterate / Stream.generate
Stream.of('a', 'b', 'c');
Stream.iterate(0, n -> n + 1).limit(10); // 0,1,2,...,9
Stream.generate(Math::random).limit(5);
// Collectors.partitioningBy
Map<Boolean, List<String>> partition = names.stream()
.collect(Collectors.partitioningBy(n -> n.length() > 3));Optionals
java
import java.util.Optional;
// Creating Optionals
Optional<String> present = Optional.of('hello');
Optional<String> empty = Optional.empty();
Optional<String> maybe = Optional.ofNullable(null); // empty
// Checking / extracting
present.isPresent(); // true
present.isEmpty(); // false (Java 11+)
present.get(); // 'hello' — throws if empty, avoid unless after isPresent check
// Safe access patterns
present.orElse('default'); // 'hello'
empty.orElse('default'); // 'default'
empty.orElseGet(() -> computeDefault()); // lazy supplier
empty.orElseThrow(() -> new RuntimeException('missing'));
// Transform
Optional<Integer> len = present.map(String::length); // Optional[5]
len.ifPresent(System.out::println); // 5
// flatMap — when the mapper returns Optional
Optional<String> address = Optional.of('user')
.flatMap(u -> findUser(u))
.flatMap(User::getAddress);
// filter
Optional<String> onlyLong = present.filter(s -> s.length() > 3); // Optional[hello]
// ifPresentOrElse (Java 9+)
present.ifPresentOrElse(
v -> System.out.println('Found: ' + v),
() -> System.out.println('Not found'));
// or (Java 9+) — return another Optional if empty
Optional<String> result = empty.or(() -> Optional.of('fallback'));
// stream (Java 9+) — 0 or 1 element stream
long count = present.stream().filter(s -> !s.isEmpty()).count();
// Anti-patterns to avoid:
// opt.get() without isPresent() check
// Optional as method parameter (use overloads instead)
// Optional in fields or collections (use null or sentinel values)
// Optional.of(null) — throws NullPointerException; use ofNullableLambdas & Functional
java
import java.util.function.*;
import java.util.*;
// Lambda syntax
Runnable r = () -> System.out.println('run');
Consumer<String> c = s -> System.out.println(s);
Supplier<String> s = () -> 'hello';
Function<String,Integer> f = str -> str.length();
Predicate<Integer> p = n -> n > 0;
BiFunction<Integer,Integer,Integer> add = (a, b) -> a + b;
UnaryOperator<Integer> dbl = n -> n * 2;
BinaryOperator<Integer> sum = (a, b) -> a + b;
// Block lambda
Function<List<Integer>, Integer> sumList = list -> {
int total = 0;
for (int n : list) total += n;
return total;
};
// Method references — 4 forms
Function<String,Integer> parse = Integer::parseInt; // static
Function<String,String> upper = String::toUpperCase; // instance (unbound)
Supplier<String> greet = "hello"::toUpperCase; // instance (bound)
Supplier<List<String>> newList = ArrayList::new; // constructor
// Composing functions
Function<String, String> trim = String::trim;
Function<String, String> upper2 = String::toUpperCase;
Function<String, String> clean = trim.andThen(upper2);
clean.apply(' hello '); // 'HELLO'
// Predicate composition
Predicate<Integer> pos = n -> n > 0;
Predicate<Integer> even = n -> n % 2 == 0;
Predicate<Integer> posEven = pos.and(even);
Predicate<Integer> either = pos.or(even);
Predicate<Integer> notPos = pos.negate();
// Comparator built from lambdas
List<String> words = new ArrayList<>(List.of('banana', 'apple', 'fig', 'cherry'));
words.sort(Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
// Effectively final — lambdas capture but cannot mutate local variables
int base = 10;
Function<Integer, Integer> addBase = n -> n + base; // base is effectively finalConcurrency
java
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
// Thread — low-level, avoid in app code
Thread t = new Thread(() -> System.out.println('thread'));
t.start();
t.join(); // wait for completion
// ExecutorService — manage thread pools
ExecutorService exec = Executors.newFixedThreadPool(4);
Future<Integer> future = exec.submit(() -> {
Thread.sleep(100);
return 42;
});
int result = future.get(1, TimeUnit.SECONDS); // blocks, timeout
exec.shutdown();
exec.awaitTermination(5, TimeUnit.SECONDS);
// ScheduledExecutorService
ScheduledExecutorService sched = Executors.newScheduledThreadPool(2);
sched.scheduleAtFixedRate(() -> System.out.println('tick'), 0, 1, TimeUnit.SECONDS);
// CompletableFuture — async pipeline (Java 8+)
CompletableFuture<String> cf = CompletableFuture
.supplyAsync(() -> fetchUser(1)) // async supplier
.thenApply(user -> user.getName()) // transform
.thenCompose(name -> fetchOrders(name)) // flatMap
.exceptionally(ex -> 'default') // error fallback
.whenComplete((v, ex) -> log(v, ex)); // side effect always
CompletableFuture.allOf(cf1, cf2, cf3).join(); // wait for all
CompletableFuture.anyOf(cf1, cf2).join(); // wait for first
// Synchronized — mutual exclusion
public class Counter {
private int value = 0;
public synchronized void increment() { value++; }
public synchronized int get() { return value; }
}
// Lock — more flexible than synchronized
ReentrantLock lock = new ReentrantLock();
lock.lock();
try { /* critical section */ }
finally { lock.unlock(); }
// ReadWriteLock — multiple readers OR one writer
ReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock();
try { /* read */ } finally { rwLock.readLock().unlock(); }
// Atomic variables — lock-free thread safety
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
counter.addAndGet(5);
counter.compareAndSet(5, 0); // set to 0 only if current value is 5
// ConcurrentHashMap — thread-safe map
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.computeIfAbsent('key', k -> 0);
map.merge('key', 1, Integer::sum);Virtual Threads (Java 21)
java
// Virtual Threads — Project Loom (Java 21 GA)
// Lightweight user-mode threads; millions can exist concurrently
// Managed by JVM, not OS; each blocked virtual thread is unmounted from carrier thread
// Create and start a virtual thread
Thread vt = Thread.ofVirtual().start(() -> System.out.println('virtual!'));
vt.join();
// Thread.ofVirtual() builder
Thread.Builder.OfVirtual builder = Thread.ofVirtual().name('worker-', 0);
Thread t1 = builder.start(() -> doWork());
Thread t2 = builder.start(() -> doWork());
// Virtual thread executor — one virtual thread per task
try (ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 100_000; i++) {
exec.submit(() -> {
Thread.sleep(Duration.ofMillis(100)); // blocks virtual thread, not carrier
return processRequest();
});
}
} // auto-close waits for all tasks
// Virtual threads shine for I/O-bound work
// Blocking a virtual thread (sleep, socket read, JDBC) is cheap
// The carrier thread is freed to run other virtual threads
// Structured concurrency (Java 21 preview -> Java 23 standard)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> fetchUser(id));
Future<String> orders = scope.fork(() -> fetchOrders(id));
scope.join().throwIfFailed(); // wait for both; cancel if either fails
return new Result(user.resultNow(), orders.resultNow());
}
// When NOT to use virtual threads:
// CPU-bound tasks — no benefit, use platform threads via ForkJoinPool
// Carrier-thread pinning: synchronized blocks inside virtual threads
// pin the carrier thread (avoid; use ReentrantLock instead)
// Check if current thread is virtual
Thread.currentThread().isVirtual(); // true inside virtual threadPattern Matching (Java 21)
java
// Pattern matching — instanceof (Java 16)
Object obj = 'hello';
if (obj instanceof String s && s.length() > 3) {
System.out.println(s.toUpperCase()); // s is String in this scope
}
// Pattern matching switch (Java 21)
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
record Triangle(double base, double height) implements Shape {}
double area(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.w() * r.h();
case Triangle t -> 0.5 * t.base() * t.height();
}; // exhaustive — compiler checks all permits cases
}
// Guarded patterns (when clause)
String classify(Object obj) {
return switch (obj) {
case Integer i when i < 0 -> 'negative int';
case Integer i when i == 0 -> 'zero';
case Integer i -> 'positive int: ' + i;
case String s when s.isEmpty() -> 'empty string';
case String s -> 'string: ' + s;
case null -> 'null';
default -> 'other: ' + obj.getClass().getSimpleName();
};
}
// Record patterns — destructure in switch
record Point(int x, int y) {}
record Rect(Point topLeft, Point bottomRight) {}
String describeRect(Object obj) {
return switch (obj) {
case Rect(Point(int x1, int y1), Point(int x2, int y2))
when x1 == x2 || y1 == y2 -> 'degenerate';
case Rect(Point(int x1, int y1), Point(int x2, int y2))
-> 'rect ' + (x2-x1) + 'x' + (y2-y1);
default -> 'not a rect';
};
}Sealed Classes (Java 17)
java
// sealed class — restricts which classes can extend it (Java 17)
// All permitted subclasses must be in the same package (or same file)
public sealed class JsonNode
permits JsonNull, JsonBool, JsonNumber, JsonString, JsonArray, JsonObject {}
public final class JsonNull extends JsonNode { public static final JsonNull INSTANCE = new JsonNull(); }
public final class JsonBool extends JsonNode { public boolean value; public JsonBool(boolean v) { value = v; } }
public final class JsonNumber extends JsonNode { public double value; public JsonNumber(double v) { value = v; } }
public final class JsonString extends JsonNode { public String value; public JsonString(String v) { value = v; } }
public non-sealed class JsonArray extends JsonNode { // non-sealed: open again for subclassing
public java.util.List<JsonNode> elements = new java.util.ArrayList<>();
}
public final class JsonObject extends JsonNode {
public java.util.Map<String, JsonNode> fields = new java.util.LinkedHashMap<>();
}
// Sealed + pattern switch = exhaustive, compiler-checked dispatch
String stringify(JsonNode node) {
return switch (node) {
case JsonNull n -> 'null';
case JsonBool b -> String.valueOf(b.value);
case JsonNumber n -> String.valueOf(n.value);
case JsonString s -> '"' + s.value + '"';
case JsonArray a -> '[' + a.elements.stream()
.map(this::stringify)
.collect(java.util.stream.Collectors.joining(',')) + ']';
case JsonObject o -> '{' + o.fields.entrySet().stream()
.map(e -> '"' + e.getKey() + '":' + stringify(e.getValue()))
.collect(java.util.stream.Collectors.joining(',')) + '}';
}; // no default needed — all permits cases covered
}
// sealed interfaces — useful for ADT (algebraic data types)
public sealed interface Result<T> permits Result.Ok, Result.Err {
record Ok<T>(T value) implements Result<T> {}
record Err<T>(String error) implements Result<T> {}
static <T> Result<T> ok(T value) { return new Ok<>(value); }
static <T> Result<T> err(String e) { return new Err<>(e); }
}
Result<Integer> r = Result.ok(42);
switch (r) {
case Result.Ok<Integer>(int v) -> System.out.println('Got ' + v);
case Result.Err<Integer>(var e) -> System.err.println('Error: ' + e);
}Strings
java
// String basics — immutable, interned literals
String s = 'Hello, World!';
s.length(); // 13
s.charAt(0); // 'H'
s.indexOf('o'); // 4
s.lastIndexOf('o'); // 8
s.substring(7, 12); // 'World'
s.toUpperCase(); // 'HELLO, WORLD!'
s.toLowerCase();
s.trim(); // strip leading/trailing whitespace
s.strip(); // Unicode-aware trim (Java 11+)
s.stripLeading(); s.stripTrailing();
s.replace('World', 'Java');
s.contains('World'); // true
s.startsWith('Hello'); s.endsWith('!');
s.isEmpty(); // false
s.isBlank(); // false (Java 11+ — true if only whitespace)
s.split(', '); // ['Hello', 'World!']
String.join('-', 'a', 'b', 'c'); // 'a-b-c'
' hello '.strip().repeat(2); // 'hellohello' (Java 11+)
// String comparison — ALWAYS use equals, never ==
s.equals('Hello, World!');
s.equalsIgnoreCase('hello, world!');
s.compareTo('Hello'); // lexicographic
// String.formatted (Java 15+) — instance version of String.format
'Name: %s, Age: %d'.formatted('Alice', 30); // 'Name: Alice, Age: 30'
// Text blocks — multiline strings (Java 15+)
String json = """
{
'name': 'Alice',
'age': 30
}
"""; // leading whitespace stripped to match closing """
// StringBuilder — mutable, efficient for building strings
StringBuilder sb = new StringBuilder();
sb.append('Hello');
sb.append(', ');
sb.append('World');
sb.insert(5, '!');
sb.delete(5, 6);
sb.reverse();
sb.toString();
// char[] conversion
char[] chars = s.toCharArray();
new String(chars);
// String.chars() stream
s.chars()
.filter(c -> c == 'l')
.count(); // 3I/O & NIO
java
import java.io.*;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
// java.nio.file.Files — high-level NIO2 API (Java 7+)
Path path = Path.of('data', 'file.txt'); // platform-independent
// Read
String text = Files.readString(path); // Java 11+
byte[] bytes = Files.readAllBytes(path);
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
// Write
Files.writeString(path, 'content', StandardOpenOption.CREATE);
Files.write(path, bytes, StandardOpenOption.APPEND);
// Streaming — for large files
try (Stream<String> stream = Files.lines(path)) {
stream.filter(l -> l.startsWith('#')).forEach(System.out::println);
}
// Copy / move / delete
Files.copy(Path.of('src.txt'), Path.of('dst.txt'), StandardCopyOption.REPLACE_EXISTING);
Files.move(path, Path.of('new.txt'));
Files.delete(path);
Files.deleteIfExists(path);
// Directory operations
Files.createDirectories(Path.of('a', 'b', 'c'));
try (Stream<Path> entries = Files.list(Path.of('.'))) {
entries.filter(Files::isRegularFile).forEach(System.out::println);
}
try (Stream<Path> walk = Files.walk(Path.of('src'))) {
walk.filter(p -> p.toString().endsWith('.java')).forEach(System.out::println);
}
// try-with-resources — java.io streams
try (var reader = new BufferedReader(new FileReader('input.txt'));
var writer = new BufferedWriter(new FileWriter('output.txt'))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line.toUpperCase());
writer.newLine();
}
}
// Temp file / directory
Path tmp = Files.createTempFile('prefix-', '.txt');
Files.writeString(tmp, 'temp data');
// Files.delete(tmp) when done
// Attributes
Files.exists(path);
Files.isDirectory(path);
Files.size(path);
Files.getLastModifiedTime(path);Reflection
java
import java.lang.reflect.*;
import java.lang.annotation.*;
// Custom annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@interface Audit {
String value() default 'default';
}
@Audit('UserService')
public class UserService {
private String secret = 'shh';
@Audit('getUser')
public String getUser(int id) { return 'user-' + id; }
}
// Inspect class at runtime
Class<?> cls = UserService.class;
// Or: Class.forName('com.example.UserService')
cls.getName(); // fully qualified name
cls.getSimpleName(); // 'UserService'
cls.getModifiers(); // Modifier.PUBLIC etc.
// Fields
Field[] fields = cls.getDeclaredFields();
Field secret = cls.getDeclaredField('secret');
secret.setAccessible(true); // bypass private
UserService svc = new UserService();
secret.get(svc); // 'shh'
secret.set(svc, 'newsecret');
// Methods
Method[] methods = cls.getDeclaredMethods();
Method m = cls.getMethod('getUser', int.class);
m.invoke(svc, 42); // 'user-42'
// Constructor + instantiation
Constructor<?> ctor = cls.getDeclaredConstructor();
Object instance = ctor.newInstance(); // no-arg constructor
// Annotations
Audit classAnnotation = cls.getAnnotation(Audit.class);
classAnnotation.value(); // 'UserService'
for (Method method : cls.getDeclaredMethods()) {
if (method.isAnnotationPresent(Audit.class)) {
System.out.println(method.getName() + ': ' + method.getAnnotation(Audit.class).value());
}
}
// Generic type info (partial — erasure applies)
Field listField = SomeClass.class.getDeclaredField('items');
ParameterizedType type = (ParameterizedType) listField.getGenericType();
type.getActualTypeArguments(); // [class java.lang.String]Modules (Java 9+)
java
// module-info.java — place in src/main/java/ root (Java 9+)
module com.example.app {
requires java.base; // implicit, always present
requires java.sql; // JDBC
requires java.net.http; // HttpClient
requires transitive com.example.api; // transitive = re-exported to dependents
exports com.example.app.api; // public API
exports com.example.app.model to com.example.client; // qualified export
opens com.example.app.model; // allow deep reflection (e.g. Jackson)
opens com.example.app.config to com.fasterxml.jackson.databind;
uses com.example.spi.Plugin; // ServiceLoader consumer
provides com.example.spi.Plugin
with com.example.app.DefaultPlugin; // ServiceLoader provider
}
// ServiceLoader — lightweight plugin/SPI mechanism
// In provider module's module-info.java:
// provides com.example.spi.Plugin with com.example.impl.MyPlugin;
ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class);
for (Plugin plugin : loader) {
plugin.execute();
}
// Or stream:
loader.stream()
.map(ServiceLoader.Provider::get)
.forEach(Plugin::execute);
// Useful module commands
// Compile: javac --module-source-path src -d out -m com.example.app
// Run: java --module-path out -m com.example.app/com.example.app.Main
// List: java --list-modules
// Describe: java --describe-module java.sql
// Named vs unnamed module
// Named: has a module-info.java
// Unnamed: legacy classpath code; can read all named modules
// Automatic: jar on --module-path without module-info; name from jar filenameTesting (JUnit 5)
java
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.*;
import org.junit.jupiter.params.provider.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calc;
@BeforeEach
void setUp() { calc = new Calculator(); }
@AfterEach
void tearDown() { /* cleanup */ }
@BeforeAll
static void initAll() { /* run once before all tests */ }
@AfterAll
static void tearDownAll() { /* run once after all tests */ }
@Test
void addTwoNumbers() {
assertEquals(5, calc.add(2, 3));
}
@Test
void divideByZeroThrows() {
assertThrows(ArithmeticException.class, () -> calc.divide(10, 0));
}
@Test
void multipleAssertions() {
assertAll('calculator',
() -> assertEquals(4, calc.add(2, 2)),
() -> assertEquals(0, calc.subtract(2, 2)),
() -> assertEquals(4, calc.multiply(2, 2)),
() -> assertEquals(1.0, calc.divide(2, 2))
);
}
@Test
@Disabled('not implemented yet')
void pendingFeature() {}
@ParameterizedTest
@ValueSource(ints = { 1, 2, 3, 4, 5 })
void positiveNumbers(int n) {
assertTrue(calc.isPositive(n));
}
@ParameterizedTest
@CsvSource({ '1,1,2', '2,3,5', '10,20,30' })
void addParameterized(int a, int b, int expected) {
assertEquals(expected, calc.add(a, b));
}
@ParameterizedTest
@MethodSource('provideStrings')
void stringsNotBlank(String s) {
assertFalse(s.isBlank());
}
static java.util.stream.Stream<String> provideStrings() {
return java.util.stream.Stream.of('alice', 'bob', 'carol');
}
@Test
@Timeout(2) // fails if test takes more than 2 seconds
void fastEnough() throws InterruptedException {
Thread.sleep(500);
}
}
// Assertions reference
assertEquals(expected, actual);
assertNotEquals(a, b);
assertTrue(condition);
assertFalse(condition);
assertNull(value);
assertNotNull(value);
assertSame(expected, actual); // reference equality
assertInstanceOf(String.class, obj);
assertArrayEquals(expected, actual);
assertThrows(Exception.class, executable);
assertDoesNotThrow(executable);Best Practices
OOP & Design
java
// OOP & Design Best Practices
// Favor composition over inheritance
class Logger { public void log(String msg) { System.out.println('[LOG] ' + msg); } }
class UserService {
private final Logger logger; // composition — not extending Logger
UserService(Logger logger) { this.logger = logger; }
public void createUser(String name) {
logger.log('Creating user: ' + name);
// ...
}
}
// Program to interfaces, not implementations
List<String> names = new ArrayList<>(); // not ArrayList<String> names
Map<String, Integer> scores = new HashMap<>();
// Immutability by default — final fields, no setters
public final class Money {
private final long cents;
private final String currency;
public Money(long cents, String currency) {
this.cents = cents; this.currency = currency;
}
public Money add(Money other) {
if (!currency.equals(other.currency)) throw new IllegalArgumentException('Currency mismatch');
return new Money(cents + other.cents, currency);
}
}
// Use records for pure data holders
record Coordinate(double lat, double lon) {}
// Sealed classes for closed hierarchies
sealed interface Event permits OrderPlaced, OrderShipped, OrderCancelled {}
record OrderPlaced(int id, String product) implements Event {}
record OrderShipped(int id, String carrier) implements Event {}
record OrderCancelled(int id, String reason) implements Event {}
// Descriptive names; avoid comments that just repeat the code
// BAD: int d; // days elapsed
// GOOD:
int daysElapsed = 7;
// Null Object pattern — return empty instead of null
public List<Order> getOrders(int userId) {
var orders = repo.find(userId);
return orders != null ? orders : List.of(); // never return null from collections
}Collections & Streams
java
// Collections & Streams Best Practices
// Use interface types in declarations
List<String> list = new ArrayList<>();
Map<String,Integer> map = new HashMap<>();
Set<String> set = new HashSet<>();
// Prefer List.of / Map.of for immutable constants
static final List<String> VALID_STATUSES = List.of('PENDING', 'ACTIVE', 'CLOSED');
static final Map<String,Integer> HTTP_CODES = Map.of('OK', 200, 'NOT_FOUND', 404);
// Size hint avoids rehashing
Map<String,Integer> sized = new HashMap<>(expectedSize * 4 / 3 + 1);
// Prefer streams for transformations; manual loops for mutations
List<String> upper = names.stream()
.filter(n -> !n.isBlank())
.map(String::toUpperCase)
.sorted()
.toList(); // Java 16+ — returns unmodifiable list
// Avoid side effects in stream operations (filter/map/flatMap must be stateless)
// BAD: stream.forEach(list::add)
// GOOD:
List<Integer> result = stream.collect(java.util.stream.Collectors.toList());
// Use Collectors.groupingBy for aggregation
Map<String, Long> countByCategory = products.stream()
.collect(java.util.stream.Collectors.groupingBy(Product::category, java.util.stream.Collectors.counting()));
// Use HashMap for O(1) lookups; TreeMap for sorted iteration
Map<String,Product> byId = products.stream()
.collect(java.util.stream.Collectors.toMap(Product::id, p -> p));
// Avoid parallel stream unless data > 10k items AND operations are CPU-bound
// Parallel stream has overhead and can cause issues with ordered collectors
// ConcurrentHashMap for concurrent access
ConcurrentHashMap<String,Integer> hits = new ConcurrentHashMap<>();
hits.merge('endpoint', 1, Integer::sum);Concurrency
java
// Concurrency Best Practices
// Prefer CompletableFuture over raw Thread/Runnable
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> fetchUser(id));
CompletableFuture<Order[]> orderFuture = CompletableFuture.supplyAsync(() -> fetchOrders(id));
CompletableFuture.allOf(userFuture, orderFuture).join();
// Use virtual threads for I/O-bound work (Java 21+)
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = new ArrayList<>();
for (String url : urls) {
futures.add(exec.submit(() -> fetch(url)));
}
for (var f : futures) process(f.get());
}
// Minimize synchronized scope — hold locks briefly
private final Object lock = new Object();
private int counter = 0;
public void increment() {
synchronized (lock) { counter++; } // only the mutation is locked
}
// Prefer AtomicInteger over synchronized for counters
private final AtomicInteger atomicCounter = new AtomicInteger();
atomicCounter.incrementAndGet();
// Avoid double-checked locking — use enum singleton or class holder
static class Holder {
static final Service INSTANCE = new Service();
}
public static Service getInstance() { return Holder.INSTANCE; }
// Use ConcurrentHashMap, CopyOnWriteArrayList for concurrent collections
// Never use synchronized Collections.synchronizedMap in new code
// Never call blocking code inside CompletableFuture.thenApply — use thenApplyAsync
CompletableFuture<String> safe = cf
.thenApplyAsync(v -> blockingTransform(v), Executors.newVirtualThreadPerTaskExecutor());
// Document thread-safety: @ThreadSafe, @NotThreadSafe, @GuardedBy
// from net.jcip.annotations (or javax.annotation.concurrent)Exception Handling
java
// Exception Handling Best Practices
// Use specific exception types
public User findUser(int id) throws UserNotFoundException {
User u = repo.find(id);
if (u == null) throw new UserNotFoundException(id);
return u;
}
// Custom checked exception for recoverable conditions
public class UserNotFoundException extends Exception {
private final int userId;
public UserNotFoundException(int id) {
super('User not found: ' + id);
this.userId = id;
}
public int getUserId() { return userId; }
}
// Unchecked for programming errors
public class InvalidOrderStateException extends RuntimeException {
public InvalidOrderStateException(String state) {
super('Invalid order state: ' + state);
}
}
// Always wrap and preserve cause
try { rawOperation(); }
catch (SQLException e) {
throw new DataAccessException('Failed to load order ' + id, e); // preserve cause
}
// try-with-resources for every AutoCloseable
try (var conn = dataSource.getConnection();
var stmt = conn.prepareStatement(SQL)) {
stmt.setInt(1, id);
return stmt.executeQuery();
}
// Avoid catch(Exception) — catch the narrowest type
// BAD: catch (Exception e) { log(e); }
// GOOD: catch specific types; let unexpected exceptions propagate
// Avoid empty catch blocks
// BAD: catch (InterruptedException e) {}
// GOOD:
try { Thread.sleep(100); }
catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore interrupt flag
throw new RuntimeException('Interrupted', e);
}
// Use Objects.requireNonNull for preconditions
public void process(Order order) {
java.util.Objects.requireNonNull(order, 'order must not be null');
}Modern Java
java
// Modern Java Best Practices (Java 14–21)
// Records for DTOs — replace POJOs
record CreateOrderRequest(String customerId, List<String> items, String currency) {}
record OrderResponse(String id, String status, double total) {}
// Sealed classes + pattern switch = type-safe ADT
sealed interface PaymentResult permits PaymentResult.Success, PaymentResult.Failure {
record Success(String transactionId, double amount) implements PaymentResult {}
record Failure(String code, String message) implements PaymentResult {}
}
String describe(PaymentResult r) {
return switch (r) {
case PaymentResult.Success s -> 'Paid ' + s.amount() + ' (txn: ' + s.transactionId() + ')';
case PaymentResult.Failure f -> 'Failed [' + f.code() + ']: ' + f.message();
};
}
// Text blocks for SQL, JSON, HTML
String sql = """
SELECT u.id, u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.active = true
GROUP BY u.id, u.name
""";
// var in local contexts — reduces verbosity without losing type safety
var users = new ArrayList<User>();
var grouped = users.stream()
.collect(java.util.stream.Collectors.groupingBy(User::department));
// Pattern instanceof replaces cast boilerplate
if (payload instanceof JsonObject obj && obj.has('type')) {
String type = obj.getString('type');
}
// Use List.copyOf, Map.copyOf for defensive copies
public List<String> getTags() {
return List.copyOf(tags); // immutable snapshot, not the live list
}
// String methods (Java 11+): isBlank, strip, repeat, lines
boolean blank = ' '.isBlank(); // true
long lineCount = text.lines().count();
String repeated = '=-'.repeat(20);Performance
java
// Performance Best Practices
// Avoid creating unnecessary objects in loops
// BAD:
for (int i = 0; i < 1000; i++) {
String s = new String('constant'); // allocates 1000 identical objects
}
// GOOD:
String s = 'constant'; // literal is interned; reused
// Use StringBuilder for string concatenation in loops
StringBuilder sb = new StringBuilder(capacity);
for (String item : items) {
sb.append(item).append(', ');
}
if (sb.length() > 2) sb.setLength(sb.length() - 2);
String result = sb.toString();
// Prefer primitives over boxed types in tight loops
// BAD: List<Integer> — autoboxing per element
// GOOD: int[] or IntStream
int sum = IntStream.range(0, 1000).sum();
// Size collections at construction to avoid rehashing
List<String> list = new ArrayList<>(expectedSize);
Map<String,V> map = new HashMap<>(expectedSize * 4 / 3 + 1);
// Lazily initialize expensive fields
private volatile ExpensiveService service;
public ExpensiveService getService() {
if (service == null) {
synchronized (this) {
if (service == null) service = new ExpensiveService();
}
}
return service;
}
// Stream vs loop — streams add overhead; manual loop faster for primitives
// Use streams for clarity; switch to loops if profiler shows hotspot
// Profile before optimizing — use JMH for micro-benchmarks
// @Benchmark public int sum() { return IntStream.range(0,1000).sum(); }
// GC tuning: prefer short-lived objects (GC-friendly), avoid finalizers
// Use try-with-resources instead of finalizers for cleanup
// String.intern() for repeated strings — shares pool entry
// Cache expensive computations with Map.computeIfAbsent
Map<Integer, Long> fibCache = new HashMap<>();
long fib(int n) {
if (n <= 1) return n;
return fibCache.computeIfAbsent(n, k -> fib(k-1) + fib(k-2));
}