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).

Java was originally developed by Sun Microsystems and later acquired by Oracle Corporation. Over the years, Java became one of the most trusted programming languages for enterprise software, Android development, cloud applications, and large-scale systems.

Today, Java powers millions of applications worldwide — from banking systems and e-commerce platforms to desktop applications and backend APIs.

Why Learn Java?

  • Ubiquitous – used in Android, enterprise back-ends, desktop apps and big-data ecosystems.
  • Strong static typing helps catch many bugs at compile time.
  • Rich standard library and a huge ecosystem of third-party libraries.
  • Excellent tooling (IDEA, Eclipse, VS Code, Maven/Gradle, JUnit).

Java is also considered one of the best languages for beginners because its syntax is clean, structured, and widely documented. Learning Java builds a strong programming foundation that makes it easier to learn other languages later.

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

Where is Java Used?

  • Android Development: Many Android applications use Java.
  • Web Applications: Large-scale backend systems often use Java frameworks like Spring.
  • Desktop Software: Java can build cross-platform desktop applications.
  • Enterprise Systems: Banking, insurance, and large corporations rely heavily on Java.
  • Cloud & Big Data: Technologies like Hadoop and Apache Kafka are built using Java.
🌍 Industry Insight
Java consistently ranks among the world's most popular programming languages because of its stability, scalability, and massive developer ecosystem.

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!”

Understanding the Program

Let us break down the program line by line:

  • public class HelloWorld → Defines a class named HelloWorld.
  • main() → The entry point where program execution starts.
  • System.out.println() → Prints output to the console.
  • String[] args → Stores command-line arguments.
💡 Important
In Java, the file name must match the public class name exactly. That is why the file is named HelloWorld.java.
🎯 Exercise 1.1

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

Key Features of Java

  • Object-Oriented: Everything in Java revolves around objects and classes.
  • Platform Independent: Java code is compiled into bytecode that runs on the JVM.
  • Secure: No direct pointer access and includes built-in security features.
  • Robust: Strong memory management and exception handling.
  • Multithreaded: Supports concurrent execution of two or more threads.

Additional Advantages of Java

  • Automatic Garbage Collection: Java automatically manages memory allocation and cleanup.
  • Large Community: Millions of developers contribute tutorials, libraries, and tools.
  • Backward Compatibility: Older Java applications often continue to work on newer JVM versions.
  • Performance: Modern JVM optimizations make Java extremely fast.

How Java Works

Java follows a two-step execution process:

  1. Compilation: The Java source file (.java) is compiled into bytecode (.class) using the Java compiler (javac).
  2. Execution: The JVM interprets or compiles the bytecode into machine code that your system can execute.
💡 Did you know?
The JVM acts like a translator between Java bytecode and your operating system, making Java platform-independent.

JDK vs JRE vs JVM

Beginners often confuse these three important Java components. Understanding them early will help you later.

  • JVM (Java Virtual Machine) → Runs Java bytecode.
  • JRE (Java Runtime Environment) → Contains the JVM and libraries needed to run Java programs.
  • JDK (Java Development Kit) → Includes tools required to develop Java applications such as javac.
🧠 Easy Memory Trick
JDK = Develop Java Programs
JRE = Run Java Programs
JVM = Execute Java Bytecode

Basic Structure of a Java Program

  • Class: The main building block of a Java program.
  • Main Method: Entry point where execution begins.
  • Statements: Instructions executed by the program.

Java Syntax Rules

  • Java is case-sensitive.
  • Every statement usually ends with a semicolon (;).
  • Curly braces { } define blocks of code.
  • Program execution starts from the main() method.
Example.java
public class Example {
    public static void main(String[] args) {
        System.out.println("Learning Java");
    }
}

Common Errors Beginners Make

  • Forgetting to match the file name with the class name.
  • Missing semicolons (;) at the end of statements.
  • Incorrect capitalization (Java is case-sensitive).
  • Not installing or configuring the JDK properly.
⚠️ Common Mistake
Writing system.out.println() instead of System.out.println() will produce an error because Java is case-sensitive.

Best Practices for Beginners

  • Practice writing code daily instead of only reading theory.
  • Use meaningful class and variable names.
  • Read compiler error messages carefully.
  • Experiment with small programs frequently.
  • Focus on understanding concepts instead of memorizing syntax.
🎯 Exercise 1.2

Modify the HelloWorld program to print your name instead of "Hello, World!".

🎯 Exercise 1.3

Try printing multiple lines using System.out.println() and observe how output is displayed.

🚀 Challenge Exercise

Create a program named AboutMe.java that prints:

  • Your name
  • Your city
  • Your favorite programming language

Summary

In this lecture, you learned what Java is, why it is widely used, and how to write and run your first Java program. You also explored its key features, execution process, Java components, and the basic structure of a Java application.

