Lecture 1 / 12
Lecture 01 · Foundations

What is Java?

Beginner ~30 min No prerequisites

Introduction

Java is a high‑level, class‑based, object‑oriented programming language that is designed to have as few implementation dependencies as possible. The slogan “Write Once, Run Anywhere” reflects its capability to run on any device that has a Java Virtual Machine (JVM).

Why Learn Java?

✅ Quick fact
The first public release of Java (JDK 1.0) appeared in 1996.

Hello World – Your First Java Program

Save the following as HelloWorld.java and run it from the command line:

HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

Compile and run:

javac HelloWorld.java   # produces HelloWorld.class
java HelloWorld         # prints “Hello, World!”
🎯 Exercise 1.1

Install the latest JDK, create the HelloWorld.java file, compile it and run it. Verify the output in your terminal.

Lecture 02 · Foundations

Setting up the JDK

Beginner ~35 min Requires: Lecture 01

Download & Install

Head to Eclipse Adoptium or Oracle JDK and download the appropriate bundle for your OS.

Verify Installation

java -version
javac -version
ℹ️ Expected output

Both commands should print the version number (e.g. java 21.0.1) and indicate that the JDK is correctly on your PATH.

IDE Recommendation

For a smoother learning experience pick an IDE:

🎯 Exercise 2.1

Install your favourite IDE, open a new project, create a Main.java file with a main method, and run the “Hello World” program inside the IDE.

Lecture 03 · Foundations

Variables & Data Types

Beginner ~45 min Requires: Lecture 02

Primitive Types

Java has 8 primitive types:

Type Size Example
byte 8 bit byte b = 100;
short 16 bit short s = 20000;
int 32 bit int i = 123456;
long 64 bit long l = 123456789L;
float 32 bit float f = 3.14f;
double 64 bit double d = 3.14159;
char 16 bit (Unicode) char c = 'A';
boolean 1 bit (logical) boolean ok = true;

Reference Types

Anything that isn’t a primitive is a *reference* type: String, arrays, objects, etc.

VariablesDemo.java
public class VariablesDemo {
    public static void main(String[] args) {
        int age = 30;
        double price = 19.99;
        boolean isMember = true;
        String name = "Alice";

        System.out.println(name + ", age " + age);
    }
}
🎯 Exercise 3.1

Create a program that declares one variable of each primitive type, prints them, then swaps two integer values without using a third temporary variable.

Lecture 04 · Foundations

Control Flow

Beginner ~45 min Requires: Lecture 03

If / else

IfDemo.java
int score = 85;
if (score >= 90) {
    System.out.println("Grade A");
} else if (score >= 80) {
    System.out.println("Grade B");
} else {
    System.out.println("Needs improvement");
}

Switch (Java 17+ enhanced switch)

SwitchDemo.java
String day = "MONDAY";

String result = switch (day) {
    case "MONDAY", "TUESDAY", "WEDNESDAY" -> "Mid‑week";
    case "THURSDAY", "FRIDAY" -> "Almost weekend";
    case "SATURDAY", "SUNDAY" -> "Weekend";
    default -> "Invalid day";
};

System.out.println(result);

Loops

LoopDemo.java
int[] numbers = { 2, 4, 6, 8 };
int sum = 0;

for (int n : numbers) {
    sum += n;
}
System.out.println("Sum = " + sum);
🎯 Exercise 4.1

Write a program that prints the first 20 Fibonacci numbers using a while loop.

Lecture 05 · Foundations

Methods

Beginner ~40 min Requires: Lecture 04

Method Syntax

A method in Java consists of: modifiers returnType name(parameters) { body }

MathUtils.java
public class MathUtils {

    public static int add(int a, int b) {
        return a + b;
    }

    public static double average(int[] values) {
        int sum = 0;
        for (int v : values) {
            sum += v;
        }
        return ( double ) sum / values.length;
    }
}

Parameter Passing

Method Overloading

Same name, different parameter list.

OverloadDemo.java
public class OverloadDemo {

    static int max(int a, int b) {
        return (a > b) ? a : b;
    }

    static double max(double a, double b) {
        return (a > b) ? a : b;
    }
}
🎯 Exercise 5.1

Write a utility class that provides overloaded factorial methods for int and long. Add a simple main method that demonstrates both.

Lecture 06 · Core Concepts

