Cheat SheetsJava A–ZJava Fundamentals

Java Fundamentals — Cheat Sheet

Java A–Z · 10 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Java Fundamentals
Java A–Z10 topicsQuick revision reference
1

JVM, JDK & JRE

  • JDK ⊃ JRE ⊃ JVM — JDK for developing, JRE for running, JVM for executing bytecode
  • Java bytecode is platform-neutral; the JVM translates it to native code per platform
  • JIT compilation converts frequently-executed bytecode to native code for peak performance
  • Heap is shared across threads; each thread has its own Stack, PC Register, and Native Method Stack
  • Class loading uses parent delegation: Bootstrap → Platform → Application ClassLoader
  • From Java 9+, use jlink to create lean custom runtimes instead of shipping a full JRE
HelloWorld.java
// Step 1: Compile source to bytecode
//   javac HelloWorld.java  →  produces HelloWorld.class

// Step 2: JVM executes the bytecode
//   java HelloWorld

public class HelloWorld {
    public static void main(String[] args) {
        // Print JVM details at runtime
        System.out.println("Java version : " + System.getProperty("java.version"));
        System.out.println("JVM name     : " + System.getProperty("java.vm.name"));
        System.out.println("Running on   : " + System.getProperty("os.name"));
    }
}
2

Variables & Data Types

  • Java has exactly 8 primitive types: byte, short, int, long, float, double, char, boolean
  • Instance/static fields default to 0/0.0/false/null; local variables must be explicitly initialised
  • Always use L suffix for long literals > Integer.MAX_VALUE, and f for float literals
  • Reference variables store memory addresses — two references to the same object share mutations
  • Integer caches −128 to 127: use .equals() for wrapper comparisons, never ==
  • var (Java 10+) enables local type inference but the variable remains statically typed
PrimitiveDemo.java
public class PrimitiveDemo {
    // Instance fields → get default values
    int    defaultInt;     // 0
    double defaultDouble;  // 0.0
    boolean defaultBool;   // false

    public static void main(String[] args) {
        byte   b  = 127;
        short  s  = 32_767;          // underscore separator (Java 7+)
        int    i  = 2_147_483_647;
        long   l  = 9_223_372_036_854_775_807L; // L suffix required
        float  f  = 3.14f;                       // f suffix required
        double d  = 3.141592653589793;
        char   c  = 'A';            // or 'A'
        boolean flag = true;

        System.out.println(Integer.MAX_VALUE);   // 2147483647
        System.out.println(Long.MIN_VALUE);      // -9223372036854775808
        System.out.println((int) c);             // 65 — char is numeric!
    }
}
3

Operators & Expressions

  • Integer division truncates toward zero: 7/2 = 3; cast to double for real division
  • Use && and || (short-circuit) for conditions — prevents NPE on chained null checks
  • For object comparison, == tests reference equality; use .equals() for content equality
  • Integer overflow wraps silently; use Math.addExact() to throw ArithmeticException
  • <<n multiplies by 2ⁿ; >>n divides by 2ⁿ; >>>n is unsigned (fills with 0)
  • n & 1 checks odd/even; n & (n-1) clears the lowest set bit — staples of bit manipulation
ArithmeticDemo.java
public class ArithmeticDemo {
    public static void main(String[] args) {
        int a = 7, b = 2;
        System.out.println(a + b);  // 9
        System.out.println(a - b);  // 5
        System.out.println(a * b);  // 14
        System.out.println(a / b);  // 3  (integer division — truncates)
        System.out.println(a % b);  // 1  (remainder)

        // Cast to double for real division
        System.out.println((double) a / b);  // 3.5

        // Negative modulo: sign follows dividend
        System.out.println(-7 % 3);  // -1
        System.out.println(7 % -3);  //  1

        // Pre vs post increment
        int x = 5;
        System.out.println(x++);  // 5 (post: use then increment)
        System.out.println(++x);  // 7 (pre:  increment then use)

        // Overflow wraps silently for int
        System.out.println(Integer.MAX_VALUE + 1);  // -2147483648
        // Use Math.addExact() to throw on overflow
        // Math.addExact(Integer.MAX_VALUE, 1); // throws ArithmeticException
    }
}
4

Control Flow

  • Classic switch falls through by default — always add break or migrate to switch expressions
  • Switch expressions (Java 14+) with -> eliminate fall-through and can return values
  • Pattern matching in switch (Java 21) replaces instanceof chains with guard-aware type matching
  • do-while guarantees at least one execution; while may execute zero times
  • Enhanced for-each is cleaner but gives no index; use classic for when you need the index
  • Labeled break outer exits a named outer loop — useful in matrix traversal problems
IfElseDemo.java
public class IfElseDemo {
    public static String classify(Object obj) {
        // Pattern matching instanceof (Java 16+)
        if (obj instanceof String s) {
            return "String of length " + s.length();
        } else if (obj instanceof Integer i && i > 0) {
            return "Positive integer: " + i;
        } else if (obj == null) {
            return "null";
        } else {
            return "Other: " + obj.getClass().getSimpleName();
        }
    }