In the next lecture, we will dive deeper into setting up the development environment, installing the JDK correctly, and preparing your system for Java development.

Lecture 02 · Foundations

Setting up the JDK

Beginner ~35 min Requires: Lecture 01

Introduction

Before writing larger Java applications, you need a proper Java development environment. This includes installing the JDK (Java Development Kit), configuring your system, and choosing a suitable code editor or IDE.

In this lecture, you will learn how to install Java correctly, verify the installation, and prepare your machine for Java development.

What is the JDK?

The Java Development Kit (JDK) is the complete package required to create, compile, and run Java applications.

The JDK includes:

  • JVM (Java Virtual Machine) → Executes Java bytecode.
  • JRE (Java Runtime Environment) → Provides libraries required to run programs.
  • Java Compiler (javac) → Converts Java source code into bytecode.
  • Development Tools → Debuggers, documentation generators, and more.
💡 Important
If you only install the JRE, you can run Java applications but cannot compile your own Java programs.

Download & Install

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

Choose the installer version that matches your system architecture:

  • Windows: Usually .msi or .exe
  • macOS: Usually .pkg
  • Linux: Usually .tar.gz or package manager installation
✅ Recommendation
Beginners usually prefer Eclipse Adoptium because it provides free OpenJDK builds that are simple to install.

Installing on Windows

  1. Download the JDK installer.
  2. Run the installer and follow the setup wizard.
  3. Allow the installer to configure environment variables if prompted.
  4. Finish installation and restart your terminal.

Installing on macOS

  1. Download the macOS package.
  2. Open the installer package.
  3. Follow the installation instructions.
  4. Open Terminal and verify the installation.

Installing on Linux

Many Linux distributions provide OpenJDK through package managers.

Ubuntu / Debian
sudo apt update
sudo apt install openjdk-21-jdk

Verify Installation

After installation, open your terminal or command prompt and run:

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 configured on your PATH.

Understanding PATH Variables

The PATH environment variable allows your operating system to locate Java commands such as java and javac from any terminal window.

If the terminal says:

'java' is not recognized as an internal or external command

then Java may not be correctly added to your system PATH.

⚠️ Common Beginner Problem
Installing Java successfully does not always mean the PATH variable is configured correctly. This is one of the most common setup mistakes beginners face.

IDE Recommendation

For a smoother learning experience, choose a Java IDE or code editor:

  • IntelliJ IDEA Community – Rich refactoring, smart code completion, debugging, and Maven/Gradle integration.
  • Eclipse – A classic Java IDE with extensive plugin support.
  • VS Code – Lightweight editor with the Java Extension Pack.
💡 Beginner Suggestion
IntelliJ IDEA Community Edition is often considered the easiest and most beginner-friendly Java IDE.

Creating Your First Java Project

Once your IDE is installed:

  1. Create a new Java project.
  2. Create a new Java class named Main.
  3. Add a main() method.
  4. Run the program using the IDE's Run button.
Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println("Java setup successful!");
    }
}

Command Line vs IDE

Beginners often wonder whether they should use the terminal or an IDE. Both are useful and important.

  • Command Line: Helps understand how Java compilation works internally.
  • IDE: Speeds up development with autocomplete, debugging, and project tools.
🧠 Best Practice
Learn both approaches. Professional developers frequently use IDEs, but understanding terminal commands builds stronger fundamentals.

Common Installation Problems

  • Installing only the JRE instead of the full JDK
  • Incorrect PATH configuration
  • Using an outdated Java version
  • Multiple Java versions conflicting on the same system
  • Restarting neither the terminal nor the computer after installation

Useful Java Commands

Terminal Commands
java -version     # Shows installed Java version
javac -version    # Shows Java compiler version
java Main         # Runs a compiled Java class
javac Main.java   # Compiles Java source code
🎯 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.

🎯 Exercise 2.2

Open your terminal and run:

java -version
javac -version

Verify that both commands display version information correctly.

🚀 Challenge Exercise

Create a Java program named EnvironmentCheck.java that prints:

  • Your Java version
  • Your operating system
  • Your user name

Research how to access system properties using System.getProperty().

Summary

In this lecture, you learned how to install the Java Development Kit, verify your installation, configure the PATH variable, and choose an IDE for Java development.

You also explored the differences between JDK, JRE, and JVM, along with common installation mistakes beginners encounter.

In the next lecture, we will begin working with variables, data types, and user input in Java.

Lecture 03 · Foundations

Variables & Data Types

Beginner ~45 min Requires: Lecture 02

Introduction

Variables are one of the most fundamental concepts in programming. A variable is simply a named storage location used to hold data in memory.

In Java, every variable must have a specific data type. The data type determines:

  • What kind of data can be stored
  • How much memory is allocated
  • What operations can be performed on the value
💡 Real-World Analogy
Think of variables as labeled containers. Different containers store different types of things — numbers, text, or true/false values.

Declaring Variables

