📘 Core Java Notes — From Absolute Basics

34 Core Java topics — from Basics all the way to Java 8+ — each one told as a short story with an animation, as if a kid were learning Java for the very first time. Open a card for a quick overview, or hit "Full Deep Dive" for the complete picture — subtopics, extra code examples, everything.

💡 Java is like writing a recipe that can be cooked in any kitchen (computer) in the world.

Java is a programming language we use to give instructions to a computer. It was created in 1995 and is still used everywhere today — apps, websites, games, all of it.

The best part: "Write Once, Run Anywhere". Meaning you write Java code once, and it can run on Windows, Mac, Linux — anywhere — without rewriting it!

How? Your .java file is first "compiled" into a .class file (bytecode). Then the JVM (Java Virtual Machine) understands that bytecode on any computer and runs it — like a universal translator!

public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello, Code10x!");
  }
}
📖 Full Deep Dive (4 subtopics) →
💡 A variable is a labeled box you put a value in — the box's name and what (type) can go in it are decided upfront.

In Java, every variable has a type — like int (whole number), double (decimal number), char (single letter), boolean (true/false), and String (text).

Java is a "statically typed" language — meaning you have to declare the box's type upfront, and only that type of thing will fit in that box.

int age = 10;
double price = 99.5;
char grade = 'A';
boolean isFun = true;
String name = "Duke";
📖 Full Deep Dive (3 subtopics) →
💡 Operators are like the buttons on your calculator — +, -, *, / for maths, and ==, >, < for comparisons.

Arithmetic operators (+ - * / %) do maths on numbers. The % operator gives the "remainder" — like 10 % 3 = 1.

Relational operators (== != > < >= <=) compare two values and give true/false. Logical operators (&& || !) combine multiple conditions.

int a = 10, b = 3;
System.out.println(a % b);            // 1
System.out.println(a > b && b > 0);   // true
📖 Full Deep Dive (5 subtopics) →
💡 if-else is like a railway junction — the track switches left or right depending on the condition.

The if block runs only when the condition is true. If it's false, the else block runs instead.

You can chain multiple conditions with else if — like checking one gate after another.

int marks = 85;
if (marks >= 90) {
  System.out.println("A grade");
} else if (marks >= 60) {
  System.out.println("B grade");
} else {
  System.out.println("Keep trying!");
}
📖 Full Deep Dive (3 subtopics) →
💡 A loop is a runner going round and round a track, until the coach (the condition) says "stop!"

Use a for loop when you know how many times to repeat. Use a while loop when you need to keep repeating as long as a condition stays true.

A do-while loop always runs at least once, even if the condition is false from the start.

for (int i = 1; i <= 5; i++) {
  System.out.println("Lap " + i);
}
📖 Full Deep Dive (5 subtopics) →
💡 An array is like a row of numbered lockers — each locker has its own index (starting at 0).

An array is a fixed-size collection that stores multiple values of the same type together.

Every value has its own "index", starting at 0 — the first element is at index 0, not 1!

int[] scores = {90, 85, 77, 60};
System.out.println(scores[0]); // 90
📖 Full Deep Dive (4 subtopics) →
💡 A class is a blueprint for a house. An object is a real house built from that blueprint — and you can build many houses from one blueprint!

A class defines what properties (fields) and what behavior (methods) an object will have.

An object is an "instance" of a class — a real thing you can use, with its own values.

class Car {
  String color;
  void drive() { System.out.println(color + " car driving!"); }
}
Car myCar = new Car();
myCar.color = "Red";
myCar.drive();
📖 Full Deep Dive (3 subtopics) →
💡 A constructor is a "welcome kit" that gives an object its starting values the moment it's born.

A constructor is a special method with the same name as the class, called automatically when an object is created with the 'new' keyword.

It lets us give an object a valid starting state right away — without setting each field separately.

class Student {
  String name;
  Student(String n) { name = n; }
}
Student s1 = new Student("Aarav");
📖 Full Deep Dive (4 subtopics) →
💡 "this" means "myself" — it points to your own field. "static" is like a shared locker that all objects use together.

this refers to the current object — it clears up confusion when a parameter and a field share the same name.

A static member belongs to the class, not to any one object — all objects share it, like a common counter.

class Counter {
  static int total = 0;
  Counter() { total++; }
}
📖 Full Deep Dive (4 subtopics) →
💡 In inheritance, a child class "inherits" features from its parent class — like a son inheriting his father's traits, plus adding his own new ones.

The extends keyword lets one class use another class's fields and methods, without rewriting them.

This boosts code reuse — write the common stuff in the parent, the specific stuff in the child.

class Animal {
  void eat() { System.out.println("Eating..."); }
}
class Dog extends Animal {
  void bark() { System.out.println("Woof!"); }
}
📖 Full Deep Dive (4 subtopics) →
💡 Polymorphism means "one name, many forms" — like an actor playing different roles in different costumes.

