Java.
A comprehensive, interactive masterclass covering every major concept — from JVM internals and OOP fundamentals to modern Java 21 features, the Streams API, and concurrent programming.
§ 01
JVM Architecture
Java compiles to platform-neutral bytecode (.class), which the Java Virtual Machine (JVM) interprets or JIT-compiles. This is what makes Java "write once, run anywhere."
loads .class files
hot code → native
automatic memory
// Every Java program starts here public class HelloWorld { // Entry point — JVM calls main() public static void main(String[] args) { // Print to standard output System.out.println("Hello, World!"); // Print without newline System.out.print("No newline"); // Formatted print (like printf) System.out.printf( "Name: %s, Age: %d%n", "Alice", 30 ); } }
§ 02
Primitives & Types
Java is statically typed. It has 8 primitive types (stored by value on the stack) and Reference types (objects stored on the heap). Every primitive has a Wrapper class.
| Type | Size | Range / Notes | Default | Wrapper |
|---|---|---|---|---|
| byte | 8-bit | -128 to 127 | 0 | Byte |
| short | 16-bit | -32,768 to 32,767 | 0 | Short |
| int | 32-bit | -2.1B to 2.1B (most common) | 0 | Integer |
| long | 64-bit | ±9.2 × 10¹⁸ — use L suffix: 42L | 0L | Long |
| float | 32-bit | ~7 decimal digits — use f: 3.14f | 0.0f | Float |
| double | 64-bit | ~15 decimal digits (default decimal) | 0.0d | Double |
| char | 16-bit | Unicode U+0000 to U+FFFF — 'A' | '\u0000' | Character |
| boolean | JVM dep. | true / false only | false | Boolean |
public class DataTypes { public static void main(String[] args) { // Primitives int age = 30; long bigNum = 9_223_372_036L; // _ separator for readability double pi = 3.141592653589; boolean isJava = true; char letter = 'A'; // Implicit widening cast (safe) int i = 100; long l = i; // int → long: automatic double d = i; // int → double: automatic // Explicit narrowing cast (may lose data) double x = 9.99; int y = (int) x; // 9 — truncated, not rounded // Autoboxing / Unboxing Integer boxed = 42; // autobox: int → Integer int unboxed = boxed; // unbox: Integer → int // var — local type inference (Java 10+) var name = "Alice"; // inferred as String var nums = new int[]{ 1, 2, 3 }; // Wrapper class utilities Integer.MAX_VALUE // 2_147_483_647 Integer.parseInt("42") // String → int Double.parseDouble("3.14") Integer.toBinaryString(255) // "11111111" Integer.toHexString(255) // "ff" } }
§ 03
Operators
Java operators: arithmetic, relational, logical, bitwise, assignment, ternary, and the instanceof pattern match operator (Java 16+).
// Arithmetic int a = 17, b = 5; a + b // 22 a - b // 12 a * b // 85 a / b // 3 (integer division!) a % b // 2 (remainder) Math.pow(a, b) // 1419857.0 // Increment / Decrement int x = 5; x++; // post-increment: use THEN add ++x; // pre-increment: add THEN use // Bitwise operators 0b1010 & 0b1100 // AND → 0b1000 = 8 0b1010 | 0b1100 // OR → 0b1110 = 14 0b1010 ^ 0b1100 // XOR → 0b0110 = 6 ~0b1010 // NOT → -11 (two's complement) 8 >> 1 // right shift → 4 (÷2) 4 << 2 // left shift → 16 (×4) // Ternary int max = (a > b) ? a : b; // 17 // instanceof pattern matching (Java 16+) Object obj = "Hello"; if (obj instanceof String s) { // s bound automatically System.out.println(s.length()); // no cast needed! }
§ 04
Control Flow
Java 14+ switch expressions use arrow syntax and return values. Pattern matching in switch (Java 21) is a major modernization.
// ── Classic switch ───────────────────────────── int day = 3; switch (day) { case 1: case 2: case 3: case 4: case 5: System.out.println("Weekday"); break; case 6: case 7: System.out.println("Weekend"); break; default: System.out.println("Invalid"); } // ── Switch expression (Java 14+) — cleaner! ──── String type = switch (day) { case 1, 2, 3, 4, 5 -> "Weekday"; case 6, 7 -> "Weekend"; default -> "Unknown"; }; // ── Switch with blocks and yield ────────────── int score = switch (grade) { case "A" -> 4; case "B" -> 3; case "C" -> { log(); yield 2; } // yield for blocks default -> 0; }; // ── Pattern matching in switch (Java 21) ────── Object obj = 42; String result = switch (obj) { case Integer i when i > 0 -> "Positive int: " + i; case Integer i -> "Non-positive: " + i; case String s -> "String: " + s; case null -> "null!"; default -> "Other"; };
§ 05
Loops
Java loops: for, while, do-while, enhanced for-each. Labels allow breaking from nested loops.
// Standard for loop for (int i = 0; i < 5; i++) { System.out.print(i + " "); // 0 1 2 3 4 } // Enhanced for-each (Iterable or array) int[] nums = {10, 20, 30, 40}; for (int n : nums) { System.out.print(n + " "); } // do-while — body runs at least once int x = 10; do { System.out.println(x); x--; } while (x > 0); // Labeled break — exit outer loop outer: for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { if (i + j == 6) break outer; System.out.print("(" + i + "," + j + ") "); } } // Labeled continue — skip to next outer iteration for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (j == 1) continue; // skip j=1 } }
§ 06
Arrays
Fixed-size, ordered, typed containers. Java arrays are objects. The Arrays utility class provides sorting, searching, copying, and filling operations.
import java.util.Arrays; // Declaration and initialization int[] arr = new int[5]; // [0,0,0,0,0] int[] arr2 = {5, 3, 8, 1, 9, 2}; // literal int len = arr2.length; // 6 (not a method!) // Arrays utility class Arrays.sort(arr2); // [1,2,3,5,8,9] in-place Arrays.sort(arr2, 1, 4); // sort subarray [1..4) int idx = Arrays.binarySearch(arr2, 5); // requires sorted! Arrays.fill(arr2, 0); // fill all with 0 Arrays.fill(arr2, 2, 5, 99); // fill [2..5) with 99 int[] copy = Arrays.copyOf(arr2, 4); // first 4 elements int[] range = Arrays.copyOfRange(arr2, 1, 4); // [1..4) String str = Arrays.toString(arr2); // "[1, 2, 3]" boolean eq = Arrays.equals(arr, arr2); // 2D arrays int[][] grid = new int[3][3]; int[][] matrix = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; System.out.println(matrix[1][2]); // 6 System.out.println(Arrays.deepToString(matrix));
§ 07
Methods
Java methods support overloading (same name, different parameters), varargs, pass-by-value semantics, and recursion. Return types are enforced at compile time.
// ── METHOD OVERLOADING ───────────────────────── public int add(int a, int b) { return a + b; } public double add(double a, double b) { return a + b; } public String add(String a, String b) { return a + b; } public int add(int a, int b, int c) { return a+b+c; } // ── VARARGS ──────────────────────────────────── public int sum(int... nums) { // treated as int[] int total = 0; for (int n : nums) total += n; return total; } sum(1, 2, 3); // 6 sum(10, 20, 30, 40); // 100 // ── PASS-BY-VALUE ────────────────────────────── // Primitives: copies value — original unchanged void doubleIt(int x) { x *= 2; } // original unchanged // Objects: copies REFERENCE — internal state CAN change void modify(StringBuilder sb) { sb.append("!"); } // affects original // ── RECURSION ───────────────────────────────── public long factorial(int n) { if (n <= 1) return 1; // base case return n * factorial(n - 1); // recursive case } // factorial(10) = 3628800
§ 08
Strings
Strings are immutable objects in Java. The String Pool caches literals. StringBuilder is mutable and far faster for concatenation in loops. Java 15+ text blocks.
String s = " Hello, World! "; // Information s.length() // 18 s.isEmpty() // false s.isBlank() // false (Java 11+) s.charAt(7) // 'W' s.indexOf("World") // 9 // Transform (returns new String!) s.trim() // "Hello, World!" s.strip() // unicode-aware trim s.toLowerCase() // " hello, world! " s.toUpperCase() s.replace("l", "L") // " HeLLo, WorLd! " s.substring(9, 14) // "World" s.repeat(2) // Java 11+ // Test s.contains("World") s.startsWith(" H") s.endsWith("! ") s.matches(".*World.*") // Split "a,b,,c".split(",") // ["a","b","","c"] String.join("-", "a","b","c") // "a-b-c"
// StringBuilder — mutable, faster in loops StringBuilder sb = new StringBuilder(); sb.append("Hello"); sb.append(", "); sb.append("World"); sb.insert(5, "!!!"); sb.delete(5, 8); sb.reverse(); sb.toString(); // convert to String // String.format (printf-style) String.format("%-10s %5.2f", "Price:", 9.99); // "Price: 9.99" // %s=String %d=int %f=float // %-10s=left-aligned 10-wide // %5.2f=5-wide, 2 decimals // Text block (Java 15+) String json = """ { "name": "Alice", "age": 30 } """; // String comparison (IMPORTANT!) String a = "hello"; String b = new String("hello"); a == b; // false! (different refs) a.equals(b); // true (compares content) a.equalsIgnoreCase(b); // true
§ 09
Classes & Objects
A class is a blueprint. An object is an instance. Java supports constructor overloading, this chaining, static members, and the builder pattern for immutable objects.
public — accessible everywhereprivate — class only (use for fields!)protected — class + subclasses + package(default) — same package only
ClassName.method()public class BankAccount { // Fields — private for encapsulation private final String owner; private double balance; private static int count = 0; // shared // Constructor chaining with this() public BankAccount(String owner) { this(owner, 0.0); // delegates below } public BankAccount(String owner, double balance) { this.owner = owner; this.balance = balance; count++; } 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; } public double getBalance() { return balance; } public static int getCount() { return count; } @Override public String toString() { return String.format("BankAccount[owner=%s, balance=%.2f]", owner, balance); } }
§ 10
Inheritance
Java supports single class inheritance via extends. Every class implicitly extends Object. super accesses the parent class. Override methods with @Override.
public abstract class Shape { protected String color; public Shape(String color) { this.color = color; } public abstract double area(); // must override public abstract double perimeter(); @Override public String toString() { return "%s[color=%s, area=%.2f]" .formatted(getClass().getSimpleName(), color, area()); } } public class Circle extends Shape { private final double radius; public Circle(String color, double radius) { super(color); // call parent constructor 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 final double width, height; public Rectangle(String c, double w, double h) { super(c); width = w; height = h; } @Override public double area() { return width * height; } @Override public double perimeter() { return 2 * (width + height); } public boolean isSquare() { return width == height; } }
§ 11
Encapsulation
Hide internal state with private fields and expose controlled access through getters/setters. Java Records (Java 16+) auto-generate this boilerplate.
public class Person { private String name; private int age; private String email; // Getter — read-only access public String getName() { return name; } public int getAge() { return age; } // Setter — controlled mutation + validation public void setName(String name) { if (name == null || name.isBlank()) throw new IllegalArgumentException("Name required"); this.name = name.trim(); } public void setAge(int age) { if (age < 0 || age > 150) throw new IllegalArgumentException("Invalid age"); this.age = age; } } // Builder Pattern — fluent API for complex objects public class User { private final String name; private final String email; private final int age; private User(Builder b) { name = b.name; email = b.email; age = b.age; } public static class Builder { private String name, email; private int age; public Builder name(String n) { name = n; return this; } public Builder email(String e) { email = e; return this; } public Builder age(int a) { age = a; return this; } public User build() { return new User(this); } } } // Usage User u = new User.Builder() .name("Alice") .email("alice@example.com") .age(30) .build();
§ 12
Polymorphism
One interface, many forms. Compile-time polymorphism = overloading. Runtime polymorphism = overriding via dynamic dispatch. The JVM decides which method to call at runtime.
// Polymorphic references — parent type, child object Shape s1 = new Circle("red", 5.0); Shape s2 = new Rectangle("blue", 4, 6); // Dynamic dispatch — JVM calls the ACTUAL type's method s1.area(); // Circle.area() = 78.54 s2.area(); // Rectangle.area() = 24.0 // Polymorphic array Shape[] shapes = { new Circle("red", 3), new Rectangle("blue", 4, 5), new Circle("green", 7) }; double totalArea = 0; for (Shape s : shapes) { totalArea += s.area(); // correct method called each time! } // Downcasting — with instanceof guard for (Shape s : shapes) { if (s instanceof Rectangle r) { // Java 16+ pattern System.out.println("Square: " + r.isSquare()); } } // Upcasting is always safe (implicit) Circle c = new Circle("yellow", 2); Shape upcast = c; // implicit, safe // Downcasting needs explicit cast (may throw ClassCastException) Circle back = (Circle) upcast; // safe here // Rectangle bad = (Rectangle) c; // ClassCastException!