In Java, variables are declared using the following syntax:

Syntax.java
dataType variableName = value;

Example:

Example.java
int age = 25;
double price = 99.99;
String name = "Alice";

Primitive Types

Java has 8 primitive data types. Primitive types store simple values directly in memory.

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;

Integer Types

Integer types are used to store whole numbers without decimal points.

  • byte → Small memory usage
  • short → Slightly larger integers
  • int → Most commonly used integer type
  • long → Very large integer values
✅ Best Practice
Most Java programs use int for integers unless very large numbers are required.

Floating-Point Types

Floating-point types are used to store decimal numbers.

  • float → Less precision, uses less memory
  • double → Higher precision, more commonly used
⚠️ Important
Decimal numbers are treated as double by default. Add f at the end when using a float.
FloatExample.java
float value = 3.14f;
double pi = 3.1415926535;

Character Type

The char type stores a single Unicode character.

CharExample.java
char grade = 'A';
char symbol = '#';

Boolean Type

The boolean type stores only two values: true or false.

BooleanExample.java
boolean isLoggedIn = true;
boolean hasPermission = false;

Reference Types

Anything that is not a primitive type is called a reference type.

Reference types store memory addresses that point to objects.

Examples include:

  • String
  • Arrays
  • Classes and objects
  • Collections
🧠 Important Concept
Primitive variables store actual values directly, while reference variables store references to objects in memory.

Strings in Java

Strings are used to store text. In Java, strings are objects of the String class.

StringExample.java
String city = "London";
String message = "Welcome to Java";

VariablesDemo.java

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);
    }
}

Type Conversion

Sometimes values must be converted from one data type to another.

Implicit Conversion

Java automatically converts smaller types into larger compatible types.

ImplicitConversion.java
int num = 100;
double result = num;

Explicit Conversion (Casting)

Larger types can be manually converted into smaller types using casting.

CastingExample.java
double value = 9.78;
int result = (int) value;
⚠️ Warning
Explicit casting may cause data loss. For example, converting 9.78 to int removes the decimal part.

Variable Naming Rules

  • Variable names are case-sensitive.
  • Names cannot start with numbers.
  • Spaces are not allowed.
  • Use meaningful names for readability.
GoodNaming.java
int studentAge = 20;
double accountBalance = 1500.75;

Common Beginner Mistakes

  • Using variables before initialization
  • Assigning incorrect data types
  • Forgetting quotes around strings
  • Using single quotes for strings instead of double quotes
  • Confusing = with ==
🎯 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.

🎯 Exercise 3.2

Create variables for:

  • Your name
  • Your age
  • Your height
  • Whether you are a student

Print all values using System.out.println().

🚀 Challenge Exercise

Create a simple billing program that stores:

  • Product name
  • Price
  • Quantity

Then calculate and print the total bill amount.

Summary

In this lecture, you learned about variables, primitive data types, reference types, type conversion, and variable naming rules.

Understanding data types is essential because every Java program relies heavily on storing and manipulating data correctly.

In the next lecture, we will explore operators and expressions in Java.

Lecture 04 · Foundations

Control Flow

Beginner ~45 min Requires: Lecture 03

Introduction

Control flow determines how a program makes decisions and repeats tasks. Without control flow, Java programs would execute statements one after another without any logic or flexibility.

Using conditions and loops, programs can:

  • Make decisions
  • Repeat actions
  • Handle different situations dynamically
  • Process large amounts of data efficiently
💡 Real-World Analogy
Traffic lights are an example of control flow: if the light is green → move, else if yellow → slow down, else → stop.

Conditional Statements

Conditional statements allow programs to execute code only when certain conditions are true.

If / else

The if statement checks a condition. If the condition evaluates to true, the code block executes.

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");
}

Understanding the Logic

  • if → Executes when the first condition is true
  • else if → Checks additional conditions
  • else → Executes if none of the conditions are true
✅ Best Practice
Use indentation and braces properly to make conditions easier to read and debug.

Nested If Statements

An if statement can exist inside another if statement. This is called nesting.

NestedIf.java
int age = 20;
boolean hasTicket = true;

if (age >= 18) {
    if (hasTicket) {
        System.out.println("Entry allowed");
    }
}

Comparison Operators

Conditions usually rely on comparison operators.

Operator Meaning
== Equal to
!= Not equal to
> Greater than
< Less than
>= Greater than or equal to
<= Less than or equal to

Logical Operators

Logical operators combine multiple conditions.

Operator Description
&& Logical AND
|| Logical OR
! Logical NOT
LogicalDemo.java
int age = 22;
boolean isStudent = true;

if (age >= 18 && isStudent) {
    System.out.println("Eligible for student discount");
}

Switch Statement

The switch statement is useful when comparing one value against multiple possible cases.

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);
🧠 Modern Java
Java 17 introduced enhanced switch expressions that are cleaner and safer than older switch syntax.

