Cheat SheetsJava A–ZModern Java (9–21)

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); // Wednesday
6

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 lazily
12

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 view
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/java