Modern Java (9–21) — Cheat Sheet
Java A–Z · 12 topics. Download the PDF or the Instagram carousel and share it.
Cheat Sheet · AiCanCode.org
Modern Java (9–21)
Java A–Z12 topicsQuick revision reference
1
var & Local Type Inference
- ✓var is a compile-time feature — variables are still statically typed, not dynamically typed
- ✓Only valid for local variables with an initialiser; not for fields, params, or return types
- ✓var can capture anonymous class types — enabling method calls not possible with named types
- ✓Avoid var when the type is not obvious from the right-hand side
- ✓var x = null is illegal — the compiler cannot infer the type
- ✓var works in for-each loops and try-with-resources (Java 9+ resources)
VarDemo.java
import java.util.*;
import java.util.stream.*;
public class VarDemo {
public static void main(String[] args) {
// Basic inference
var message = "Hello Java 10"; // inferred: String
var count = 42; // inferred: int
var list = new ArrayList<String>(); // inferred: ArrayList<String>
var map = new HashMap<String, List<Integer>>(); // inferred: complex type
list.add("item");
System.out.println(message.toUpperCase()); // HELLO JAVA 10
// for-each with var
var names = List.of("Alice", "Bob", "Carol");
for (var name : names) {
System.out.print(name.length() + " "); // 5 3 5
}
System.out.println();
// Classic for init
for (var i = 0; i < 3; i++) System.out.print(i + " ");
System.out.println();
// try-with-resources
try (var reader = new java.io.StringReader("test")) {
System.out.println((char) reader.read()); // t
} catch (Exception e) { e.printStackTrace(); }
// INVALID uses:
// var field = "x"; // fields — compile error
// void method(var x) {} // params — compile error
// var x; // no initialiser — compile error
// var x = null; // ambiguous type — compile error
}
}2
Records
- ✓record auto-generates canonical constructor, accessors (x() not getX()), equals, hashCode, toString
- ✓Records are implicitly final — cannot be extended; all components are implicitly private final
- ✓Compact constructor (no param list) auto-assigns fields after body — ideal for validation/normalisation
- ✓Records can implement interfaces, add instance/static methods, and be generic
- ✓Records make perfect Map keys and Set elements — equals/hashCode are correct by default
- ✓Use records for DTOs, value objects, and any class whose purpose is to hold data
RecordDemo.java
import java.util.*;
public class RecordDemo {
// Basic record — all boilerplate generated automatically
record Point(double x, double y) {
// Can add instance methods
public double distanceTo(Point other) {
double dx = this.x - other.x;
double dy = this.y - other.y;
return Math.sqrt(dx*dx + dy*dy);
}
// Static factory method
public static Point origin() { return new Point(0, 0); }
}
// Record implementing interface
interface Shape { double area(); }
record Circle(double radius) implements Shape {
// Compact constructor — validation only, no need to assign fields
Circle {
if (radius <= 0) throw new IllegalArgumentException("radius must be positive");
}
@Override public double area() { return Math.PI * radius * radius; }
}
public static void main(String[] args) {
var p1 = new Point(3, 4);
var p2 = Point.origin();
// Auto-generated accessors: x(), y() (not getX)
System.out.println(p1.x()); // 3.0
System.out.println(p1.y()); // 4.0
// Auto-generated toString
System.out.println(p1); // Point[x=3.0, y=4.0]
// Auto-generated equals + hashCode
var p3 = new Point(3, 4);
System.out.println(p1.equals(p3)); // true
System.out.println(p1 == p3); // false
System.out.println(p1.distanceTo(p2)); // 5.0
var c = new Circle(5);
System.out.printf("Area: %.2f%n", c.area()); // 78.54
try { new Circle(-1); } catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // radius must be positive
}
// Records work perfectly in collections
Set<Point> points = Set.of(p1, p2, new Point(1,1));
System.out.println(points.contains(new Point(3,4))); // true
}
}3
Sealed Classes
- ✓sealed restricts which classes can extend or implement a type using permits.
- ✓Permitted subtypes must be final, sealed, or non-sealed.
- ✓Sealed hierarchies enable exhaustive switch expressions without a default.
- ✓Sealed interfaces + records = concise algebraic data types in Java.
- ✓All permitted subtypes must reside in the same package or module.
Shape.java
// Sealed hierarchy for shapes
public sealed class Shape
permits Circle, Rectangle, Triangle {}
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
public double radius() { return radius; }
}
public final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) {
this.width = width; this.height = height;
}
public double width() { return width; }
public double height() { return height; }
}
public non-sealed class Triangle extends Shape {
// can be extended freely
}4
Pattern Matching
- ✓Type pattern instanceof String s combines check and cast into one.
- ✓Pattern binding variables are flow-scoped — available only where the match holds.
- ✓Switch pattern matching (Java 21) supports type patterns, guarded patterns (when), and null.
- ✓Sealed types + switch patterns = exhaustive dispatch without default.
- ✓Guards use when keyword: case String s when s.length() > 10.
PatternInstanceof.java
// Old style
Object obj = "Hello, Java!";
if (obj instanceof String) {
String s = (String) obj; // redundant cast
System.out.println(s.length());
}
// Pattern matching (Java 16+)
if (obj instanceof String s) {
System.out.println(s.length()); // s is already a String
}
// Combining with conditions
if (obj instanceof String s && s.length() > 5) {
System.out.println("Long string: " + s);
}5
Switch Expressions
- ✓Switch expressions return a value; switch statements do not.
- ✓Arrow cases (case X ->) prevent fall-through and require no break.
- ✓Use yield to return a value from a multi-statement block arm.
- ✓Multiple labels per case: case A, B, C -> ... replaces fall-through patterns.
- ✓Switch expressions must be exhaustive — all inputs must be covered.
SwitchExpression.java
// Traditional switch statement (error-prone)
int day = 3;
String dayName;
switch (day) {
case 1: dayName = "Monday"; break;
case 2: dayName = "Tuesday"; break;
// ...
default: dayName = "Unknown";
}
// Modern switch expression
String dayName = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday";
case 4 -> "Thursday";
case 5 -> "Friday";
default -> "Weekend";
};
System.out.println(dayName); // Wednesday6
Text Blocks
- ✓Text blocks use triple quotes """; opening """ must be followed by a newline.
- ✓Incidental indentation is automatically stripped based on closing """ position.
- ✓Text blocks end with a newline unless closing """ is on the last content line.
- ✓Use \<newline> to suppress a line break, and \s to preserve trailing whitespace.
- ✓Text blocks are ordinary String objects at runtime — no new type.
TextBlock.java
// Old way — messy escape sequences
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 30\n" +
"}";
// Text block — clean and readable
String json = """
{
"name": "Alice",
"age": 30
}
""";
System.out.println(json);
// {
// "name": "Alice",
// "age": 30
// }7
String Formatting
- ✓%s, %d, %f, %n are the most common format specifiers in String.format / printf.
- ✓String::formatted (Java 15+) is the instance-method equivalent of String.format.
- ✓Use StringBuilder in loops to avoid O(n²) string allocations.
- ✓String.join and Collectors.joining are the clean way to join collections.
- ✓String concatenation with + is optimised by the compiler for simple expressions but not loops.
StringFormat.java
String name = "Alice";
int age = 30;
double gpa = 3.756;
// Basic formatting
String s = String.format("Name: %s, Age: %d, GPA: %.2f", name, age, gpa);
// "Name: Alice, Age: 30, GPA: 3.76"
// Width and alignment
System.out.printf("%-15s %5d %8.2f%n", name, age, gpa);
// "Alice 30 3.76"
// Zero-padding, hex
System.out.printf("ID: %08d Hex: %X%n", 42, 255);
// "ID: 00000042 Hex: FF"
// Java 15+ — instance method on String
String result = "Hello, %s! You are %d years old.".formatted(name, age);8
Regular Expressions
- ✓Compile patterns once with Pattern.compile() — compilation is expensive.
- ✓Matcher.find() searches; matches() checks the entire input string.
- ✓Groups: (expr) captures, (?<name>expr) names them, group(n) or group("name") retrieves.
- ✓String.matches/replaceAll/split recompile on each call — use cached Pattern for loops.
- ✓Flags like CASE_INSENSITIVE, MULTILINE, DOTALL modify matching semantics.
PatternMatcher.java
import java.util.regex.*;
// Compile once — expensive operation
private static final Pattern EMAIL =
Pattern.compile("^[\\w.+-]+@[\\w-]+\\.[\\w.]+$");
public boolean isValidEmail(String email) {
return EMAIL.matcher(email).matches();
}
// Extract groups
Pattern date = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher m = date.matcher("Today is 2025-06-15 and tomorrow is 2025-06-16");
while (m.find()) {
System.out.println("Year=" + m.group(1)
+ " Month=" + m.group(2)
+ " Day=" + m.group(3));
}9
Date and Time API
- ✓All java.time types are immutable and thread-safe — use them instead of Date/Calendar.
- ✓LocalDate for dates, LocalTime for times, LocalDateTime for both, ZonedDateTime for timezone-aware.
- ✓DateTimeFormatter is thread-safe; SimpleDateFormat is not.
- ✓Period is date-based (Y/M/D); Duration is time-based (H/M/S/nanos).
- ✓Use Instant for machine timestamps; use ZonedDateTime for human-readable timezone events.
CoreTypes.java
import java.time.*;
// Current values
LocalDate today = LocalDate.now();
LocalTime now = LocalTime.now();
LocalDateTime ldt = LocalDateTime.now();
Instant instant = Instant.now(); // Unix epoch millis
// Specific values
LocalDate dob = LocalDate.of(1990, Month.JUNE, 15);
LocalTime noon = LocalTime.of(12, 0);
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/New_York"));
// Arithmetic — returns new instances (immutable)
LocalDate nextWeek = today.plusDays(7);
LocalDate lastMonth = today.minusMonths(1);
LocalDate nextYear = today.plusYears(1);
// Comparisons
boolean isBefore = dob.isBefore(today);
long daysOld = ChronoUnit.DAYS.between(dob, today);10
Java Module System (JPMS)
- ✓module-info.java declares requires (dependencies) and exports (public API).
- ✓Packages not exported are inaccessible to other modules — strong encapsulation.
- ✓opens grants reflective access; needed by Spring, Hibernate, Jackson.
- ✓Unnamed module = classpath; automatic module = JAR on module path without module-info.
- ✓Use --add-opens and --add-exports as temporary migration helpers, not permanent solutions.
module-info.java
// src/module-info.java
module com.example.myapp {
// Dependencies
requires java.base; // implicit — always required
requires java.sql;
requires com.fasterxml.jackson.databind;
// What we expose
exports com.example.myapp.api;
exports com.example.myapp.model;
// Reflective access for frameworks (Spring, Hibernate, etc.)
opens com.example.myapp.config to spring.core;
opens com.example.myapp.model to com.fasterxml.jackson.databind;
// Service declarations
uses com.example.myapp.spi.PaymentProvider;
provides com.example.myapp.spi.PaymentProvider
with com.example.myapp.impl.StripePaymentProvider;
}11
Functional Programming in Java
- ✓Function<T,R>, Predicate<T>, Consumer<T>, Supplier<T> are the core functional interfaces.
- ✓compose() applies right-to-left; andThen() applies left-to-right.
- ✓Higher-order functions take or return functions — enables decorators, retry, timing wrappers.
- ✓Currying/partial application: fix some arguments, return a function for the rest.
- ✓Pure functions (no side effects, deterministic) are easy to test, cache, and parallelise.
FunctionalInterfaces.java
import java.util.function.*;
// Function<T, R> — transformation
Function<String, Integer> length = String::length;
Function<Integer, String> toStr = Object::toString;
// compose: g.compose(f) = g(f(x))
Function<String, String> lengthStr = toStr.compose(length);
lengthStr.apply("hello"); // "5"
// andThen: f.andThen(g) = g(f(x))
Function<String, String> lengthStr2 = length.andThen(toStr);
lengthStr2.apply("hello"); // "5" (same result, different composition order)
// Predicate<T> — test
Predicate<String> isLong = s -> s.length() > 5;
Predicate<String> isUpper = s -> s.equals(s.toUpperCase());
Predicate<String> isLongAndUpper = isLong.and(isUpper);
Predicate<String> either = isLong.or(isUpper);
Predicate<String> notLong = isLong.negate();
// Consumer<T> — side effect
Consumer<String> print = System.out::println;
Consumer<String> log = s -> logger.info(s);
Consumer<String> printAndLog = print.andThen(log);
// Supplier<T> — lazy value
Supplier<List<String>> newList = ArrayList::new;
Supplier<Instant> now = Instant::now; // evaluated lazily12
Java 21 — Key Features
- ✓Virtual threads (JEP 444) finalised — use Executors.newVirtualThreadPerTaskExecutor().
- ✓Sequenced Collections (JEP 431) adds getFirst/getLast/addFirst/addLast/reversed to ordered collections.
- ✓Record patterns (JEP 440) allow destructuring records in instanceof and switch.
- ✓Pattern matching for switch (JEP 441) finalised — type patterns, guards, null, exhaustiveness.
- ✓String Templates (JEP 430) preview — interpolation with injection-safe template processors.
VThreadsSequenced.java
// Virtual threads — millions of concurrent I/O tasks
try (ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 100_000).forEach(i ->
exec.submit(() -> {
Thread.sleep(Duration.ofMillis(100)); // blocks, but no OS thread wasted
return processRequest(i);
}));
} // all 100,000 tasks complete, ~100ms total
// Sequenced Collections (Java 21 — JEP 431)
// SequencedCollection: List, Deque, LinkedHashSet
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
list.getFirst(); // "a" (was: list.get(0))
list.getLast(); // "c" (was: list.get(list.size()-1))
list.addFirst("z"); // ["z","a","b","c"]
list.addLast("w"); // ["z","a","b","c","w"]
list.reversed(); // ["w","c","b","a","z"] view
// SequencedMap: LinkedHashMap
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("one", 1); map.put("two", 2); map.put("three", 3);
map.firstEntry(); // one=1
map.lastEntry(); // three=3
map.reversed(); // reversed order viewLearn this free with Aria, your AI tutor → AiCanCode.org/learn/java