Loops

Loops allow code to repeat automatically until a condition becomes false.

  • for loop → Best for counting and fixed repetitions
  • enhanced for loop → Best for arrays and collections
  • while loop → Repeats while a condition is true
  • do-while loop → Executes at least once before checking the condition

For Loop

ForLoop.java
for (int i = 1; i <= 5; i++) {
    System.out.println(i);
}

Enhanced For Loop

Enhanced for loops simplify iteration through arrays and collections.

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

for (int n : numbers) {
    sum += n;
}

System.out.println("Sum = " + sum);

While Loop

A while loop executes as long as its condition remains true.

WhileDemo.java
int count = 1;

while (count <= 5) {
    System.out.println(count);
    count++;
}

Do-While Loop

A do-while loop executes the block first, then checks the condition.

DoWhileDemo.java
int number = 1;

do {
    System.out.println(number);
    number++;
} while (number <= 3);

Break and Continue

Java provides special statements to control loop execution.

  • break → Exits the loop immediately
  • continue → Skips the current iteration
BreakContinue.java
for (int i = 1; i <= 10; i++) {

    if (i == 5) {
        continue;
    }

    if (i == 8) {
        break;
    }

    System.out.println(i);
}

Common Beginner Mistakes

  • Using = instead of == in conditions
  • Creating infinite loops accidentally
  • Forgetting curly braces in nested conditions
  • Using incorrect loop conditions
  • Confusing break and continue
⚠️ Infinite Loop Warning
Always ensure loop conditions eventually become false, otherwise the program may run forever.
🎯 Exercise 4.1

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

🎯 Exercise 4.2

Write a program that checks whether a number is even or odd using an if-else statement.

🎯 Exercise 4.3

Create a multiplication table generator using a for loop.

🚀 Challenge Exercise

Create a number guessing game where:

  • The program stores a secret number
  • The user repeatedly guesses the number
  • The program prints whether the guess is too high or too low
  • The loop ends when the correct number is guessed

Summary

In this lecture, you learned how Java programs make decisions and repeat tasks using conditions and loops.

You explored:

  • if-else statements
  • switch expressions
  • for, while, and do-while loops
  • break and continue statements

In the next lecture, we will begin working with methods and modular programming in Java.

Lecture 05 · Foundations

Methods

Beginner ~40 min Requires: Lecture 04

Introduction

Methods are reusable blocks of code that perform specific tasks. They help organize programs into smaller, manageable pieces.

Instead of writing the same code repeatedly, developers create methods and call them whenever needed.

💡 Real-World Analogy
Think of a method like a coffee machine button. Pressing the button performs a predefined task automatically.

Why Methods Matter

  • Reduce code duplication
  • Improve readability
  • Make debugging easier
  • Encourage modular programming
  • Allow code reuse across projects

Method Syntax

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

MethodSyntax.java
accessModifier returnType methodName(parameters) {

    // method body

}

Understanding Method Components

Component Description
public Access modifier
static Belongs to the class instead of an object
int Return type
add Method name
(int a, int b) Parameters

Creating Methods

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;
    }
}

Calling Methods

Methods do nothing until they are called.

MethodCall.java
int result = MathUtils.add(5, 10);

System.out.println(result);

Return Values

Methods can return values back to the caller using the return keyword.

The return type determines what type of value the method sends back.

✅ Important
If a method does not return anything, use the void return type.
VoidMethod.java
public static void greet() {
    System.out.println("Welcome!");
}

Method Parameters

Parameters allow methods to receive input values.

ParametersDemo.java
public static void displayName(String name) {

    System.out.println("Hello " + name);

}

Method Arguments vs Parameters

Term Meaning
Parameter Variable in the method definition
Argument Actual value passed to the method

Parameter Passing

  • Primitives are passed by value — the method receives a copy.
  • Objects are passed by reference value — the method receives a copy of the reference, allowing it to modify the object.
🧠 Key Concept
Changing a primitive inside a method does not affect the original variable outside the method.

Method Overloading

Method overloading allows multiple methods to share the same name but use different parameter lists.

Java identifies overloaded methods based on:

  • Number of parameters
  • Parameter types
  • Order of parameters
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;
    }
}

Recursive Methods

A recursive method is a method that calls itself.

Recursion is commonly used for mathematical problems, trees, and algorithms.

RecursionDemo.java
public static int factorial(int n) {

    if (n == 1) {
        return 1;
    }

    return n * factorial(n - 1);
}
⚠️ Warning
Recursive methods must include a stopping condition, otherwise they can cause infinite recursion and stack overflow errors.

Scope of Variables

Variables declared inside methods are called local variables.

They can only be accessed within that method.

ScopeDemo.java
public static void test() {

    int number = 10;

    System.out.println(number);
}

Main Method

Every Java application starts execution from the main() method.