Overloading: same method name, different parameters — like add(int,int) and add(double,double). Decided at compile-time.

Overriding: a child class redefines a parent's method in its own way — like Dog defining its own sound(). Decided at run-time.

class Animal { void sound() { System.out.println("..."); } }
class Cat extends Animal { void sound() { System.out.println("Meow"); } }
📖 Full Deep Dive (4 subtopics) →
💡 Encapsulation seals data inside a capsule — you can't touch it directly from outside, only through the "get" and "set" doors.

Make fields private, so no one can change them directly from outside.

Provide public getter/setter methods so the value can be read/changed in a controlled way — like withdrawing cash from an ATM, not opening the vault directly.

class Account {
  private double balance;
  public double getBalance() { return balance; }
  public void deposit(double amt) { if (amt > 0) balance += amt; }
}
📖 Full Deep Dive (4 subtopics) →
💡 While driving, you only use the steering wheel and pedals — you don't need to see what's happening inside the engine. That's Abstraction!

Abstraction hides complex implementation and shows only what's necessary.

An abstract class or interface lets us state "what to do" — the implementing class decides "how to do it".

abstract class Shape {
  abstract double area();
}
class Circle extends Shape {
  double r;
  double area() { return 3.14 * r * r; }
}
📖 Full Deep Dive (3 subtopics) →
💡 An interface is a 100% contract — just rules, no work done yet. An abstract class is a half-built house — some work is already done, the rest is up to you.

Before Java 8, an interface had only method signatures, no body — the implementing class had to write everything.

An abstract class can have some complete methods and some abstract (incomplete) ones — and a class can extend only one abstract class, but implement many interfaces.

interface Flyable { void fly(); }

abstract class Bird {
  void breathe() { System.out.println("breathing"); }
  abstract void fly();
}
📖 Full Deep Dive (5 subtopics) →
💡 try-catch is like a safety net at the circus — if the performer (the code) falls (an error occurs), the net (catch) catches it so the show (the program) doesn't crash.

You put code that might error out inside the try block. If an error occurs, the catch block "catches" it and the program doesn't crash.

The finally block always runs — whether an error occurred or not — like doing cleanup, closing files.

try {
  int x = 10 / 0;
} catch (ArithmeticException e) {
  System.out.println("Cannot divide by zero!");
} finally {
  System.out.println("Done trying.");
}
📖 Full Deep Dive (4 subtopics) →
💡 ArrayList is a train with its compartments in order (accessed by index). HashSet is a sticker album where no sticker repeats. HashMap is a locker room where every locker has its own name-tag (key).

ArrayList is an ordered list, duplicates allowed, accessed by index.

HashSet holds only unique values, no order guaranteed. HashMap stores key-value pairs — you can find a value instantly by its key, like a locker by name.

List<String> names = new ArrayList<>();
Set<Integer> ids = new HashSet<>();
Map<String, Integer> ages = new HashMap<>();
ages.put("Aarav", 10);
📖 Full Deep Dive (5 subtopics) →
💡 A single thread is a lone runner doing everything one by one. Multithreading is many runners racing on different tracks at once — the work gets done faster!

A thread is an independent flow within a program. Normally, Java runs in a single thread (main).

You can create new threads by extending Thread or implementing Runnable, which then do work in parallel.

class MyThread extends Thread {
  public void run() { System.out.println("Running..."); }
}
new MyThread().start();
📖 Full Deep Dive (4 subtopics) →
💡 A String is like writing on stone — once written, it can't be changed; changing it means carving a new stone (a new object). StringBuilder is a whiteboard you can erase and rewrite.

String is immutable — when you do s = s + "x", a new String object is created; the old one stays as it was.

If you need lots of changes (like in a loop), use StringBuilder instead — it modifies the same object, and is fast.

String s = "Hi";
s = s + " Java";      // new object created

StringBuilder sb = new StringBuilder("Hi");
sb.append(" Java");   // same object modified
📖 Full Deep Dive (5 subtopics) →
💡 Type casting is like pouring a small glass of water into a bigger glass (widening), or forcing a big glass of water into a small one (narrowing — some might spill).

Widening (implicit) casting automatically converts a smaller type into a bigger one — like int to double, with no loss.

Narrowing (explicit) casting requires manually converting a bigger type into a smaller one — like double to int — and some data (the decimal part) can be lost.

Wrapper classes (Integer, Double, Character, Boolean) let you use primitive types as objects. Autoboxing automatically turns a primitive into its wrapper; unboxing does the reverse.

double d = 100.99;
int i = (int) d;      // narrowing, i = 100

