OOP & Advanced Classes — Cheat Sheet
Java A–Z · 18 topics. Download the PDF or the Instagram carousel and share it.
Cheat Sheet · AiCanCode.org
OOP & Advanced Classes
Java A–Z18 topicsQuick revision reference
1
Abstract Classes
- ✓An abstract class cannot be instantiated — only its concrete subclasses can
- ✓A class with even one abstract method must be declared abstract
- ✓Abstract class constructors run via super() — useful to enforce mandatory field initialisation
- ✓The Template Method pattern uses final + abstract: skeleton fixed, steps customisable
- ✓Abstract classes can have instance fields and constructors; interfaces cannot
- ✓A subclass must implement all abstract methods or declare itself abstract
Shape.java
public abstract class Shape {
// Field shared by all shapes
protected String color;
// Constructor — called via super() in subclasses
public Shape(String color) {
this.color = color;
}
// Abstract method — subclasses MUST implement
public abstract double area();
public abstract double perimeter();
// Concrete method — inherited as-is
public void printInfo() {
System.out.printf("%s | color=%s | area=%.2f | perimeter=%.2f%n",
getClass().getSimpleName(), color, area(), perimeter());
}
}
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color); // calls Shape(String)
this.radius = radius;
}
@Override public double area() { return Math.PI * radius * radius; }
@Override public double perimeter() { return 2 * Math.PI * radius; }
}
public class Rectangle extends Shape {
private double width, height;
public Rectangle(String color, double w, double h) {
super(color);
this.width = w; this.height = h;
}
@Override public double area() { return width * height; }
@Override public double perimeter() { return 2 * (width + height); }
public static void main(String[] args) {
Shape[] shapes = {
new Circle("red", 5),
new Rectangle("blue", 4, 6)
};
for (Shape s : shapes) s.printInfo();
// Circle | color=red | area=78.54 | perimeter=31.42
// Rectangle | color=blue | area=24.00 | perimeter=20.00
}
}2
Encapsulation
- ✓Make fields private; expose state through public methods with validation
- ✓Immutable classes: final class, final fields, no setters, defensive copies of mutable fields
- ✓"Tell, Don't Ask" — push logic into the class instead of extracting state and computing outside
- ✓Derived values (getFahrenheit from celsius) eliminate redundant fields and synchronisation bugs
- ✓Every public method is a public API commitment — minimise the surface area
- ✓Validate in the constructor and setters so the object is always in a valid state
Temperature.java
public class Temperature {
private double celsius; // private — callers cannot access directly
public Temperature(double celsius) {
setCelsius(celsius); // reuse setter validation in constructor
}
// Getter
public double getCelsius() { return celsius; }
// Setter with validation
public void setCelsius(double celsius) {
if (celsius < -273.15)
throw new IllegalArgumentException("Below absolute zero: " + celsius);
this.celsius = celsius;
}
// Derived values — no extra field needed, calculated on demand
public double getFahrenheit() { return celsius * 9.0 / 5.0 + 32; }
public double getKelvin() { return celsius + 273.15; }
@Override
public String toString() {
return String.format("%.1f°C / %.1f°F / %.1fK", celsius, getFahrenheit(), getKelvin());
}
public static void main(String[] args) {
Temperature t = new Temperature(100);
System.out.println(t); // 100.0°C / 212.0°F / 373.2K
t.setCelsius(0);
System.out.println(t.getKelvin()); // 273.15
// t.celsius = -300; // compile error — field is private
}
}3
Polymorphism
- ✓Runtime polymorphism: method dispatch is based on the actual object type, not the variable's declared type
- ✓Fields and static methods are NOT polymorphic — they are resolved by the declared (compile-time) type
- ✓Upcasting is always safe and implicit; downcasting requires instanceof guard + explicit cast
- ✓Pattern-matching instanceof (Java 16+) combines the check and cast: if (a instanceof Dog dog)
- ✓Overloaded method selection is based on the declared parameter types at compile time
- ✓Programming to a supertype (interface/abstract class) lets new subtypes be added without changing callers
PaymentDemo.java
abstract class Payment {
protected double amount;
public Payment(double amount) { this.amount = amount; }
public abstract String process(); // each subclass processes differently
public void printReceipt() {
System.out.println("Receipt: " + process() + " — $" + amount);
}
}
class CreditCard extends Payment {
private String last4;
public CreditCard(double amt, String last4) { super(amt); this.last4 = last4; }
@Override public String process() { return "Credit card *" + last4; }
}
class PayPal extends Payment {
private String email;
public PayPal(double amt, String email) { super(amt); this.email = email; }
@Override public String process() { return "PayPal (" + email + ")"; }
}
class Crypto extends Payment {
private String wallet;
public Crypto(double amt, String wallet) { super(amt); this.wallet = wallet; }
@Override public String process() { return "Crypto wallet " + wallet; }
}
public class PaymentDemo {
// Works for ALL Payment subtypes — past, present, and future
static void checkout(Payment payment) {
payment.printReceipt(); // dynamic dispatch selects correct process()
}
public static void main(String[] args) {
Payment[] payments = {
new CreditCard(99.99, "4242"),
new PayPal(49.00, "user@example.com"),
new Crypto(200.00, "0x1A2B..."),
};
for (Payment p : payments) checkout(p);
// Receipt: Credit card *4242 — $99.99
// Receipt: PayPal (user@example.com) — $49.0
// Receipt: Crypto wallet 0x1A2B... — $200.0
}
}4
Exception Handling
- ✓Checked exceptions (extend Exception) must be caught or declared; unchecked (extend RuntimeException) do not
- ✓Never catch Error — JVM errors like OutOfMemoryError are unrecoverable
- ✓finally always runs; try-with-resources (Java 7+) is the modern way to close AutoCloseable resources
- ✓Always chain exceptions with cause: throw new MyException("msg", originalException)
- ✓Never swallow exceptions with an empty catch block — at minimum log them
- ✓Catch the narrowest applicable exception type; avoid catching bare Exception or Throwable
ExceptionDemo.java
import java.io.IOException;
public class ExceptionDemo {
// Checked exception — must be caught or declared with throws
static String readFile(String path) throws IOException {
if (path == null) throw new IOException("Path cannot be null");
return "file content";
}
public static void main(String[] args) {
// Basic try-catch-finally
try {
String content = readFile(null);
System.out.println(content);
} catch (IOException e) {
System.out.println("IO error: " + e.getMessage()); // IO error: Path cannot be null
} finally {
System.out.println("finally always runs"); // always executes
}
// Multi-catch (Java 7+) — handle multiple types in one block
try {
String s = null;
int[] arr = new int[3];
s.length(); // NullPointerException
arr[5] = 1; // ArrayIndexOutOfBoundsException
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
System.out.println("Caught: " + e.getClass().getSimpleName());
}
// Exception chaining — wrap root cause
try {
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
throw new RuntimeException("Calculation failed", e); // wraps original
}
} catch (RuntimeException e) {
System.out.println(e.getMessage()); // Calculation failed
System.out.println(e.getCause().getMessage()); // / by zero
}
}
}5
Enums
- ✓Enum constants are instances of the enum class — fully type-safe, no invalid values possible
- ✓Enum constructors are implicitly private; values(), ordinal(), name(), valueOf() are built-in
- ✓Enums with abstract methods let each constant carry its own behaviour
- ✓EnumSet uses a bit-vector internally — always prefer over HashSet<YourEnum>
- ✓EnumMap is array-backed by ordinal — faster than HashMap<YourEnum, V>
- ✓Enums are the best Singleton implementation: thread-safe and serialisation-safe by the JVM spec
EnumBasics.java
public enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
public boolean isWeekend() {
return this == SATURDAY || this == SUNDAY;
}
}
public class EnumBasics {
public static void main(String[] args) {
Day today = Day.WEDNESDAY;
System.out.println(today.name()); // WEDNESDAY
System.out.println(today.ordinal()); // 2 (zero-based)
System.out.println(today.isWeekend()); // false
// Iterate all constants
for (Day d : Day.values()) {
System.out.print(d + " ");
}
System.out.println();
// Parse from String
Day friday = Day.valueOf("FRIDAY");
System.out.println(friday.isWeekend()); // false
// Switch expression — compiler verifies exhaustiveness
String type = switch (today) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
case SATURDAY, SUNDAY -> "Weekend";
};
System.out.println(type); // Weekday
}
}6
Wrapper Classes
- ✓All numeric wrappers extend Number — intValue(), doubleValue() etc. convert between types
- ✓Integer cache covers −128 to 127: use .equals() for all wrapper comparisons, never ==
- ✓Unboxing a null wrapper throws NullPointerException — guard with null checks or Optional
- ✓parseInt() vs valueOf(): parseInt returns a primitive; valueOf returns a (possibly cached) wrapper
- ✓Autoboxing in tight loops is costly — use primitive arrays or streams for numeric aggregation
- ✓Character has rich utility: isDigit(), isLetter(), isWhitespace(), toUpperCase(), toLowerCase()
WrapperConstants.java
public class WrapperConstants {
public static void main(String[] args) {
// Range constants
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Integer.MIN_VALUE); // -2147483648
System.out.println(Long.MAX_VALUE); // 9223372036854775807
System.out.println(Double.MAX_VALUE); // 1.7976931348623157E308
// Parsing — String → primitive
int i = Integer.parseInt("42");
long l = Long.parseLong("9876543210");
double d = Double.parseDouble("3.14");
boolean b = Boolean.parseBoolean("true"); // case-insensitive
System.out.println(i + " " + l + " " + d + " " + b);
// Conversion methods
System.out.println(Integer.toBinaryString(255)); // 11111111
System.out.println(Integer.toHexString(255)); // ff
System.out.println(Integer.toOctalString(8)); // 10
System.out.println(Integer.bitCount(255)); // 8
System.out.println(Integer.reverse(1)); // MSB becomes LSB
// Number hierarchy — any Number can give any numeric primitive
Number n = 3.7; // Double is-a Number
System.out.println(n.intValue()); // 3 (truncates)
System.out.println(n.longValue()); // 3
System.out.println(n.doubleValue()); // 3.7
// Character utilities
System.out.println(Character.isDigit('5')); // true
System.out.println(Character.isLetter('A')); // true
System.out.println(Character.toLowerCase('Z')); // z
System.out.println(Character.isWhitespace(' ')); // true
}
}7
Type Casting
- ✓Widening (byte→int→long→double) is implicit; narrowing requires an explicit cast and can lose data
- ✓Narrowing truncates toward zero — (int) 3.9 = 3; it never rounds
- ✓Byte arithmetic is promoted to int — (byte)(a + b) needs the explicit cast back
- ✓Upcasting is always safe; downcasting needs instanceof guard or throws ClassCastException
- ✓Pattern-matching instanceof (Java 16+) is the modern way: if (a instanceof Dog dog)
- ✓Array covariance (String[] IS-A Object[]) allows upcast but ArrayStoreException on wrong insert
PrimitiveCasting.java
public class PrimitiveCasting {
public static void main(String[] args) {
// Widening — implicit, safe
int i = 100;
long l = i; // int → long: implicit
float f = l; // long → float: implicit (may lose precision!)
double d = f; // float → double: implicit
System.out.println(d); // 100.0
// Precision loss: long → float
long big = 123_456_789_123L;
float approx = big; // implicit widening
System.out.println(big); // 123456789123
System.out.println(approx); // 1.23456794E11 — precision lost!
// Narrowing — explicit cast required
double pi = 3.14159;
int truncated = (int) pi; // truncates, does NOT round
System.out.println(truncated); // 3
// Truncation wraps on overflow
int big2 = 300;
byte small = (byte) big2; // 300 % 256 = 44
System.out.println(small); // 44
// Numeric promotion in expressions
byte a = 10, b = 20;
// byte result = a + b; // compile error! a+b is promoted to int
byte result = (byte)(a + b); // explicit cast back
System.out.println(result); // 30
// char ↔ int casting
char c = 'A';
int ascii = c; // widening char → int
System.out.println(ascii); // 65
char back = (char)(ascii + 1); // narrowing int → char
System.out.println(back); // B
}
}8
Packages & Imports
- ✓Package name maps 1-to-1 to directory structure — javac enforces this
- ✓Package-private (no modifier) is the default — accessible within the same package only
- ✓Use reverse-domain naming: com.company.project.module — never use java.* or javax.*
- ✓Static imports (import static) bring static members into scope — great for Math, assertions
- ✓Name conflicts: import only one; use the fully-qualified name for the other
- ✓Never use the default (unnamed) package in production — classes there cannot be imported
Package structure
// File: src/com/example/model/User.java
package com.example.model;
public class User {
private String email; // only this class
String username; // package-private — visible to all in com.example.model
protected int age; // package + subclasses
public String displayName; // everywhere
public User(String email, String username, int age) {
this.email = email;
this.username = username;
this.age = age;
this.displayName = username;
}
public String getEmail() { return email; }
}
// File: src/com/example/model/UserRepository.java
package com.example.model; // same package
public class UserRepository {
public User findByUsername(String username) {
User u = new User("a@b.com", username, 25);
// Can access package-private field directly — same package
System.out.println("Looking for: " + u.username);
return u;
}
}
// File: src/com/example/service/UserService.java
package com.example.service; // different package
import com.example.model.User; // must import
public class UserService {
public void greet(User user) {
System.out.println("Hello, " + user.displayName); // public — OK
// user.username; // compile error — package-private, different package
// user.age; // compile error — protected, not a subclass
}
}9
static Keyword
- ✓Static fields are class-level — one shared copy for all instances; changes affect all
- ✓Static methods cannot access instance fields or use this — only static members
- ✓Static initialisers run once when the class is first loaded, in declaration order
- ✓Static nested classes have no enclosing-instance reference; non-static inner classes do
- ✓Non-static inner classes holding outer references can cause memory leaks if they outlive the outer object
- ✓Call static members on the class name (Counter.getCount()), not on an instance variable
Counter.java
public class Counter {
// Static field — one per class, shared by all instances
private static int count = 0;
// Instance field — one per object
private final int id;
private String name;
public Counter(String name) {
this.name = name;
this.id = ++count; // increment shared counter
}
// Static method — belongs to class, no 'this'
public static int getCount() { return count; }
// Static constant — public static final by convention in UPPER_SNAKE_CASE
public static final int MAX_INSTANCES = 100;
// Static utility method (no state needed)
public static boolean isValidName(String name) {
return name != null && !name.isBlank() && name.length() <= 50;
}
@Override public String toString() { return "Counter#" + id + "(" + name + ")"; }
public static void main(String[] args) {
Counter a = new Counter("alpha");
Counter b = new Counter("beta");
Counter c = new Counter("gamma");
System.out.println(a); // Counter#1(alpha)
System.out.println(Counter.getCount()); // 3 — class-level call
System.out.println(c.getCount()); // also 3 — works but misleading
System.out.println(Counter.isValidName("hello")); // true
System.out.println(Counter.MAX_INSTANCES); // 100
}
}10
final Keyword
- ✓final variable: assign once — for fields, in declaration or constructor (blank final)
- ✓final does not make an object immutable — it only prevents reassigning the reference
- ✓final method: cannot be overridden — use for security-sensitive or template skeleton methods
- ✓final class: cannot be subclassed — String and all wrappers are final
- ✓Effectively final (Java 8+): never reassigned after init; can be captured in lambdas
- ✓static final primitive constants are inlined by the compiler at call sites
FinalDemo.java
import java.util.ArrayList;
import java.util.List;
public class FinalDemo {
// Static constant — public static final, UPPER_SNAKE_CASE
public static final double TAX_RATE = 0.18;
// Blank final — assigned in constructor, not at declaration
private final String id;
private final List<String> items = new ArrayList<>(); // final ref, mutable object!
public FinalDemo(String id) {
this.id = id; // assigned exactly once
// this.id = "other"; // compile error — already assigned
}
public void addItem(String item) {
items.add(item); // OK — final only prevents reassigning the reference
// items = new ArrayList<>(); // compile error — cannot reassign final field
}
public static void main(String[] args) {
FinalDemo demo = new FinalDemo("order-42");
demo.addItem("book");
demo.addItem("pen");
System.out.println(demo.items); // [book, pen]
// final local variable — effectively like a constant in scope
final int MAX = 10;
// MAX = 20; // compile error
// Effectively final (Java 8+) — not declared final but never reassigned
String prefix = "Hello"; // effectively final
Runnable r = () -> System.out.println(prefix + " World"); // OK in lambda
r.run();
// String prefix2 = "Hello";
// prefix2 = "Hi"; // reassigned — NOT effectively final
// Runnable r2 = () -> System.out.println(prefix2); // compile error
}
}11
Inner and Nested Classes
- ✓Static nested class: no enclosing instance reference; use for Builder, helper types.
- ✓Inner class: implicitly holds enclosing instance reference; can cause memory leaks.
- ✓Anonymous class: one-shot inline implementation; prefer lambda for single-method interfaces.
- ✓Local class: defined inside a method; captures effectively-final variables.
- ✓To create an inner class instance from outside: outer.new Inner().
StaticNested.java
public class Outer {
private static int staticField = 10;
private int instanceField = 20;
// Static nested — no reference to Outer instance
public static class Builder {
private String name;
private int age;
public Builder name(String name) {
this.name = name; return this;
}
public Builder age(int age) {
this.age = age; return this;
}
public Person build() {
return new Person(name, age);
}
// Can access outer static members
void show() { System.out.println(staticField); }
// void bad() { System.out.println(instanceField); } // ERROR
}
}
// Instantiate without an Outer instance
Outer.Builder b = new Outer.Builder().name("Alice").age(30);12
Annotations
- ✓@Override, @Deprecated, @SuppressWarnings, @FunctionalInterface are built-in compiler annotations.
- ✓Define custom annotations with @interface; control scope with @Retention and @Target.
- ✓RetentionPolicy.RUNTIME is required for runtime access via reflection.
- ✓ElementType controls where an annotation can be applied (METHOD, TYPE, FIELD, etc.).
- ✓Annotation processors (APT) run at compile time and can generate new source files.
BuiltinAnnotations.java
public class Animal {
public String sound() { return "..."; }
}
public class Dog extends Animal {
@Override // compile error if sound() doesn't exist in Animal
public String sound() { return "Woof"; }
@Deprecated(since = "2.0", forRemoval = true)
public void oldMethod() { /* will be removed */ }
@SuppressWarnings("unchecked")
public void uncheckedOp(Object obj) {
List<String> list = (List<String>) obj; // suppresses warning
}
}
@FunctionalInterface
interface Transformer<T, R> {
R transform(T input);
// adding a second abstract method here → compile error
}13
Reflection API
- ✓Class<?> is the entry point; obtain via .class, getClass(), or Class.forName().
- ✓getDeclaredXxx() returns all members; getXxx() returns only public/inherited ones.
- ✓setAccessible(true) bypasses private access — use cautiously; blocked by JPMS opens.
- ✓Method.invoke() is slower than direct calls; use MethodHandle for performance-critical reflection.
- ✓Generic types are erased at runtime but preserved in field/method signatures — accessible via getGenericType().
ReflectFields.java
import java.lang.reflect.*;
Class<?> cls = User.class;
// Basic info
System.out.println(cls.getName()); // com.example.User
System.out.println(cls.getSimpleName()); // User
System.out.println(cls.getSuperclass()); // class java.lang.Object
// Fields
for (Field field : cls.getDeclaredFields()) {
System.out.printf(" %-20s [%s]%n",
field.getName(),
field.getType().getSimpleName());
}
// Access private field
Field nameField = cls.getDeclaredField("name");
nameField.setAccessible(true); // bypass access control
User user = new User("Alice", 30);
String name = (String) nameField.get(user);
System.out.println("Private name: " + name);14
Advanced Generics
- ✓PECS: Producer Extends (read), Consumer Super (write).
- ✓<? extends T> allows reading as T; <? super T> allows writing T into the structure.
- ✓Recursive bounds <T extends Comparable<T>> constrain T to self-comparable types.
- ✓Type erasure: List<String> and List<Integer> are the same class at runtime.
- ✓Workarounds for erasure: Class<T> token, TypeReference anonymous subclass, or @SuppressWarnings("unchecked") cast.
Wildcards.java
// Upper bounded — read from (producer)
public double sumList(List<? extends Number> list) {
double sum = 0;
for (Number n : list) sum += n.doubleValue(); // can READ
// list.add(1.5); // COMPILE ERROR — can't write
return sum;
}
sumList(new ArrayList<Integer>()); // works
sumList(new ArrayList<Double>()); // works
// Lower bounded — write to (consumer)
public void addNumbers(List<? super Integer> list) {
list.add(1); // can WRITE Integer or subtype
list.add(2);
// Integer i = list.get(0); // COMPILE ERROR — can only get Object
}
addNumbers(new ArrayList<Integer>()); // works
addNumbers(new ArrayList<Number>()); // works
addNumbers(new ArrayList<Object>()); // works
// PECS in Collections.copy
// src is producer (we read from it) → extends
// dest is consumer (we write to it) → super
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T t : src) dest.add(t);
}15
String Internals
- ✓String is immutable — every "modification" creates a new object.
- ✓String literals are pooled; new String("x") creates a separate heap object.
- ✓Always use equals() for content comparison, never == (unless you know both are interned).
- ✓Java 9+ Compact Strings store Latin-1 text as byte[] — half the memory of char[].
- ✓substring() creates a new String (O(n) copy) — not O(1) as in some other languages.
StringPool.java
// String literals are pooled automatically
String a = "hello";
String b = "hello";
System.out.println(a == b); // true — same pool object
System.out.println(a.equals(b)); // true — same content
// new String() always creates a new heap object
String c = new String("hello");
System.out.println(a == c); // false — different object
System.out.println(a.equals(c)); // true — same content
// intern() moves heap string into pool
String d = c.intern();
System.out.println(a == d); // true — now same pool object
// String pool lives in Heap (since Java 7)
// Pre-Java 7 it was in PermGen — caused OOM for large apps
// Immutability means every "modification" creates a new String
String s = "hello";
s.concat(" world"); // returns new String, original unchanged
String result = s.concat(" world"); // must capture the return16
hashCode and equals Contract
- ✓equals() true → hashCode() must be equal. hashCode() equal does NOT imply equals() true.
- ✓Always override hashCode when you override equals — IDEs and Lombok do this automatically.
- ✓Use Objects.hash(field1, field2, ...) for a clean, collision-resistant hashCode.
- ✓Cache hashCode in immutable objects for performance (see String).
- ✓Bad hashCode (e.g. constant) degrades HashMap to O(n) — evenly distributing hashes is important.
BrokenContract.java
// BROKEN — equals without hashCode
public class BrokenPoint {
int x, y;
@Override
public boolean equals(Object o) {
if (!(o instanceof BrokenPoint p)) return false;
return x == p.x && y == p.y;
}
// hashCode not overridden — uses Object's identity hash
}
BrokenPoint p1 = new BrokenPoint(1, 2);
BrokenPoint p2 = new BrokenPoint(1, 2);
System.out.println(p1.equals(p2)); // true
System.out.println(p1.hashCode() == p2.hashCode()); // false (probably)
Set<BrokenPoint> set = new HashSet<>();
set.add(p1);
set.contains(p2); // FALSE — looks in wrong bucket!
Map<BrokenPoint, String> map = new HashMap<>();
map.put(p1, "origin");
map.get(p2); // NULL — same bug17
Immutability
- ✓Immutable classes: final class, private final fields, no setters, defensive copies in/out.
- ✓Defensive copy in constructor prevents the caller from mutating internal state indirectly.
- ✓Never return a mutable internal field reference — return a copy or unmodifiable view.
- ✓java.time types (LocalDate, Instant) are immutable — prefer them over java.util.Date.
- ✓Records are shallowly immutable — use List.copyOf() in compact constructors for mutable components.
ImmutableClass.java
// Truly immutable class
public final class DateRange { // 1. final class
private final LocalDate start; // 2. private final
private final LocalDate end;
private final List<String> notes; // mutable field!
public DateRange(LocalDate start, LocalDate end, List<String> notes) {
if (start.isAfter(end))
throw new IllegalArgumentException("start must be before end");
this.start = start;
this.end = end;
this.notes = List.copyOf(notes); // 4. defensive copy → unmodifiable
}
public LocalDate getStart() { return start; } // 3. no setters
public LocalDate getEnd() { return end; }
public List<String> getNotes() {
return notes; // 5. safe — List.copyOf returned an unmodifiable list
}
// Wither method — returns new instance with one field changed
public DateRange withStart(LocalDate newStart) {
return new DateRange(newStart, end, notes);
}
}18
Anonymous Classes
- ✓Anonymous class = nameless class declared and instantiated inline: new Interface() { ... }.
- ✓Can implement interfaces or extend classes; can have fields and methods but no constructors.
- ✓Captures effectively-final variables from the enclosing scope.
- ✓Use lambda for single-method functional interfaces; use anonymous class for multiple methods or local state.
- ✓TypeReference<List<User>>(){} is an important anonymous class use case that cannot be a lambda.
AnonymousClassSyntax.java
// Anonymous class implementing an interface
Runnable r = new Runnable() {
private int runCount = 0; // can have fields
@Override
public void run() {
runCount++;
System.out.println("Run #" + runCount);
}
};
r.run(); // Run #1
r.run(); // Run #2
// Anonymous class extending an abstract class
abstract class Greeter {
abstract String greeting();
void greet(String name) {
System.out.println(greeting() + ", " + name + "!");
}
}
Greeter formal = new Greeter() {
@Override
String greeting() { return "Good day"; }
// inherits greet() from Greeter
};
formal.greet("Alice"); // Good day, Alice!
// Capturing effectively-final variable from enclosing scope
String prefix = "Hello"; // effectively final
Greeter casual = new Greeter() {
@Override String greeting() { return prefix; } // captures prefix
};Learn this free with Aria, your AI tutor → AiCanCode.org/learn/java