MainMethod.java
public static void main(String[] args) {

    System.out.println("Program started");

}

Common Beginner Mistakes

  • Forgetting the return statement
  • Using the wrong return type
  • Calling non-static methods incorrectly
  • Passing incorrect arguments
  • Creating recursive methods without a base case
🎯 Exercise 5.1

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

🎯 Exercise 5.2

Create a method called isEven() that returns true if a number is even, otherwise returns false.

🎯 Exercise 5.3

Write a calculator program that uses separate methods for:

  • Addition
  • Subtraction
  • Multiplication
  • Division
🚀 Challenge Exercise

Build a menu-driven banking simulation using methods.

Include methods for:

  • Deposit
  • Withdraw
  • Check balance
  • Exit program

Summary

In this lecture, you learned how methods help organize and reuse code in Java.

You explored:

  • Method syntax
  • Parameters and return values
  • Method overloading
  • Recursion
  • Variable scope

Methods are one of the most important building blocks in Java programming. They are heavily used in every real-world application.

In the next lecture, we will begin learning Object-Oriented Programming (OOP) concepts in Java.

Lecture 06 · Core Concepts

Object-Oriented Programming

Intermediate ~60 min Requires: Lecture 05

Introduction

Object-Oriented Programming (OOP) is a programming paradigm based on the concept of objects.

Objects combine both:

  • Data → variables and fields
  • Behavior → methods and actions

Java is primarily an object-oriented programming language, and most real-world Java applications are built using OOP principles.

💡 Real-World Analogy
A car is an object. It has properties like color and speed, and behaviors like start(), brake(), and accelerate().

Why OOP Matters

  • Improves code organization
  • Encourages code reuse
  • Makes applications easier to maintain
  • Supports scalable software development
  • Helps teams collaborate efficiently

Core Pillars

  • Encapsulation – hide internal state behind private fields and expose behaviour via methods.
  • Inheritance – reuse code with extends.
  • Polymorphism – treat objects of different subclasses uniformly, often via interfaces.
  • Abstraction – define contracts with abstract classes or interfaces.

Classes and Objects

A class is a blueprint used to create objects.

An object is an instance of a class.

✅ Important
Classes define structure and behavior, while objects represent actual data in memory.

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++;
    }
}

Creating Objects

Objects are created using the new keyword.

ObjectDemo.java
Person person = new Person("Alice", 25);

System.out.println(person.getName());

Encapsulation

Encapsulation means hiding internal data and controlling access through methods.

In Java, this is commonly done using:

  • private fields
  • Getter methods
  • Setter methods
🧠 Why Encapsulation?
Encapsulation protects objects from invalid or unsafe modifications.

Getter and Setter Methods

GettersSetters.java
private double balance;

public double getBalance() {
    return balance;
}

public void setBalance(double value) {

    if (value >= 0) {
        balance = value;
    }
}

Constructors

Constructors are special methods used to initialize objects.

A constructor:

  • Has the same name as the class
  • Does not have a return type
  • Runs automatically when an object is created
✅ Constructor Tip
Constructors help ensure objects start in a valid state.

Inheritance

Inheritance allows one class to inherit properties and methods from another class.

The child class uses the extends keyword.

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;
    }
}

The super Keyword

The super keyword refers to the parent class.

It is commonly used to:

  • Call parent constructors
  • Access parent methods
  • Reuse inherited functionality

Method Overriding

Method overriding allows subclasses to provide their own implementation of inherited methods.

OverrideDemo.java
class Animal {

    void sound() {
        System.out.println("Animal sound");
    }
}

class Dog extends Animal {

    @Override
    void sound() {
        System.out.println("Bark");
    }
}

Polymorphism

Polymorphism allows one interface or parent class reference to represent many different object types.

PolymorphismDemo.java
Animal pet = new Dog();

pet.sound();
🧠 Dynamic Method Dispatch
Java decides which overridden method to execute at runtime, not compile time.

Abstraction

Abstraction focuses on hiding implementation details and showing only essential behavior.

Java supports abstraction using:

  • Abstract classes
  • Interfaces

Abstract Class Example

Shape.java
abstract class Shape {

    abstract double area();

}

Interface Example

Vehicle.java
interface Vehicle {

    void start();

}

Access Modifiers

Modifier Accessibility
public Accessible everywhere
private Accessible only within the same class
protected Accessible in subclasses and same package
default Accessible only within the same package

Composition vs Inheritance

Inheritance represents an "is-a" relationship, while composition represents a "has-a" relationship.

Example:

  • A Dog is an Animal → inheritance
  • A Car has an Engine → composition

Common Beginner Mistakes

  • Making everything public
  • Confusing classes and objects
  • Forgetting constructors
  • Using inheritance unnecessarily
  • Ignoring encapsulation principles
⚠️ OOP Advice
Good object-oriented design focuses on simplicity, readability, and maintainability.
🎯 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.