Object‑Oriented Programming

Intermediate ~60 min Requires: Lecture 05

Core Pillars

Class definition

Person.java
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    public void birthday() { age++; }
}

Inheritance example

Employee.java
public class Employee extends Person {
    private String department;

    public Employee(String name, int age, String dept) {
        super(name, age);
        department = dept;
    }

    public String getDepartment() { return department; }
}
🎯 Exercise 6.1

Model a simple library system: create a base class Item (with id, title) and subclasses Book and Magazine. Add a method printInfo() that is overridden in each subclass.

Lecture 07 · Core Concepts

Collections Framework

Intermediate ~55 min Requires: Lecture 06

Why Collections?

They provide well‑tested, efficient implementations of common data structures (lists, sets, maps) plus a rich set of utility methods.

List – ordered, duplicates allowed

ListDemo.java
import java.util.*; // List, ArrayList

public class ListDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Alice");   // duplicate allowed

        for (String n : names) {
            System.out.println(n);
        }
    }
}

Set – unique elements

SetDemo.java
import java.util.*; // HashSet

public class SetDemo {
    public static void main(String[] args) {
        Set<Integer> primes = new HashSet<>();
        primes.add(2);
        primes.add(3);
        primes.add(2);  // ignored – duplicate

        System.out.println(primes);
    }
}

Map – key/value pairs

MapDemo.java
import java.util.*; // Map, HashMap

public class MapDemo {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 90);
        scores.put("Bob", 85);
        scores.put("Alice", 95); // overwrites previous

        System.out.println(scores);
    }
}
🎯 Exercise 7.1

Implement a simple phone‑book using a HashMap<String, String> where the key is a person’s name and the value is a phone number. Add methods to add, lookup and remove entries.

Lecture 08 · Core Concepts

Generics

Intermediate ~45 min Requires: Lecture 07

Why Generics?

They let you write **type‑safe** collections and APIs without casting.

Simple generic class

Box.java
public class Box<T> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T get() { return value; }
    public void set(T value) { this.value = value; }
}

Using bounded type parameters

Restrict a type to subclasses of a given class or interface.

NumberUtils.java
public class NumberUtils {
    public static <T extends Number>
    double sum(List<T> list) {
        double total = 0;
        for (T n : list) {
            total += n.doubleValue();
        }
        return total;
    }
}
🎯 Exercise 8.1

Create a generic Pair<K,V> class that stores two values. Write a short demo that creates a Pair<String,Integer> and prints both elements.

Lecture 09 · Advanced

Streams & Lambdas (Java 8+)

Intermediate ~55 min Requires: Lecture 08

Lambda syntax

A lambda expression is an anonymous function that can be treated as an instance of a functional interface.

LambdaDemo.java
import java.util.function.*;

public class LambdaDemo {
    public static void main(String[] args) {
        Predicate<Integer> isEven = n -> n % 2 == 0;
        System.out.println(isEven.test(42)); // true
    }
}

Stream pipeline

Streams enable functional‑style processing of sequences of elements.

StreamDemo.java
import java.util.stream.*;
import java.util.*;

public class StreamDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1,2,3,4,5);
        int sum = numbers.stream()
                               .filter(n -> n % 2 == 0)   // keep evens
                               .mapToInt(n -> n * n)       // square them
                               .sum();                 // sum

        System.out.println("Result = " + sum);
    }
}
🎯 Exercise 9.1

Read a list of words from an array, convert them to uppercase, filter those longer than 5 characters, sort them alphabetically and print each on its own line – all using the Stream API.

Lecture 10 · Advanced

Exception Handling

Intermediate ~45 min Requires: Lecture 09

Checked vs Unchecked

Try‑catch‑finally

ExceptionDemo.java
import java.io.*;

public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            BufferedReader br = new BufferedReader(new FileReader("missing.txt"));
            System.out.println(br.readLine());
        } catch (FileNotFoundException e) {
            System.err.println("File not found!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("Cleanup if necessary");
        }
    }
}

Custom exception

InvalidAgeException.java
public class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}
🎯 Exercise 10.1

Create a BankAccount class with a withdraw(double amount) method. Throw a custom InsufficientFundsException when the balance would become negative, and write a driver program that catches and reports the error.

Lecture 11 · Advanced

Concurrency

