Week 1

Environment setup

How Java works

Types and Variables

Arrays

Basic IO

Control Flow

Packages

OOP in Java

Static Members

Practice

Assignment

Back end Track

🎯 Learning Objectives

By the end of this module, you will be able to:


1. Compiled vs Interpreted — Where Java Fits

You already know JavaScript, which is an interpreted language — the engine reads and executes your source code line by line at runtime, with no separate compilation step.

Java works differently. It is a compiled language — your source code must be transformed into a lower-level format before it can run. But Java's compilation story has an interesting twist: it compiles to an intermediate format called bytecode, not directly to machine code.

This gives Java the best of both worlds:

JavaScript C / C++ Java
Compilation step None — interpreted directly Compiled to machine code Compiled to bytecode
Platform independent? ✅ (runtime handles it) ❌ (per-platform binary) ✅ (JVM handles it)
Startup speed Fast Fast Moderate
Runtime speed Moderate Very fast Fast (thanks to JIT)

2. The Journey from Source Code to Running Program

Every time you write and run a Java program, your code goes through three stages:

┌─────────────────┐      javac      ┌─────────────────┐      JVM       ┌──────────────────┐
│   Source Code   │ ─────────────▶  │    Bytecode     │ ─────────────▶ │  Running Program │
│   (Main.java)   │   compilation   │   (Main.class)  │   execution    │                  │
└─────────────────┘                 └─────────────────┘                └──────────────────┘
   Human-friendly                      JVM-friendly                      CPU-friendly
   You write this                    javac produces this                JVM runs this
  1. Source Code (.java) — What you write. Readable by humans, not executable by computers.
  2. Bytecode (.class) — What the Java compiler (javac) produces. Not machine code, but a compact, platform-neutral instruction set designed specifically for the JVM.
  3. Machine Code — What the JVM translates bytecode into at runtime, specific to the CPU and OS it's running on.

3. What is Bytecode?

Bytecode is the bridge between your readable source code and the machine code a CPU understands. It's the format the JVM is designed to read and execute.

Take this simple Java statement:

int localValue = 5 + 10;

The compiler turns this into bytecode instructions that look like this:

0: iconst_5      // Push integer 5 onto the stack
1: bipush 10     // Push integer 10 onto the stack
3: iadd          // Add the two integers
4: istore_1      // Store the result in local variable 1

You'll never write or read bytecode directly — this is shown purely to make the concept concrete. The compiler handles all of this for you.

Why bytecode instead of machine code?

This is the key insight: because Java compiles to bytecode rather than machine code, the same .class file runs identically on Windows, Linux, and macOS — as long as a JVM is installed. The JVM itself is platform-specific, but your compiled program is not.

🔍 This is the origin of Java's famous slogan: "Write once, run anywhere." In Node.js, platform independence comes from the runtime interpreting your source directly. In Java, it comes from the JVM interpreting bytecode — a compiled intermediate form.


4. The Java Virtual Machine (JVM)

The JVM is the engine that takes your compiled bytecode and actually runs it. It sits between your program and the operating system, handling execution, memory, and performance — so you don't have to.

A JVM implementation is platform-specific (there's a different one for Windows, Linux, macOS), but it guarantees that any valid Java bytecode runs identically on all of them. This is why JVM implementations are platform-dependent, but Java programs are not.

The JVM has four main components:


4.1 Class Loader

When your program starts, the JVM doesn't load everything at once. The Class Loader reads .class files from disk and loads them into memory on demand — only when that class is actually needed for the first time.

This lazy loading keeps startup time fast and memory usage efficient.


4.2 Memory Areas

The JVM divides memory into distinct areas, each with a specific purpose:

Area What lives here Who can access it
Heap All objects created with new All threads (shared)
Stack Local variables, method calls, return values One stack per thread (private)
Metaspace Class metadata — the "blueprints" of loaded classes JVM internally

A practical way to think about it: when you write Person p = new Person("Alice"), the Person object lives on the Heap, while the local variable p (which holds the reference to it) lives on the Stack.

💡 You don't manage any of this memory manually in Java — the JVM handles it. This is a significant difference from languages like C or C++, where manual memory management is a major source of bugs.


4.3 The Execution Engine

This is what actually runs your bytecode. It has three parts that work together:

The Interpreter Reads and executes bytecode instructions one at a time. It starts fast — no warm-up needed — but runs slower over time because it re-interprets the same instructions every time they're encountered.

The JIT (Just-In-Time) Compiler As your program runs, the JVM monitors which parts of the code execute most frequently — these are called "hot" paths, typically tight loops or methods called thousands of times. The JIT compiler takes these hot paths and compiles them directly into native machine code that runs at full CPU speed.

Program starts
    ↓
Interpreter runs bytecode (fast startup)
    ↓
JVM identifies hot code paths
    ↓
JIT compiles hot paths → native machine code
    ↓
Hot paths now run at native speed

This is why Java's reputation for being slow is outdated. For long-running applications (like web servers), Java code warmed up by the JIT is extremely fast — in some benchmarks comparable to or faster than C++, because the JIT can optimise based on actual runtime behaviour that static compilers can't observe.

🔍 Node.js uses a similar approach — V8's JIT compiler optimises hot JavaScript code at runtime. The core idea is the same; Java just applies it to bytecode rather than source code.

The Garbage Collector (GC) Automatically reclaims memory from objects your program is no longer using. You never call free() or delete — the GC finds unreachable objects and reclaims their memory in the background.

Modern JVMs include several GC implementations (G1, ZGC, Shenandoah) that can clean up memory with near-zero pause time — an important property for applications that must respond quickly.

💡 JavaScript engines also have garbage collectors — V8's GC works on similar principles. The concept is the same; Java just exposes more control over GC behaviour when you need it.


4.4 The JVM is Polyglot

The JVM doesn't know or care about the Java language — it only understands bytecode. Any language that compiles to valid bytecode can run on the JVM. This is how Kotlin, Scala, and Groovy work — they compile to the same .class bytecode format and run on the exact same JVM.

This means the skills you build understanding the JVM apply beyond Java itself.


5. JRE and JDK — How They Fit Together

Now that you understand the JVM, the relationship between the three acronyms is straightforward:

┌─────────────────────────────────────────────────┐
│                      JDK                        │
│  ┌───────────────────────────────────────────┐  │
│  │                   JRE                     │  │
│  │  ┌──────────────────────────────────────┐ │  │
│  │  │              JVM                     │ │  │
│  │  │  Class Loader, Memory, Execution     │ │  │
│  │  └──────────────────────────────────────┘ │  │
│  │  Standard Libraries (math, I/O, net...)   │  │
│  └───────────────────────────────────────────┘  │
│  Dev Tools: javac, jar, javadoc...              │
└─────────────────────────────────────────────────┘
Contains Purpose
JVM Execution engine, memory management, GC Runs bytecode
JRE JVM + standard libraries Everything needed to run a Java program
JDK JRE + development tools (javac, jar, etc.) Everything needed to write and run Java programs

As a developer, you always install the JDK — it includes everything. The JRE alone is for end-users who only need to run Java applications, not build them. Since Java 11, the JRE is no longer distributed separately; it's always bundled inside the JDK.


✏️ Exercises

Exercise 1 — Compile and Inspect Manually

Write this simple class in a plain text editor (not IntelliJ) and save it as Calculator.java:

public class Calculator {
    public static void main(String[] args) {
        int result = 5 + 10;
        System.out.println(result);
    }
}

Now in your terminal:

javac Calculator.java   # compile
ls                      # list files (macOS/Linux) — or 'dir' on Windows
java Calculator         # run