🎯 Exercise 6.2

Create a class called BankAccount with:

  • Private balance field
  • Deposit method
  • Withdraw method
  • Getter method for balance
🎯 Exercise 6.3

Create an abstract class called Employee and subclasses:

  • Developer
  • Manager

Override a method called work().

🚀 Challenge Exercise

Build a mini school management system using OOP concepts.

Include:

  • Student class
  • Teacher class
  • Course class
  • Inheritance and polymorphism
  • Encapsulation using private fields

Summary

In this lecture, you explored the foundations of Object-Oriented Programming in Java.

You learned about:

  • Classes and objects
  • Encapsulation
  • Inheritance
  • Polymorphism
  • Abstraction
  • Constructors and access modifiers

OOP is one of the most important concepts in modern software development and is heavily used in enterprise Java applications, Android development, and backend systems.

In the next lecture, we will begin working with collections and advanced data handling in Java.

Lecture 07 · Core Concepts

Collections Framework

Intermediate ~55 min Requires: Lecture 06

Introduction to the Collections Framework

The Java Collections Framework is a powerful set of classes and interfaces used to store, organize, and manage groups of objects efficiently. Instead of manually creating arrays and complex data structures, Java provides ready-made implementations for common programming needs.

The framework includes dynamic lists, sets, maps, queues, iterators, sorting utilities, and algorithms that are optimized and widely used in real-world software development.

💡 Key Concept
Collections are dynamic data structures that can grow and shrink automatically during program execution.

Why Collections?

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

  • Dynamic sizing unlike fixed-size arrays
  • Built-in algorithms for sorting and searching
  • Improved code readability
  • Reusable and optimized implementations
  • Support for generics and type safety

Collection Hierarchy Overview

The Collections Framework is built using interfaces and classes.

Collection
 ├── List
 │    ├── ArrayList
 │    └── LinkedList
 │
 ├── Set
 │    ├── HashSet
 │    └── TreeSet
 │
 └── Queue
      └── PriorityQueue

Map
 ├── HashMap
 └── TreeMap

Different collection types are designed for different use cases.

Generics in Collections

Collections usually use Generics to specify the type of data they store.

ArrayList<String> names = new ArrayList<>();

This ensures type safety and prevents storing incorrect object types.

✅ Best Practice
Always use generics with collections to avoid unnecessary type casting and runtime errors.

List – ordered, duplicates allowed

A List stores elements in insertion order and allows duplicate values.

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);
        }
    }
}

Common List Methods

names.add("Charlie");
names.remove("Bob");
names.get(0);
names.size();
names.contains("Alice");
Method Purpose
add() Adds an element
remove() Removes an element
get() Accesses an element by index
size() Returns collection size
contains() Checks if an element exists

ArrayList vs LinkedList

ArrayList LinkedList
Fast random access Fast insertions/removals
Uses dynamic arrays Uses linked nodes
Better for reading data Better for modifying data frequently

Set – unique elements

A Set stores unique elements and automatically removes duplicates.

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);
    }
}

HashSet vs TreeSet

HashSet TreeSet
Unordered Sorted automatically
Faster operations Slightly slower
Uses hashing Uses tree structure

Map – key/value pairs

A Map stores data as 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);
    }
}

Common Map Methods

scores.put("Charlie", 88);
scores.get("Alice");
scores.remove("Bob");
scores.containsKey("Alice");
scores.containsValue(95);

HashMap vs TreeMap

HashMap TreeMap
Unordered keys Sorted keys
Faster performance Maintains natural ordering
Uses hashing Uses balanced trees

Iterating Through Collections

Collections can be traversed using loops or iterators.

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

Enhanced for-loops are cleaner and easier to read.

Sorting Collections

The Collections utility class provides sorting methods.

Collections.sort(names);

You can also sort in reverse order:

Collections.sort(names, Collections.reverseOrder());

Queue Interface

A Queue processes elements in FIFO (First In First Out) order.

Queue<Integer> queue = new LinkedList<>();

queue.add(10);
queue.add(20);

System.out.println(queue.poll());

poll() removes and returns the first element.

Collections Utility Class

The Collections class provides useful helper methods.

Collections.max(numbers);
Collections.min(numbers);
Collections.shuffle(numbers);

Common Beginner Mistakes

  • Using raw collections without generics
  • Choosing the wrong collection type
  • Forgetting that Sets remove duplicates
  • Accessing invalid indexes in Lists
  • Confusing HashMap ordering behavior
🚀 Real-World Insight
The Collections Framework is heavily used in backend systems, Android apps, enterprise software, APIs, databases, and game development.
🎯 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.

🎯 Exercise 7.2

Create an ArrayList<Integer> and write a program that sorts numbers in ascending and descending order.

🎯 Exercise 7.3

Create a HashSet<String> to store usernames and prevent duplicate registrations.

