📘 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 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) →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) →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) →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) →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 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 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 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 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) →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) →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) →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) →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) →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) →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 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 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) →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) →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 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 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 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 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 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 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 <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) →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 (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, (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) →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) →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 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) →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 { } 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) →