int x = 5;
Integer boxed = x;    // autoboxing
int y = boxed;        // unboxing
📖 Full Deep Dive (3 subtopics) →
💡 A method is a reusable machine — build it once, and whenever you need it, just "call" it and the work gets done. Recursion is a machine that calls itself again and again, until it stops.

A method takes parameters, does its job, and (optionally) returns a value — saving you from repeating code.

Java follows "pass by value" — a method gets a copy of the values, the original variable never changes (for objects, it gets a copy of the reference).

In recursion, a method calls itself — like factorial(5) = 5 * factorial(4)... until the base case (factorial(1)=1) is reached.

int factorial(int n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}
📖 Full Deep Dive (4 subtopics) →
💡 switch is like a vending machine — press a number, get exactly that item, instead of checking if-else one by one. break exits a loop; continue skips to the next round.

switch replaces multiple if-else checks when you're comparing one variable against several fixed values — a break after each case is essential, otherwise the next case runs too (fall-through).

break stops a loop immediately. continue skips the current iteration and moves to the next one.

switch (day) {
  case 1: System.out.println("Mon"); break;
  case 2: System.out.println("Tue"); break;
  default: System.out.println("Other");
}
📖 Full Deep Dive (3 subtopics) →
💡 A 2D array is like a seating chart (rows and columns) — like rows and seats in a classroom, each seat has its own row-column address.

A 2D array is an "array of arrays" — the first index is the row, the second is the column.

It stores grid-like data — like a tic-tac-toe board, or a matrix.

int[][] grid = {
  {1, 2, 3},
  {4, 5, 6}
};
System.out.println(grid[1][2]); // 6 (row 1, col 2)
📖 Full Deep Dive (4 subtopics) →
💡 Every Java object has a "default ID card" (from the Object class) — toString() gives its name, equals() recognizes "is this the same thing", hashCode() gives a unique-ish number for fast lookup.

Every class implicitly extends the Object class, so every object gets toString(), equals(), and hashCode() for free.

The default toString() gives odd-looking text (like ClassName@hashcode) — which is why we override it for readable output.

equals() and hashCode() should be overridden together — this is essential for HashMap/HashSet to work correctly.

class Point {
  int x, y;
  public String toString() { return "(" + x + "," + y + ")"; }
  public boolean equals(Object o) {
    if (!(o instanceof Point)) return false;
    Point p = (Point) o;
    return x == p.x && y == p.y;
  }
}
📖 Full Deep Dive (4 subtopics) →
💡 A nested class is like a matryoshka (Russian doll) — a class hidden inside another class, working closely with the outer one.

A static nested class can be used independently, without an outer class object — accessed just as Outer.Nested.

A non-static Inner class is tied to an outer object — it can directly see the outer's instance members too.

An anonymous class is a nameless class defined for immediate, one-time use — like a button click listener.

class Outer {
  static class Nested { void show() { System.out.println("Nested"); } }
  class Inner { void show() { System.out.println("Inner"); } }
}
📖 Full Deep Dive (4 subtopics) →
💡 An enum is like a traffic signal — only RED, YELLOW, GREEN exist, there's no possible 4th color. Fixed, predefined choices.

An enum is a special type where only fixed constants are defined — like Day.MONDAY, Day.TUESDAY.

It's better than a plain String/int because the compiler won't let an invalid value slip through — only the options you defined are allowed.

enum Level { LOW, MEDIUM, HIGH }

Level l = Level.MEDIUM;
if (l == Level.MEDIUM) System.out.println("Medium level!");
📖 Full Deep Dive (4 subtopics) →
💡 Generics are like a universal toolbox where you can label it "this time I'll only hold screws (Integer)" or "this time only nails (String)" — same box, but only one type of thing goes in, no mix-ups.

Generics <T> let us build classes/methods that "work for any type", without writing separate versions.

You get compile-time type-safety — the compiler flags an error immediately if you try to insert the wrong type, saving you from a run-time crash.

class Box<T> {
  T item;
  void set(T item) { this.item = item; }
  T get() { return item; }
}
Box<String> b = new Box<>();
b.set("Hello");
📖 Full Deep Dive (4 subtopics) →
💡 Access modifiers are like the doors of a house — public is a door open to everyone, private is just your own room, protected is for family (subclasses) only, and default is for people in the same neighborhood (package) only.

public: accessible from anywhere. private: only within that same class. protected: within the same package + subclasses. default (no modifier written): only within the same package.

A package is a folder/group of related classes — like java.util, which holds collection classes such as List and Map. To create your own package you write "package com.code10x;".

package com.code10x;

public class Robot {
  private int battery;
  protected void charge() { battery = 100; }
}
📖 Full Deep Dive (4 subtopics) →
💡 Comparable is when something tells you itself "here's how I sort" (like kids in a line whose height is built-in). Comparator is an outside teacher who decides a custom order. Queue is like a line (first come, first served).