    public static void main(String[] args) {
        System.out.println(classify("hello"));  // String of length 5
        System.out.println(classify(42));       // Positive integer: 42
        System.out.println(classify(-5));       // Other: Integer
        System.out.println(classify(null));     // null

        // Ternary
        int score = 75;
        String result = score >= 60 ? "Pass" : "Fail";
        System.out.println(result);  // Pass
    }
}
5

Arrays in Java

  • Array length is fixed at creation; use ArrayList for dynamic sizing
  • Elements default to 0/false (primitives) or null (objects) — no explicit init needed
  • Arrays.sort() uses dual-pivot quicksort for primitives (O(n log n)) and TimSort for objects
  • Arrays.binarySearch() only works correctly on a pre-sorted array
  • System.arraycopy() is the fastest bulk-copy — it calls native code
  • Prefix sum enables O(1) range queries; two-pointer reduces pair-sum from O(n²) to O(n)
ArrayBasics.java
import java.util.Arrays;

public class ArrayBasics {
    public static void main(String[] args) {
        // Declaration and allocation
        int[] scores = new int[5];      // [0, 0, 0, 0, 0]
        scores[0] = 95;
        scores[4] = 87;
        System.out.println(scores.length);  // 5

        // Array initialiser — size inferred
        String[] days = {"Mon", "Tue", "Wed", "Thu", "Fri"};

        // Enhanced for-each
        for (String day : days) System.out.print(day + " ");
        System.out.println();

        // Arrays utility methods
        int[] nums = {5, 2, 8, 1, 9, 3};
        Arrays.sort(nums);                     // in-place sort
        System.out.println(Arrays.toString(nums)); // [1, 2, 3, 5, 8, 9]

        int idx = Arrays.binarySearch(nums, 5); // works only on sorted array
        System.out.println("Index of 5: " + idx);

        int[] copy = Arrays.copyOf(nums, 4);    // first 4 elements
        System.out.println(Arrays.toString(copy)); // [1, 2, 3, 5]

        int[] range = Arrays.copyOfRange(nums, 2, 5); // indices 2..4
        System.out.println(Arrays.toString(range)); // [3, 5, 8]

        int[] filled = new int[4];
        Arrays.fill(filled, 7);
        System.out.println(Arrays.toString(filled)); // [7, 7, 7, 7]
    }
}
6

Strings & StringBuilder

  • Strings are immutable — every modification creates a new String object; the original is unchanged
  • String literals share pooled instances; new String() bypasses the pool — always compare with .equals()
  • Use StringBuilder (not +) in loops to avoid O(n²) concatenation cost
  • strip() (Java 11+) is Unicode-aware; prefer it over trim() for modern code
  • isBlank() (Java 11+) returns true for empty strings AND strings containing only whitespace
  • substring(start, end) — end index is exclusive; substring(7, 11) gives 4 characters
StringPool.java
public class StringPool {
    public static void main(String[] args) {
        // Literals go to the string pool
        String a = "hello";
        String b = "hello";
        System.out.println(a == b);       // true  — same pooled object
        System.out.println(a.equals(b));  // true

        // new String() bypasses the pool
        String c = new String("hello");
        System.out.println(a == c);       // false — different heap objects
        System.out.println(a.equals(c));  // true  — same content

        // intern() returns the pooled reference
        String d = c.intern();
        System.out.println(a == d);       // true  — now pointing to pool

        // Strings are immutable — every "change" creates a new object
        String s = "Java";
        s = s + " 21";   // s now points to a NEW String "Java 21"
        System.out.println(s);            // Java 21
        // The original "Java" object is unchanged (and eventually GC'd)
    }
}
7

Methods in Java

  • Java is strictly pass-by-value — for objects, the reference is copied, not the object
  • Method overloading is resolved at compile time (static polymorphism); overriding is runtime
  • Varargs (int... nums) must be the last parameter; internally treated as an array
  • Every recursive call consumes a stack frame — deep recursion without a base case causes StackOverflowError
  • Java does not optimise tail recursion — for large inputs, prefer iterative solutions
  • Use lo + (hi - lo) / 2 instead of (lo + hi) / 2 in binary search to avoid integer overflow
PassByValue.java
public class PassByValue {

    static void tryChangeInt(int x) {
        x = 99;  // changes local copy only
    }

    static void tryChangeRef(StringBuilder sb) {
        sb.append(" World");  // mutates the OBJECT — caller sees this
    }

    static void tryReplaceRef(StringBuilder sb) {
        sb = new StringBuilder("Replaced"); // changes LOCAL copy of reference only
    }

    public static void main(String[] args) {
        // Primitive — original unchanged
        int n = 5;
        tryChangeInt(n);
        System.out.println(n);  // 5

        // Object — mutation IS visible (both refs point to same object)
        StringBuilder s = new StringBuilder("Hello");
        tryChangeRef(s);
        System.out.println(s);  // Hello World

        // Reassigning ref inside method — caller NOT affected
        tryReplaceRef(s);
        System.out.println(s);  // Hello World (unchanged)
    }
}
8

