Getting started
Hello World
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Compile and run:
javac HelloWorld.java
java HelloWorld
Version note
This cheatsheet focuses on modern Java (Java 17+) with coverage of Java 21 features like Virtual Threads and Record Patterns.
| Version | Coverage |
|---|---|
| Java 21+ | Virtual Threads, Sequenced Collections, Record Patterns |
| Java 17+ | Sealed classes, pattern matching enhancements |
| Java 16+ | Records, instanceof patterns |
| Java 11+ | var in lambdas, new String/Files methods |
| Java 8+ | Lambdas, Streams, Optional, CompletableFuture |
Features are marked with version badges (e.g., Java 17+) where version-specific.
Variables
// Primitive types
int age = 25;
double price = 19.99;
boolean isActive = true;
char grade = 'A';
long bigNumber = 100000L;
float decimal = 3.14f;
// Reference types
String name = "John";
Integer boxedInt = 42;
Constants
final int MAX_SIZE = 100;
final String APP_NAME = "MyApp";
// Static constants
public static final double PI = 3.14159;
Type casting
// Implicit casting (widening)
int i = 100;
double d = i; // int to double
// Explicit casting (narrowing)
double d = 9.99;
int i = (int) d; // 9 (loses decimal)
// Object casting
Object obj = "Hello";
String str = (String) obj;
String operations
Common methods
String str = "Hello World";
str.length() // 11
str.charAt(0) // 'H'
str.substring(0, 5) // "Hello"
str.toLowerCase() // "hello world"
str.toUpperCase() // "HELLO WORLD"
str.trim() // Remove whitespace
str.replace("World", "Java") // "Hello Java"
str.contains("World") // true
str.startsWith("Hello") // true
str.endsWith("World") // true
str.split(" ") // ["Hello", "World"]
String comparison
String a = "hello";
String b = "hello";
String c = new String("hello");
a == b // true (same reference)
a == c // false (different objects)
a.equals(c) // true (same content)
a.equalsIgnoreCase("HELLO") // true
a.compareTo(b) // 0 (equal)
String formatting
// String.format()
String.format("Hello %s", "World") // "Hello World"
String.format("Number: %d", 42) // "Number: 42"
String.format("Price: %.2f", 19.99) // "Price: 19.99"
// Text blocks (Java 15+)
String json = """
{
"name": "John",
"age": 30
}
""";
StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
sb.insert(5, ","); // "Hello, World"
sb.reverse(); // "dlroW ,olleH"
sb.delete(0, 6); // "World"
String result = sb.toString();
Arrays
Array basics
// Declaration and initialization
int[] numbers = {1, 2, 3, 4, 5};
String[] names = new String[3];
names[0] = "Alice";
names[1] = "Bob";
names[2] = "Charlie";
// Access
int first = numbers[0];
int length = numbers.length; // ⚠️ No parentheses
Multidimensional arrays
// 2D array
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Access
int element = matrix[1][2]; // 6
// Jagged array
int[][] jagged = new int[3][];
jagged[0] = new int[2];
jagged[1] = new int[3];
jagged[2] = new int[1];
Array operations
import java.util.Arrays;
int[] arr = {5, 2, 8, 1, 9};
// Sorting
Arrays.sort(arr); // {1, 2, 5, 8, 9}
// Searching (on sorted array)
int index = Arrays.binarySearch(arr, 5); // 2
// Copying
int[] copy = Arrays.copyOf(arr, arr.length);
int[] range = Arrays.copyOfRange(arr, 0, 3);
// Filling
Arrays.fill(arr, 0); // All elements = 0
// Comparison
Arrays.equals(arr, copy); // true
// Converting to string
Arrays.toString(arr); // "[1, 2, 5, 8, 9]"
Control flow
Conditionals
// if-else
if (age >= 18) {
System.out.println("Adult");
} else if (age >= 13) {
System.out.println("Teen");
} else {
System.out.println("Child");
}
// Ternary operator
String status = (age >= 18) ? "Adult" : "Minor";
// Switch (traditional)
switch (day) {
case "Monday":
System.out.println("Start of week");
break;
case "Friday":
System.out.println("End of week");
break;
default:
System.out.println("Midweek");
}
Switch expressions (Java 14+)
// Arrow syntax
String result = switch (day) {
case "Monday", "Tuesday" -> "Early week";
case "Wednesday" -> "Midweek";
case "Thursday", "Friday" -> "Late week";
default -> "Weekend";
};
// With yield
int numDays = switch (month) {
case "February" -> {
if (isLeapYear) {
yield 29;
} else {
yield 28;
}
}
case "April", "June", "September", "November" -> 30;
default -> 31;
};
Loops
// for loop
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
// Enhanced for loop
String[] names = {"Alice", "Bob", "Charlie"};
for (String name : names) {
System.out.println(name);
}
// while loop
int count = 0;
while (count < 5) {
System.out.println(count);
count++;
}
// do-while loop
do {
System.out.println(count);
count++;
} while (count < 5);
Loop control
// break - exit loop
for (int i = 0; i < 10; i++) {
if (i == 5) break;
System.out.println(i);
}
// continue - skip iteration
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue;
System.out.println(i); // Odd numbers only
}
// Labeled break
outer: for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) break outer;
System.out.println(i + "," + j);
}
}
Classes and objects
Basic class
public class Person {
// Fields
private String name;
private int age;
// Constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// Method
public void greet() {
System.out.println("Hello, I'm " + name);
}
}
// Usage
Person person = new Person("Alice", 30);
person.greet();
Constructor overloading
public class Rectangle {
private int width;
private int height;
// Default constructor
public Rectangle() {
this(1, 1); // Call another constructor
}
// Parameterized constructor
public Rectangle(int size) {
this(size, size);
}
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
}
Static members
public class Counter {
private static int count = 0; // Shared by all instances
private int id;
public Counter() {
count++;
this.id = count;
}
public static int getCount() {
return count; // Static method
}
public int getId() {
return id;
}
}
// Usage
Counter c1 = new Counter();
Counter c2 = new Counter();
Counter.getCount(); // 2 (call on class, not instance)
Inheritance
// Parent class
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void makeSound() {
System.out.println("Some sound");
}
}
// Child class
public class Dog extends Animal {
public Dog(String name) {
super(name); // Call parent constructor
}
@Override
public void makeSound() {
System.out.println("Woof!");
}
public void fetch() {
System.out.println(name + " is fetching");
}
}
// Usage
Dog dog = new Dog("Buddy");
dog.makeSound(); // "Woof!"
Interfaces and abstract classes
Interfaces
// Interface definition
public interface Drawable {
void draw(); // Abstract method (implicitly public)
// Default method (Java 8+)
default void display() {
System.out.println("Displaying...");
}
// Static method (Java 8+)
static void info() {
System.out.println("Drawable interface");
}
}
// Implementation
public class Circle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing circle");
}
}
Multiple interfaces
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
// Implement multiple interfaces
public class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("Duck flying");
}
@Override
public void swim() {
System.out.println("Duck swimming");
}
}
Abstract classes
public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// Abstract method
public abstract double getArea();
// Concrete method
public void displayColor() {
System.out.println("Color: " + color);
}
}
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
Interface vs Abstract class
// ⚠️ Interface: Only abstract methods and constants (pre-Java 8)
// ⚠️ Abstract class: Can have fields and concrete methods
// ⚠️ A class can implement multiple interfaces but extend only one class
// Choose interface when:
// - Defining capabilities (Flyable, Serializable)
// - Multiple inheritance needed
// Choose abstract class when:
// - Sharing code among related classes
// - Need non-static, non-final fields
Collections
ArrayList
import java.util.ArrayList;
ArrayList<String> list = new ArrayList<>();
// Adding
list.add("Apple");
list.add("Banana");
list.add(0, "Orange"); // Insert at index
// Accessing
String first = list.get(0);
int size = list.size();
// Removing
list.remove(0); // By index
list.remove("Banana"); // By value
list.clear(); // Remove all
// Checking
boolean has = list.contains("Apple");
boolean empty = list.isEmpty();
// Iteration
for (String item : list) {
System.out.println(item);
}
LinkedList
import java.util.LinkedList;
LinkedList<Integer> list = new LinkedList<>();
// Stack operations
list.push(1); // Add to front
list.push(2);
int top = list.pop(); // Remove from front
// Queue operations
list.offer(1); // Add to end
list.offer(2);
int first = list.poll(); // Remove from front
// Deque operations
list.addFirst(1);
list.addLast(2);
int head = list.getFirst();
int tail = list.getLast();
HashMap
import java.util.HashMap;
HashMap<String, Integer> map = new HashMap<>();
// Adding
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 35);
// Accessing
int age = map.get("Alice"); // 25
int def = map.getOrDefault("Dave", 0); // 0
// Checking
boolean has = map.containsKey("Alice");
boolean hasValue = map.containsValue(25);
// Removing
map.remove("Bob");
// Iteration
for (String key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
HashSet
import java.util.HashSet;
HashSet<String> set = new HashSet<>();
// Adding
set.add("Apple");
set.add("Banana");
set.add("Apple"); // Ignored (no duplicates)
// Checking
boolean has = set.contains("Apple");
// Removing
set.remove("Banana");
// Iteration
for (String item : set) {
System.out.println(item);
}
// Set operations
HashSet<Integer> a = new HashSet<>(Arrays.asList(1, 2, 3));
HashSet<Integer> b = new HashSet<>(Arrays.asList(3, 4, 5));
a.addAll(b); // Union
a.retainAll(b); // Intersection
a.removeAll(b); // Difference
TreeMap (sorted)
import java.util.TreeMap;
TreeMap<String, Integer> map = new TreeMap<>();
map.put("Zebra", 3);
map.put("Apple", 1);
map.put("Mango", 2);
// Automatically sorted by key
// Keys: [Apple, Mango, Zebra]
String first = map.firstKey(); // "Apple"
String last = map.lastKey(); // "Zebra"
PriorityQueue
import java.util.PriorityQueue;
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(1);
pq.offer(3);
int min = pq.poll(); // 1 (smallest first)
int peek = pq.peek(); // 3 (next smallest)
Lambda expressions
Basic lambda
// Traditional anonymous class
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// Lambda expression
Runnable r2 = () -> System.out.println("Hello");
// With parameters
Comparator<String> comp = (a, b) -> a.compareTo(b);
// Multi-line
Consumer<String> print = (s) -> {
String result = s.toUpperCase();
System.out.println(result);
};
Functional interfaces
// Predicate - returns boolean
Predicate<Integer> isEven = n -> n % 2 == 0;
isEven.test(4); // true
// Function - transforms input
Function<String, Integer> len = s -> s.length();
len.apply("Hello"); // 5
// Consumer - accepts input, no return
Consumer<String> print = s -> System.out.println(s);
print.accept("Hello");
// Supplier - no input, returns value
Supplier<Double> random = () -> Math.random();
random.get();
// BiFunction - two inputs, one output
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
add.apply(5, 3); // 8
Method references
// Static method reference
Function<String, Integer> parse = Integer::parseInt;
parse.apply("123"); // 123
// Instance method reference
String str = "Hello";
Supplier<Integer> getLen = str::length;
getLen.get(); // 5
// Constructor reference
Supplier<ArrayList<String>> create = ArrayList::new;
ArrayList<String> list = create.get();
// Array constructor reference
Function<Integer, int[]> createArray = int[]::new;
int[] arr = createArray.apply(10);
Common uses
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// forEach
names.forEach(name -> System.out.println(name));
names.forEach(System.out::println); // Method reference
// Comparator
names.sort((a, b) -> a.compareTo(b));
names.sort(String::compareTo);
// Filter with removeIf
names.removeIf(name -> name.startsWith("A"));
// Map computeIfAbsent
Map<String, Integer> map = new HashMap<>();
map.computeIfAbsent("key", k -> k.length());
Streams API
Creating streams
import java.util.stream.*;
// From collection
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
// From array
String[] arr = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
// Using Stream.of()
Stream<String> stream = Stream.of("a", "b", "c");
// Empty stream
Stream<String> empty = Stream.empty();
// Infinite stream
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
// Random numbers
Stream<Double> randoms = Stream.generate(Math::random);
Intermediate operations
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// filter - keep matching elements
numbers.stream()
.filter(n -> n % 2 == 0) // [2, 4, 6]
// map - transform elements
numbers.stream()
.map(n -> n * 2) // [2, 4, 6, 8, 10, 12]
// flatMap - flatten nested streams
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4)
);
nested.stream()
.flatMap(List::stream) // [1, 2, 3, 4]
// distinct - remove duplicates
Stream.of(1, 2, 2, 3, 3, 3)
.distinct() // [1, 2, 3]
// sorted - sort elements
numbers.stream()
.sorted() // [1, 2, 3, 4, 5, 6]
.sorted(Comparator.reverseOrder()) // [6, 5, 4, 3, 2, 1]
// limit - take first n
numbers.stream()
.limit(3) // [1, 2, 3]
// skip - skip first n
numbers.stream()
.skip(2) // [3, 4, 5, 6]
Terminal operations
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// forEach - iterate
numbers.stream()
.forEach(System.out::println);
// collect - to collection
List<Integer> list = numbers.stream()
.filter(n -> n > 2)
.collect(Collectors.toList());
Set<Integer> set = numbers.stream()
.collect(Collectors.toSet());
// toArray - to array
Integer[] arr = numbers.stream()
.toArray(Integer[]::new);
// reduce - combine to single value
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b); // 15
Optional<Integer> max = numbers.stream()
.reduce(Integer::max); // Optional[5]
// count - count elements
long count = numbers.stream()
.filter(n -> n > 2)
.count(); // 3
// anyMatch, allMatch, noneMatch
boolean hasEven = numbers.stream()
.anyMatch(n -> n % 2 == 0); // true
boolean allPositive = numbers.stream()
.allMatch(n -> n > 0); // true
// findFirst, findAny
Optional<Integer> first = numbers.stream()
.filter(n -> n > 3)
.findFirst(); // Optional[4]
Collectors
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// Joining strings
String joined = names.stream()
.collect(Collectors.joining(", ")); // "Alice, Bob, Charlie, David"
// Grouping by
Map<Integer, List<String>> byLength = names.stream()
.collect(Collectors.groupingBy(String::length));
// {3=[Bob], 5=[Alice, David], 7=[Charlie]}
// Partitioning (boolean key)
Map<Boolean, List<String>> byFirstLetter = names.stream()
.collect(Collectors.partitioningBy(s -> s.startsWith("A")));
// {false=[Bob, Charlie, David], true=[Alice]}
// Counting
Map<Integer, Long> counts = names.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
// Summing
int totalLength = names.stream()
.collect(Collectors.summingInt(String::length));
// Statistics
IntSummaryStatistics stats = numbers.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
stats.getAverage();
stats.getMax();
Stream examples
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
// Chain operations
List<String> result = words.stream()
.filter(w -> w.length() > 5)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
// [BANANA, CHERRY]
// Parallel stream (for performance)
long count = words.parallelStream()
.filter(w -> w.startsWith("a"))
.count();
// ⚠️ Order matters for performance
// Good: filter before map (fewer transformations)
words.stream()
.filter(w -> w.length() > 5)
.map(String::toUpperCase);
// Bad: map before filter (more transformations)
words.stream()
.map(String::toUpperCase)
.filter(w -> w.length() > 5);
Exception handling
Try-catch
try {
int result = 10 / 0; // ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero");
e.printStackTrace();
} finally {
System.out.println("Always executes");
}
// Multiple catch blocks
try {
String str = null;
System.out.println(str.length());
} catch (NullPointerException e) {
System.out.println("Null pointer");
} catch (Exception e) {
System.out.println("General error");
}
// Multi-catch (Java 7+)
try {
// code
} catch (IOException | SQLException e) {
System.out.println("IO or SQL error");
}
Try-with-resources
// Auto-closes resources
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
String line = br.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
// BufferedReader automatically closed
// Multiple resources
try (FileInputStream fis = new FileInputStream("in.txt");
FileOutputStream fos = new FileOutputStream("out.txt")) {
// Use streams
}
Throwing exceptions
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException("Not enough funds");
}
balance -= amount;
}
// Runtime exception (unchecked, no throws needed)
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
this.age = age;
}
Custom exceptions
// Checked exception
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) {
super(message);
}
}
// Unchecked exception
public class InvalidAgeException extends RuntimeException {
public InvalidAgeException(String message) {
super(message);
}
}
// Usage
try {
withdraw(1000);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
}
Generics
Generic class
public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
// Usage
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
String str = stringBox.get(); // No casting needed
Box<Integer> intBox = new Box<>();
intBox.set(42);
Generic method
public class Utils {
// Generic method
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
// Multiple type parameters
public static <K, V> void printPair(K key, V value) {
System.out.println(key + ": " + value);
}
}
// Usage
Integer[] numbers = {1, 2, 3};
Utils.printArray(numbers);
Utils.printPair("Name", "Alice");
Bounded type parameters
// Upper bound (extends)
public class NumberBox<T extends Number> {
private T number;
public double getDoubleValue() {
return number.doubleValue(); // Number method
}
}
// Works with Number subclasses
NumberBox<Integer> intBox = new NumberBox<>();
NumberBox<Double> doubleBox = new NumberBox<>();
// NumberBox<String> fails - String is not a Number
// Multiple bounds
public class Container<T extends Number & Comparable<T>> {
// T must be Number AND implement Comparable
}
Wildcards
// Upper bounded wildcard
public void processList(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n.doubleValue());
}
}
// Accepts List<Integer>, List<Double>, etc.
// Lower bounded wildcard
public void addNumbers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
// Accepts List<Integer>, List<Number>, List<Object>
// Unbounded wildcard
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// Accepts any List
// ⚠️ Cannot add to ? extends (read-only)
List<? extends Number> list = new ArrayList<Integer>();
// list.add(1); // Compile error
Number n = list.get(0); // OK
File I/O
Reading files
import java.nio.file.*;
import java.io.*;
// Read entire file (Java 11+)
String content = Files.readString(Path.of("file.txt"));
// Read all lines
List<String> lines = Files.readAllLines(Path.of("file.txt"));
// Read with BufferedReader
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
// Read with Scanner
try (Scanner scanner = new Scanner(new File("file.txt"))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}
}
Writing files
// Write string (Java 11+)
Files.writeString(Path.of("file.txt"), "Hello, World!");
// Write lines
List<String> lines = Arrays.asList("Line 1", "Line 2", "Line 3");
Files.write(Path.of("file.txt"), lines);
// Append to file
Files.writeString(
Path.of("file.txt"),
"New line",
StandardOpenOption.APPEND
);
// Write with BufferedWriter
try (BufferedWriter bw = new BufferedWriter(new FileWriter("file.txt"))) {
bw.write("Hello, World!");
bw.newLine();
bw.write("Second line");
}
File operations
import java.nio.file.*;
Path path = Path.of("file.txt");
// Check existence
boolean exists = Files.exists(path);
// Create file
Files.createFile(path);
// Create directory
Files.createDirectory(Path.of("mydir"));
Files.createDirectories(Path.of("path/to/dir")); // Parent dirs too
// Delete
Files.delete(path);
Files.deleteIfExists(path);
// Copy
Files.copy(
Path.of("source.txt"),
Path.of("dest.txt"),
StandardCopyOption.REPLACE_EXISTING
);
// Move/rename
Files.move(
Path.of("old.txt"),
Path.of("new.txt"),
StandardCopyOption.REPLACE_EXISTING
);
// File attributes
long size = Files.size(path);
boolean isDir = Files.isDirectory(path);
boolean readable = Files.isReadable(path);
Directory listing
// List files in directory
try (DirectoryStream<Path> stream = Files.newDirectoryStream(Path.of("."))) {
for (Path file : stream) {
System.out.println(file.getFileName());
}
}
// List with filter
try (DirectoryStream<Path> stream =
Files.newDirectoryStream(Path.of("."), "*.txt")) {
for (Path file : stream) {
System.out.println(file);
}
}
// Walk directory tree
Files.walk(Path.of("."))
.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(System.out::println);
Modern Java features
Records (Java 16+)
// Compact data class
public record Person(String name, int age) {}
// Equivalent to:
// - private final fields
// - constructor
// - getters (name(), age())
// - equals(), hashCode(), toString()
// Usage
Person person = new Person("Alice", 30);
String name = person.name(); // ⚠️ Getter is name(), not getName()
int age = person.age();
// With validation
public record Person(String name, int age) {
public Person { // Compact constructor
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
}
// Custom methods
public record Point(int x, int y) {
public double distanceFromOrigin() {
return Math.sqrt(x * x + y * y);
}
}
Sealed classes (Java 17+)
// Restrict which classes can extend
public sealed class Shape
permits Circle, Rectangle, Triangle {
}
public final class Circle extends Shape {
// final: cannot be extended
}
public non-sealed class Rectangle extends Shape {
// non-sealed: can be extended by any class
}
public sealed class Triangle extends Shape
permits RightTriangle {
// sealed: only RightTriangle can extend
}
// Pattern matching with sealed classes
String describe(Shape shape) {
return switch (shape) {
case Circle c -> "Circle";
case Rectangle r -> "Rectangle";
case Triangle t -> "Triangle";
// No default needed - compiler knows all cases
};
}
Pattern matching (Java 16+)
// instanceof pattern (Java 16+)
if (obj instanceof String s) {
System.out.println(s.toUpperCase()); // s in scope
}
// Pattern matching for switch (Java 21+)
String format(Object obj) {
return switch (obj) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
case null -> "null";
default -> obj.toString();
};
}
// Guard patterns
String process(Object obj) {
return switch (obj) {
case String s when s.length() > 5 -> "Long string";
case String s -> "Short string";
default -> "Not a string";
};
}
Text blocks (Java 15+)
// Multi-line strings
String json = """
{
"name": "John",
"age": 30,
"city": "New York"
}
""";
String html = """
<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>
""";
// With formatting
String query = """
SELECT id, name, age
FROM users
WHERE age > %d
AND city = '%s'
""".formatted(18, "New York");
Local variable type inference (Java 10+)
// var keyword
var name = "Alice"; // String
var age = 30; // int
var list = new ArrayList<String>(); // ArrayList<String>
// In loops
for (var item : list) {
System.out.println(item);
}
for (var entry : map.entrySet()) {
var key = entry.getKey();
var value = entry.getValue();
}
// ⚠️ Cannot use var without initializer
// var x; // Compile error
// ⚠️ Cannot use var with null
// var x = null; // Compile error
Optional API (Java 8+)
import java.util.Optional;
// Creating Optionals
Optional<String> opt1 = Optional.of("Hello"); // Non-null value
Optional<String> opt2 = Optional.ofNullable(null); // May be null
Optional<String> opt3 = Optional.empty(); // Empty Optional
// Transforming values
Optional<String> name = Optional.of("Alice");
Optional<Integer> length = name.map(String::length); // Optional[5]
Optional<String> upper = name
.map(String::toUpperCase)
.map(s -> s + "!"); // Optional[ALICE!]
// flatMap - for nested Optionals
Optional<String> result = name.flatMap(n -> findAddress(n));
// Filtering
Optional<String> longName = name
.filter(n -> n.length() > 5); // Optional.empty
// Retrieving values
String value = opt1.orElse("default"); // "Hello"
String value2 = opt3.orElse("default"); // "default"
String value3 = opt3.orElseGet(() -> "computed"); // Lazy evaluation
String value4 = opt1.orElseThrow(); // "Hello" or throw
String value5 = opt3.orElseThrow(
() -> new IllegalStateException("No value"));
// Conditional actions
opt1.ifPresent(s -> System.out.println(s)); // Prints "Hello"
opt1.ifPresentOrElse(
s -> System.out.println("Value: " + s),
() -> System.out.println("Empty")
);
// Converting to Stream (Java 9+)
Stream<String> stream = opt1.stream(); // Stream with 1 element
CompletableFuture (Java 8+)
import java.util.concurrent.CompletableFuture;
// Creating CompletableFutures
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "Hello"; // Runs in ForkJoinPool
});
CompletableFuture<Void> voidFuture = CompletableFuture.runAsync(() -> {
System.out.println("Task completed");
});
// Custom executor
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletableFuture<String> cf = CompletableFuture.supplyAsync(
() -> "Hello",
executor
);
// Chaining - thenApply (transform)
CompletableFuture<Integer> length = future
.thenApply(String::length);
// Chaining - thenAccept (consume)
future.thenAccept(s -> System.out.println(s));
// Chaining - thenCompose (for nested CompletableFutures)
CompletableFuture<String> result = future
.thenCompose(s -> fetchUser(s));
// Combining two futures
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = f1.thenCombine(
f2,
(s1, s2) -> s1 + " " + s2
); // "Hello World"
// Wait for all
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2);
all.join(); // Blocks until all complete
// Wait for any
CompletableFuture<Object> any = CompletableFuture.anyOf(f1, f2);
Object firstResult = any.join();
// Error handling - exceptionally
CompletableFuture<String> safe = future
.exceptionally(ex -> {
System.err.println("Error: " + ex.getMessage());
return "default";
});
// Error handling - handle (for both success and error)
CompletableFuture<String> handled = future
.handle((result, ex) -> {
if (ex != null) {
return "Error: " + ex.getMessage();
}
return result.toUpperCase();
});
// Get result (blocking)
String value = future.get(); // May throw checked exceptions
String value2 = future.join(); // Unchecked exceptions
String value3 = future.getNow("default"); // Non-blocking with default
Virtual Threads (Java 21+)
// Creating virtual threads
Thread vThread = Thread.ofVirtual().start(() -> {
System.out.println("Virtual thread running");
});
// Start and get thread
Thread vt = Thread.startVirtualThread(() -> {
System.out.println("Hello from virtual thread");
});
// With name
Thread named = Thread.ofVirtual()
.name("worker-", 0)
.start(() -> {
System.out.println(Thread.currentThread().getName());
});
// Virtual thread executor
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(() -> {
System.out.println("Task 1");
});
executor.submit(() -> {
System.out.println("Task 2");
});
executor.shutdown();
// Structured concurrency with virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var future1 = executor.submit(() -> fetchUser(1));
var future2 = executor.submit(() -> fetchUser(2));
var future3 = executor.submit(() -> fetchUser(3));
// All tasks complete when try-with-resources closes
var user1 = future1.get();
var user2 = future2.get();
var user3 = future3.get();
}
// ⚠️ Virtual threads are lightweight (millions possible)
// ⚠️ Platform threads are heavy (~thousands max)
// ⚠️ Virtual threads are managed by JVM, not OS
Sequenced Collections (Java 21+)
import java.util.*;
// SequencedCollection - new interface
// Implemented by List, Deque, LinkedHashSet
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
// Add at ends
list.addFirst("start"); // ["start", "a", "b", "c"]
list.addLast("end"); // ["start", "a", "b", "c", "end"]
// Get at ends
String first = list.getFirst(); // "start"
String last = list.getLast(); // "end"
// Remove at ends
list.removeFirst(); // ["a", "b", "c", "end"]
list.removeLast(); // ["a", "b", "c"]
// Reversed view (not a copy!)
List<String> reversed = list.reversed();
// reversed: ["c", "b", "a"]
// Changes to reversed affect original
reversed.addFirst("x"); // Adds to end of original!
// list: ["a", "b", "c", "x"]
// reversed: ["x", "c", "b", "a"]
// SequencedSet - implemented by LinkedHashSet
SequencedSet<String> set = new LinkedHashSet<>();
set.add("apple");
set.add("banana");
set.add("cherry");
set.addFirst("aardvark"); // ["aardvark", "apple", "banana", "cherry"]
String first = set.getFirst(); // "aardvark"
set.reversed().forEach(System.out::println); // Reverse iteration
// SequencedMap - implemented by LinkedHashMap
SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
// Put at ends
map.putFirst("start", 0); // {start=0, a=1, b=2, c=3}
map.putLast("end", 4); // {start=0, a=1, b=2, c=3, end=4}
// Get entries at ends
Map.Entry<String, Integer> firstEntry = map.firstEntry(); // start=0
Map.Entry<String, Integer> lastEntry = map.lastEntry(); // end=4
// Reversed view
SequencedMap<String, Integer> reversedMap = map.reversed();
reversedMap.forEach((k, v) -> System.out.println(k + "=" + v));
// Prints: end=4, c=3, b=2, a=1, start=0
Record Patterns (Java 21+)
// Deconstructing records in instanceof
record Point(int x, int y) {}
Object obj = new Point(1, 2);
// Pattern matching with record deconstruction
if (obj instanceof Point(int x, int y)) {
System.out.println("x=" + x + ", y=" + y); // x=1, y=2
}
// Nested records
record Rectangle(Point topLeft, Point bottomRight) {}
Object rect = new Rectangle(new Point(0, 0), new Point(10, 10));
// Nested pattern matching
if (rect instanceof Rectangle(Point(int x1, int y1), Point(int x2, int y2))) {
int width = x2 - x1;
int height = y2 - y1;
System.out.println("Width: " + width + ", Height: " + height);
}
// Record patterns in switch
String describe(Object obj) {
return switch (obj) {
case Point(int x, int y) when x == 0 && y == 0 ->
"Origin";
case Point(int x, int y) when x == y ->
"On diagonal";
case Point(int x, int y) ->
"Point at (" + x + ", " + y + ")";
case null -> "null";
default -> "Unknown";
};
}
// More complex example
record User(String name, int age) {}
record Admin(String name, int age, String role) {}
String getInfo(Object obj) {
return switch (obj) {
case User(String name, int age) when age < 18 ->
name + " is a minor";
case User(String name, int age) ->
name + " is " + age + " years old";
case Admin(String name, int age, String role) ->
name + " is admin with role: " + role;
default -> "Unknown";
};
}
// Generic record patterns
record Box<T>(T value) {}
void process(Object obj) {
if (obj instanceof Box(String s)) {
System.out.println("String in box: " + s);
} else if (obj instanceof Box(Integer i)) {
System.out.println("Integer in box: " + i);
}
}
Common patterns
Singleton pattern
public class Singleton {
private static Singleton instance;
private Singleton() {
// Private constructor
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// Thread-safe eager initialization
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
Builder pattern
public class User {
private final String name; // Required
private final String email; // Required
private final int age; // Optional
private final String phone; // Optional
private User(Builder builder) {
this.name = builder.name;
this.email = builder.email;
this.age = builder.age;
this.phone = builder.phone;
}
public static class Builder {
private final String name;
private final String email;
private int age = 0;
private String phone = "";
public Builder(String name, String email) {
this.name = name;
this.email = email;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public User build() {
return new User(this);
}
}
}
// Usage
User user = new User.Builder("Alice", "alice@example.com")
.age(30)
.phone("555-1234")
.build();
Factory pattern
// Factory method
public abstract class Animal {
public abstract void makeSound();
public static Animal createAnimal(String type) {
return switch (type) {
case "dog" -> new Dog();
case "cat" -> new Cat();
default -> throw new IllegalArgumentException("Unknown type");
};
}
}
// Usage
Animal animal = Animal.createAnimal("dog");
animal.makeSound();
Observer pattern
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String message);
}
class Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
// Usage
Subject subject = new Subject();
subject.attach(msg -> System.out.println("Observer 1: " + msg));
subject.attach(msg -> System.out.println("Observer 2: " + msg));
subject.notifyObservers("Hello!");
Concurrency basics
Creating threads
// Extending Thread
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
MyThread thread = new MyThread();
thread.start(); // ⚠️ Call start(), not run()
// Implementing Runnable
Runnable task = () -> {
System.out.println("Task running");
};
Thread thread = new Thread(task);
thread.start();
// With lambda
new Thread(() -> {
System.out.println("Lambda thread");
}).start();
Thread methods
Thread thread = new Thread(() -> {
// Task
});
thread.start(); // Start execution
thread.join(); // Wait for completion
thread.interrupt(); // Request interruption
boolean alive = thread.isAlive();
// Static methods
Thread.sleep(1000); // Sleep 1 second
Thread.currentThread(); // Get current thread
Thread.yield(); // Hint to scheduler
Synchronized
public class Counter {
private int count = 0;
// Synchronized method
public synchronized void increment() {
count++;
}
// Synchronized block
public void decrement() {
synchronized (this) {
count--;
}
}
public synchronized int getCount() {
return count;
}
}
ExecutorService
import java.util.concurrent.*;
// Thread pool
ExecutorService executor = Executors.newFixedThreadPool(5);
// Submit tasks
executor.submit(() -> {
System.out.println("Task 1");
});
executor.submit(() -> {
System.out.println("Task 2");
});
// Shutdown
executor.shutdown();
// Wait for tasks to complete
executor.awaitTermination(1, TimeUnit.MINUTES);
// Single thread executor
ExecutorService single = Executors.newSingleThreadExecutor();
// Cached thread pool (creates threads as needed)
ExecutorService cached = Executors.newCachedThreadPool();
// Scheduled executor
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(3);
scheduled.schedule(() -> {
System.out.println("Delayed task");
}, 5, TimeUnit.SECONDS);
Gotchas
String comparison
String a = "hello";
String b = "hello";
String c = new String("hello");
// ⚠️ Don't use == for strings
a == b // true (string pool)
a == c // false (different objects)
// ✅ Use equals()
a.equals(c) // true (same content)
Integer caching
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
// ⚠️ Integers -128 to 127 are cached
a == b // true (cached)
c == d // false (not cached)
// ✅ Use equals()
c.equals(d) // true
Array length
int[] arr = {1, 2, 3};
String str = "hello";
// ⚠️ Array length is property, String length is method
arr.length // 3 (no parentheses)
str.length() // 5 (with parentheses)
NullPointerException
String str = null;
// ⚠️ Common NPE sources
str.length() // NPE
str.equals("hello") // NPE
// ✅ Safe alternatives
"hello".equals(str) // false (no NPE)
Objects.equals(str, "hello") // false (no NPE)
Optional.ofNullable(str)
.map(String::length)
.orElse(0); // 0 (no NPE)
Array initialization
// ⚠️ Don't confuse declaration and initialization
int[] arr; // Declared but not initialized
// arr[0] = 1; // NullPointerException
// ✅ Initialize before use
int[] arr = new int[5]; // Initialized with zeros
arr[0] = 1; // OK
Floating-point comparison
double a = 0.1 + 0.2; // 0.30000000000000004
double b = 0.3;
// ⚠️ Don't use == for floating-point
a == b // false
// ✅ Use epsilon comparison
double epsilon = 0.0001;
Math.abs(a - b) < epsilon // true
Stream reuse
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println);
// ⚠️ Cannot reuse stream
// stream.forEach(System.out::println); // IllegalStateException
// ✅ Create new stream or use collection
List<String> list = Arrays.asList("a", "b", "c");
list.stream().forEach(System.out::println);
list.stream().forEach(System.out::println); // OK
Checked vs unchecked exceptions
// ⚠️ Checked exceptions must be caught or declared
public void readFile() throws IOException {
Files.readString(Path.of("file.txt")); // IOException
}
// ⚠️ Unchecked exceptions don't require declaration
public void divide(int a, int b) {
return a / b; // May throw ArithmeticException (unchecked)
}
Also see
- Oracle Java Documentation - Official Java documentation
- Java Language Specification - Detailed language specs
- Java Tutorials - Official Oracle tutorials
- OpenJDK - Open-source Java implementation
- Java API Specification - Complete API reference
- Effective Java - Best practices book by Joshua Bloch