Lecture 08 · Core Concepts

Generics

Intermediate ~45 min Requires: Lecture 07

Introduction to Generics

Generics are one of Java’s most powerful features. They allow classes, interfaces, and methods to work with different data types while maintaining type safety.

Before Generics were introduced in Java 5, developers had to store objects using the Object class, which required manual casting and increased the chance of runtime errors.

💡 Key Concept
Generics allow you to write reusable and type-safe code that works with many different data types.

Why Generics?

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

  • Improves code reusability
  • Eliminates unnecessary type casting
  • Provides compile-time type checking
  • Makes code cleaner and easier to maintain
  • Used heavily in the Collections Framework

Problem Without Generics

Before Generics, collections stored data as Object types.

WithoutGenerics.java
import java.util.*;

public class WithoutGenerics {

    public static void main(String[] args) {

        ArrayList list = new ArrayList();

        list.add("Hello");
        list.add(100);

        String text = (String) list.get(0);

        System.out.println(text);
    }
}

This approach is unsafe because incorrect casting can cause runtime exceptions.

Using Generics Properly

With Generics, you specify the exact type of data allowed.

GenericList.java
import java.util.*;

public class GenericList {

    public static void main(String[] args) {

        ArrayList<String> names = new ArrayList<>();

        names.add("Alice");
        names.add("Bob");

        String first = names.get(0);

        System.out.println(first);
    }
}

Now Java automatically ensures only String values can be stored.

✅ Best Practice
Always use Generics with collections to avoid unsafe casting and runtime type errors.

Generic Type Parameters

Generic classes use placeholder type names inside angle brackets <>.

Type Parameter Meaning
T Type
E Element
K Key
V Value
N Number

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;
    }
}

Creating Generic Objects

Box<String> textBox = new Box<>("Hello");
Box<Integer> numberBox = new Box<>(100);

System.out.println(textBox.get());
System.out.println(numberBox.get());

The same class works with multiple data types.

Generic Methods

Methods can also use Generics independently of the class.

GenericMethod.java
public class GenericMethod {

    public static <T>
    void printData(T value) {

        System.out.println(value);
    }

    public static void main(String[] args) {

        printData("Java");
        printData(500);
        printData(3.14);
    }
}

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;
    }
}

This method only accepts numeric types such as Integer, Double, or Float.

Wildcards in Generics

Wildcards make Generics more flexible.

List<?> items;

The ? symbol means “unknown type”.

Wildcard Description
? Any type
? extends Number Number or subclass
? super Integer Integer or parent class

Generic Interfaces

Interfaces can also use Generics.

interface Storage<T> {
    void save(T item);
    T load();
}

Type Erasure

Java implements Generics using a concept called Type Erasure.

During compilation, generic type information is removed and converted into regular Java bytecode. This means Generics provide compile-time safety but do not exist at runtime.

⚠️ Important Limitation
Because of type erasure, you cannot create objects like new T() directly inside generic classes.

Common Beginner Mistakes

  • Using raw types instead of generic types
  • Forgetting angle brackets <>
  • Using incorrect type parameters
  • Mixing incompatible generic types
  • Assuming Generics exist at runtime

Real-World Usage of Generics

Generics are heavily used throughout modern Java development.

  • Collections Framework
  • Spring Boot applications
  • REST APIs
  • Database frameworks like Hibernate
  • Reusable utility libraries
🎯 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.

🎯 Exercise 8.2

Create a generic method called display() that prints arrays of different types such as integers, strings, and doubles.

🎯 Exercise 8.3

Create a generic class called Storage<T> that stores and retrieves one object of any type.

Lecture 09 · Advanced

Streams & Lambdas (Java 8+)

Intermediate ~55 min Requires: Lecture 08

Introduction to Functional Programming in Java

Java 8 introduced one of the biggest upgrades in Java history: Lambda Expressions and the Stream API. These features allow developers to write cleaner, shorter, and more expressive code using a functional programming style.

Instead of manually writing loops and temporary variables, Streams allow operations such as filtering, mapping, sorting, and collecting data in a concise pipeline.

💡 Key Concept
Lambdas define behavior, while Streams process collections of data using that behavior.

What is a Lambda Expression?

A lambda expression is an anonymous function that can be passed as data.

It allows you to write compact code without creating separate classes for simple operations.

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
    }
}

Understanding Lambda Structure

(parameters) -> expression
Part Purpose
(parameters) Input values
-> Lambda operator
expression Code to execute

More Lambda Examples

() -> System.out.println("Hello")

x -> x * x

(a, b) -> a + b

Lambdas become especially powerful when combined with collections and streams.

Functional Interfaces

A lambda expression works with a functional interface, which is an interface containing only one abstract method.

@FunctionalInterface
interface Calculator {
    int operate(int a, int b);
}

You can then provide implementations using lambdas:

Calculator add = (a, b) -> a + b;

System.out.println(add.operate(5, 3));

Common Built-in Functional Interfaces

Interface Purpose
Predicate<T> Returns true or false
Function<T,R> Transforms data
Consumer<T> Consumes data without returning
Supplier<T> Provides data

What is the Stream API?

The Stream API processes collections of data in a declarative and functional way.

Streams do not store data themselves — they operate on existing collections like lists and sets.

✅ Important
Streams are designed for data processing, not data storage.

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)
                           .mapToInt(n -> n * n)
                           .sum();

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

Understanding Stream Operations

Operation Purpose
filter() Select matching elements
map() Transform elements
sorted() Sort elements
collect() Store results
forEach() Iterate through elements

Filtering Data

List<Integer> nums = Arrays.asList(1,2,3,4,5,6);

nums.stream()
    .filter(n -> n % 2 == 0)
    .forEach(System.out::println);

The filter() method keeps only matching elements.

Mapping Data

The map() method transforms each element.

names.stream()
     .map(String::toUpperCase)
     .forEach(System.out::println);

Sorting Streams

numbers.stream()
       .sorted()
       .forEach(System.out::println);

You can also use custom comparators with lambdas.

numbers.stream()
       .sorted((a, b) -> b - a)
       .forEach(System.out::println);

Collecting Results

The collect() method converts stream results into collections.

List<String> result = names.stream()
                           .map(String::toUpperCase)
                           .collect(Collectors.toList());

Method References

Method references are a shorter form of lambdas.

names.forEach(System.out::println);

This is equivalent to:

names.forEach(name -> System.out.println(name));

Stream Pipeline Stages

A stream pipeline usually has three stages:

  1. Source → collection or array
  2. Intermediate operations → filter, map, sorted
  3. Terminal operation → collect, sum, forEach

Advantages of Streams

  • Cleaner and shorter code
  • Less manual looping
  • Easier data transformations
  • Supports parallel processing
  • Improved readability

Parallel Streams

Java can process streams in parallel using multiple CPU cores.

numbers.parallelStream()
       .forEach(System.out::println);
⚡ Performance Insight
Parallel streams can improve performance for large datasets, but they may introduce overhead for small tasks.

Common Beginner Mistakes

  • Trying to reuse a stream after a terminal operation
  • Using streams for extremely simple tasks unnecessarily
  • Confusing map() with filter()
  • Overusing parallel streams
  • Writing overly complex lambda expressions

Real-World Usage

Streams and Lambdas are heavily used in modern Java applications.

  • Spring Boot applications
  • REST APIs
  • Data processing systems
  • Backend enterprise software
  • Big-data pipelines
🎯 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.

🎯 Exercise 9.2

Create a list of integers and use streams to calculate the sum of all odd numbers.

🎯 Exercise 9.3

Create a lambda expression that checks whether a number is positive, negative, or zero.

Lecture 10 · Advanced

Exception Handling

Intermediate ~45 min Requires: Lecture 09

Checked vs Unchecked

  • Checked – compiler forces you to handle or declare (IOException, SQLException).
  • Unchecked – subclasses of RuntimeException (e.g. NullPointerException) need not be declared.

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:

  • Add a task (string description).
  • Mark a task as completed.
  • Delete a task.
  • Display all tasks with status.
  • Persist tasks to a plain‑text file on exit and reload them on start.

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

  • Class design & encapsulation.
  • Collections (ArrayList) and streaming operations.
  • File I/O with NIO.
  • Basic console UI handling.
  • Exception handling for I/O and parsing errors.
🎯 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.
Lecture 13 · Advanced

Interfaces & Abstract Classes

Intermediate ~50 min Requires: Lecture 12

Content coming soon...

Lecture 14 · Advanced

Enums & Annotations

Intermediate ~45 min Requires: Lecture 13

Content coming soon...

Lecture 15 · Advanced

File I/O & NIO

Intermediate ~50 min Requires: Lecture 14

Content coming soon...

Lecture 16 · Advanced

Networking & Sockets

Intermediate ~55 min Requires: Lecture 15

Content coming soon...

Lecture 17 · Professional

JDBC & Databases

Advanced ~60 min Requires: Lecture 16

Content coming soon...

Lecture 18 · Professional

Unit Testing (JUnit)

Advanced ~50 min Requires: Lecture 17

Content coming soon...

Lecture 19 · Professional

Design Patterns

Advanced ~55 min Requires: Lecture 18

Content coming soon...

Lecture 20 · Professional

Build Tools (Maven & Gradle)

Advanced ~50 min Requires: Lecture 19

Content coming soon...

Lecture 21 · Professional

Introduction to Spring Boot

Advanced ~60 min Requires: Lecture 20

Content coming soon...

Lecture 22 · Professional

Final Project — Enterprise Java App

Advanced ~90 min Requires: All Previous

Content coming soon...