Classes & Objects

  • If you define any constructor, the compiler no longer generates a default no-arg constructor
  • this() to chain constructors must be the first statement in the constructor body
  • Make fields private; expose state through public getters and validated setters
  • All classes extend Object implicitly — override toString(), equals(), hashCode() for proper behaviour
  • equals() and hashCode() must be overridden together — inconsistency breaks HashMap/HashSet
  • Objects become eligible for GC when all references to them go out of scope
BankAccount.java
public class BankAccount {
    // Fields (state)
    private String owner;
    private double balance;
    private static int totalAccounts = 0; // shared across all instances

    // No-arg constructor
    public BankAccount() {
        this("Unknown", 0.0);  // delegates to 2-arg constructor
    }

    // Parameterised constructor
    public BankAccount(String owner, double initialBalance) {
        if (initialBalance < 0) throw new IllegalArgumentException("Balance cannot be negative");
        this.owner   = owner;        // 'this' disambiguates field from param
        this.balance = initialBalance;
        totalAccounts++;
    }

    // Copy constructor
    public BankAccount(BankAccount other) {
        this(other.owner, other.balance);
    }

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
        balance += amount;
    }

    public boolean withdraw(double amount) {
        if (amount > balance) return false;
        balance -= amount;
        return true;
    }

    // Getters
    public String getOwner()   { return owner; }
    public double getBalance() { return balance; }
    public static int getTotalAccounts() { return totalAccounts; }

    @Override
    public String toString() {
        return "BankAccount[owner=" + owner + ", balance=" + balance + "]";
    }

    public static void main(String[] args) {
        BankAccount acc1 = new BankAccount("Alice", 1000.0);
        BankAccount acc2 = new BankAccount("Bob",   500.0);
        BankAccount acc3 = new BankAccount(acc1); // copy constructor

        acc1.deposit(200);
        acc1.withdraw(50);
        System.out.println(acc1);  // BankAccount[owner=Alice, balance=1150.0]
        System.out.println(BankAccount.getTotalAccounts()); // 3
    }
}
9

Inheritance

  • Java supports single inheritance for classes; use interfaces for multiple type inheritance
  • @Override makes the compiler verify the signature — always use it when overriding
  • super() to call parent constructor must be the first line; compiler inserts it if missing
  • Method dispatch is decided at runtime based on the actual object type (dynamic dispatch)
  • Covariant return types let overriding methods return a more specific type
  • Prefer composition over inheritance when the IS-A relationship is not genuine
Animal.java / Dog.java
public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public String speak() {
        return name + " makes a sound";
    }

    public String describe() {
        return "I am " + name;
    }
}

public class Dog extends Animal {
    private String breed;

    public Dog(String name, String breed) {
        super(name);           // must call parent constructor first
        this.breed = breed;
    }

    @Override                  // compiler verifies signature matches
    public String speak() {
        return name + " barks!";
    }

    @Override
    public String describe() {
        return super.describe() + ", a " + breed;  // reuse + extend
    }

    public static void main(String[] args) {
        Dog dog = new Dog("Rex", "Labrador");
        System.out.println(dog.speak());    // Rex barks!
        System.out.println(dog.describe()); // I am Rex, a Labrador

        // Polymorphic reference — Animal variable holds a Dog
        Animal a = new Dog("Buddy", "Poodle");
        System.out.println(a.speak());      // Buddy barks! (runtime dispatch)
        System.out.println(a instanceof Dog); // true
    }
}
10

Interfaces

  • Interface methods are public+abstract by default; fields are public+static+final
  • A class can implement multiple interfaces — Java's answer to multiple inheritance
  • Default methods (Java 8+) add behaviour to interfaces without breaking existing implementations
  • If two interfaces have the same default method, the implementing class must override it
  • A functional interface has exactly one abstract method — use @FunctionalInterface to enforce this
  • Prefer interfaces for defining contracts and capabilities; use abstract classes when sharing code
Circle.java
public interface Drawable {
    // Abstract method — must be implemented
    void draw();

    // Constant (public static final implicitly)
    int MAX_SIZE = 1000;
}

public interface Resizable {
    void resize(double factor);
}

// Implementing multiple interfaces
public class Circle implements Drawable, Resizable {
    private double radius;

    public Circle(double radius) { this.radius = radius; }

    @Override
    public void draw() {
        System.out.println("Drawing circle with radius " + radius);
    }

    @Override
    public void resize(double factor) {
        radius *= factor;
        System.out.println("Resized to radius " + radius);
    }

    public static void main(String[] args) {
        Circle c = new Circle(5.0);
        c.draw();          // Drawing circle with radius 5.0
        c.resize(2.0);     // Resized to radius 10.0

        // Polymorphic — interface reference
        Drawable d = new Circle(3.0);
        d.draw();
        // d.resize(2.0); // compile error — Drawable doesn't have resize
    }
}
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/java