Intermediate ~60 min Requires: Lecture 10

Threads basics

Two ways to create a thread:

  1. Extend Thread and override run().
  2. Implement Runnable (or Callable) and pass it to a Thread or an ExecutorService.
ThreadDemo.java
class MyTask implements Runnable {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread " + Thread.currentThread().getName() + ": " + i);
            try { Thread.sleep(500); } catch (InterruptedException e) { }
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyTask(), "Worker‑1");
        Thread t2 = new Thread(new MyTask(), "Worker‑2");
        t1.start();
        t2.start();
    }
}

Executor framework

Higher‑level API for managing thread pools.

ExecutorDemo.java
import java.util.concurrent.*;

public class ExecutorDemo {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            int id = i;
            pool.submit(() -> {
                System.out.println("Task " + id + " executed by " + Thread.currentThread().getName());
            });
        }
        pool.shutdown();
    }
}
🎯 Exercise 11.1

Implement a simple producer‑consumer pipeline using a BlockingQueue and two threads (one producing numbers, the other consuming and printing them). Use an ExecutorService to run both.

Lecture 12 · Capstone

Capstone Project – Console To‑Do List

Advanced ~90 min Requires: All Lectures

Build a **single‑file** console application that lets the user manage a to‑do list. The program should let you:

Suggested Architecture

  1. Model – a Task class with id, description and completed flag.
  2. Repository – a List<Task> stored in memory; use Files API for persistence.
  3. Service – static methods for add/save/load/delete.
  4. UI loop – a while(true) menu that reads user input with Scanner.

Starter Code (you’ll flesh it out)

TodoApp.java
import java.util.*;
import java.nio.file.*;

class Task {
    private static int counter = 1;
    private final int id;
    private String description;
    private boolean done;

    public Task(String description) {
        this.id = counter++;
        this.description = description;
        this.done = false;
    }

    public int getId() { return id; }
    public String getDescription() { return description; }
    public boolean isDone() { return done; }
    public void toggle() { done = !done; }

    public String serialize() {
        return id + "," + description.replaceAll(",", "\\,") + "," + done;
    }

    public static Task deserialize(String line) {
        String[] parts = line.split(",", 3);
        Task t = new Task(parts[1]);
        t.id = Integer.parseInt(parts[0]);          // reset id
        t.done = Boolean.parseBoolean(parts[2]);
        return t;
    }
}

public class TodoApp {
    private static final Path FILE = Paths.get("tasks.txt");
    private static final List<Task> tasks = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        load();
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("\n--- To‑Do List ---");
            tasks.forEach(t -> System.out.println(t.getId() + ". [" + (t.isDone() ? "x" : " ") + "] " + t.getDescription()));
            System.out.println("\nChoose: (a)dd, (t)oggle, (d)elete, (q)uit");
            String cmd = sc.nextLine().trim().toLowerCase();
            switch (cmd) {
                case "a":
                    System.out.print("Enter description: ");
                    String desc = sc.nextLine();
                    tasks.add(new Task(desc));
                    break;
                case "t":
                    System.out.print("Id to toggle: ");
                    int id = Integer.parseInt(sc.nextLine());
                    tasks.stream()
                         .filter(t -> t.getId() == id)
                         .findFirst()
                         .ifPresent(Task::toggle);
                    break;
                case "d":
                    System.out.print("Id to delete: ");
                    int delId = Integer.parseInt(sc.nextLine());
                    tasks.removeIf(t -> t.getId() == delId);
                    break;
                case "q":
                    save();
                    System.out.println("Good‑bye!");
                    return;
                default:
                    System.out.println("Unknown command");
            }
        }
    }

    private static void load() throws Exception {
        if (Files.exists(FILE)) {
            Files.lines(FILE).forEach(l -> tasks.add(Task.deserialize(l)));
        }
    }

    private static void save() throws Exception {
        List<String> lines = new ArrayList<>();
        tasks.forEach(t -> lines.add(t.serialize()));
        Files.write(FILE, lines);
    }
}

What You’ll Demonstrate

🎯 Final Challenge

Extend the app with:

  1. A priority field (enum LOW/MEDIUM/HIGH) and sort the output by priority.
  2. Use java.time.LocalDate to add a due‑date, and display overdue tasks.
  3. Write JUnit tests for the Task class and the persistence logic.