Comparable (the compareTo method) is defined inside the class — it states the "natural ordering". Comparator (the compare method) provides a custom ordering from outside, as many as you like.

Queue follows FIFO (First-In-First-Out) order — like a ticket line. Deque can add/remove from both ends.

TreeMap/TreeSet automatically keep their keys in sorted order (unlike HashMap/HashSet, where order isn't guaranteed).

List<String> names = new ArrayList<>(List.of("Zoya","Aman","Ravi"));
names.sort(Comparator.naturalOrder());
// [Aman, Ravi, Zoya]

Queue<Integer> q = new LinkedList<>();
q.add(1); q.add(2);
System.out.println(q.poll()); // 1
📖 Full Deep Dive (5 subtopics) →
💡 A lambda expression is like a quick "instant note" — instead of writing a full formal letter (building a class and a method), you jot down the task in one line right away. A Stream is a factory conveyor belt where data passes through stage after stage (filter -> map -> collect) to produce the final result.

A lambda expression, (param) -> { code }, implements a functional interface (one with a single abstract method) concisely.

The Stream API lets you chain operations like filter, map, and sort on collections — without writing manual loops, resulting in shorter, more readable code.

List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);
List<Integer> evenSquares = nums.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .toList();
// [4, 16, 36]
📖 Full Deep Dive (14 subtopics) →
💡 File handling is like writing in/reading from a diary — you write into the diary with FileWriter, and read it with BufferedReader. try-with-resources is a smart helper that remembers to close the diary for you.

The java.io package gives tools for working with files — FileReader/FileWriter for characters, BufferedReader for fast line-by-line reading.

try-with-resources automatically closes a resource once the block finishes — it protects you from the mistake of leaving a file/connection open.

try (BufferedReader br = new BufferedReader(new FileReader("notes.txt"))) {
  String line = br.readLine();
  System.out.println(line);
} catch (IOException e) {
  System.out.println("File error: " + e.getMessage());
}
📖 Full Deep Dive (4 subtopics) →
💡 Multi-catch is one security guard who handles different kinds of troublemakers (exceptions) at a single gate — no need to build a separate gate for each.

A single catch block can catch multiple exception types using "|": catch (IOException | SQLException e).

You can build your own hierarchy of custom exceptions — like a base AppException, extended into a specific PaymentFailedException — this organizes errors in large projects.

try {
  riskyOperation();
} catch (IOException | SQLException e) {
  System.out.println("Handled: " + e.getMessage());
}

class AppException extends Exception {}
class PaymentFailedException extends AppException {}
📖 Full Deep Dive (4 subtopics) →
💡 A thread's lifecycle is like a runner's journey — New (standing at the start line) -> Runnable (ready to run) -> Running (actually running) -> Waiting (catching their breath) -> Terminated (finish line). The Executor Framework is a race manager that automatically decides which runner runs when.

A thread has 5 states: New, Runnable, Running, Waiting/Blocked, Terminated.

wait()/notify() let threads wait for each other's signal. ExecutorService manages a thread pool — instead of manually creating new threads, ready threads get reused.

A deadlock happens when two threads keep waiting for the resource the other one holds, and neither moves forward — like two people at a door, each waiting for the other to go first, forever.

ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(() -> System.out.println("Task running!"));
pool.shutdown();
📖 Full Deep Dive (6 subtopics) →
💡 The Heap is a big warehouse where all the objects live. The Stack is a small to-do list that tracks method calls. The Garbage Collector is a cleaner who walks through the warehouse and removes objects nobody is using anymore.

Stack memory stores method calls and local variables — fast, and automatically cleaned up once the method ends.

Heap memory stores objects (created with "new") — they stick around as long as some reference is using them.

The Garbage Collector (GC) automatically finds and removes objects with no remaining references — you never need to manually free memory in Java.

void createObjects() {
  Student s = new Student(); // heap par banta hai
} // method khatam -> s ab unreachable -> GC eligible
📖 Full Deep Dive (5 subtopics) →
💡 A static block is a building's "one-time" inauguration — it runs exactly once, when the class loads. Cloning is a photocopier that makes an exact copy of an object. Serialization is packing an object into a suitcase to send somewhere, so you can later unpack (deserialize) the exact same thing.

A static block { } runs once, the moment the class loads — best for initializing static variables. An instance block runs every time an object is created (before the constructor).

clone() is a method from the Object class that creates a copy of an existing object (you need to implement the Cloneable interface).

Implementing the Serializable interface lets you convert an object into bytes (e.g. to save to a file or send over a network), and later deserialize it back into an object.

class Config implements Cloneable, Serializable {
  static { System.out.println("Loaded once!"); }
  int value = 10;
}
📖 Full Deep Dive (4 subtopics) →