What is Java?
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?
- 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).
Hello World – Your First Java Program
Save the following as HelloWorld.java and run it from the command line:
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!”
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.
How Java Works
Java follows a two-step execution process:
- Compilation: The Java source file (
.java) is compiled into bytecode (.class) using the Java compiler (javac). - Execution: The JVM interprets or compiles the bytecode into machine code that your system can execute.
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.
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.
Modify the HelloWorld program to print your name instead of "Hello, World!".
Try printing multiple lines using System.out.println() and observe how output is displayed.
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 and basic structure. In the next lecture, we will dive deeper into setting up your development environment and understanding variables.
Setting up the JDK
Download & Install
Head to Eclipse Adoptium or Oracle JDK and download the appropriate bundle for your OS.
Verify Installation
java -version
javac -version
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:
- IntelliJ IDEA Community – rich refactoring, debugger, and Maven/Gradle integration.
- Eclipse – classic, plugin-heavy.
- VS Code with the
Java Extension Pack.
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.
Understanding JDK, JRE, and JVM
- JDK (Java Development Kit): Full toolkit for developing Java applications.
- JRE (Java Runtime Environment): Provides libraries and JVM to run Java programs.
- JVM (Java Virtual Machine): Executes compiled Java bytecode.
Setting Environment Variables (Important)
To run Java from any directory, you need to configure system environment variables:
Windows
- Search for Environment Variables in system settings.
- Add the JDK
binfolder path to thePATHvariable.
Linux / macOS
export JAVA_HOME=/path/to/jdk
export PATH=$JAVA_HOME/bin:$PATH
Command Line vs IDE
You can write and run Java programs using:
- Command Line: More control, better for understanding fundamentals.
- IDE: Faster development with debugging and auto-completion.
Creating Your First Project Structure
A typical Java project structure looks like this:
MyProject/
├── src/
│ └── Main.java
└── bin/
src folder contains source code, while compiled files are often placed in a separate directory.
Troubleshooting Common Issues
- 'java' is not recognized: PATH variable is not set correctly.
- Version mismatch: Multiple Java versions installed on your system.
- Permission errors: Try running terminal as administrator (Windows) or use
sudo(Linux/macOS).
Open your terminal and verify that both java and javac commands work from any directory.
Create a simple project folder manually, write a Java file, compile it using terminal, and run it without using an IDE.
Summary
In this lecture, you installed the JDK, verified your setup, and explored development tools and environment configuration. You also learned the difference between JDK, JRE, and JVM, and how to troubleshoot common setup issues. In the next lecture, we will begin writing more structured Java programs using variables and data types.
Setting up the JDK
Download & Install
Head to Eclipse Adoptium or Oracle JDK and download the appropriate bundle for your OS.
Verify Installation
java -version
javac -version
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:
- IntelliJ IDEA Community – rich refactoring, debugger, and Maven/Gradle integration.
- Eclipse – classic, plugin‑heavy.
- VS Code with the
Java Extension Pack.
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.
Variables & Data Types
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.
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); } }
Create a program that declares one variable of each primitive type, prints them, then swaps two integer values without using a third temporary variable.
Control Flow
If / else
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)
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
- for (int i=0; i<n; i++) – classic counting loop.
- enhanced for – iterate over arrays/collections
(
for (String s : list)). - while / do‑while – condition‑driven loops.
int[] numbers = { 2, 4, 6, 8 }; int sum = 0; for (int n : numbers) { sum += n; } System.out.println("Sum = " + sum);
Write a program that prints the first 20 Fibonacci numbers using a while loop.
Methods
Method Syntax
A method in Java consists of: modifiers returnType name(parameters) { body }
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
- 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 mutate the object.
Method Overloading
Same name, different parameter list.
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; } }
Write a utility class that provides overloaded factorial methods for int
and long. Add a simple main method that demonstrates both.
Object‑Oriented Programming
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.
Class definition
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
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; } }
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.
Collections Framework
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
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
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
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); } }
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.
Generics
Why Generics?
They let you write **type‑safe** collections and APIs without casting.
Simple generic class
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.
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; } }
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.
Streams & Lambdas (Java 8+)
Lambda syntax
A lambda expression is an anonymous function that can be treated as an instance of a functional interface.
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.
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); } }
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.
Exception Handling
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
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
public class InvalidAgeException extends Exception { public InvalidAgeException(String message) { super(message); } }
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.
Concurrency
Threads basics
Two ways to create a thread:
- Extend
Threadand overriderun(). - Implement
Runnable(orCallable) and pass it to aThreador anExecutorService.
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.
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(); } }
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.
Capstone Project – Console To‑Do List
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
- Model – a
Taskclass withid,descriptionandcompletedflag. - Repository – a
List<Task>stored in memory; useFilesAPI for persistence. - Service – static methods for add/save/load/delete.
- UI loop – a
while(true)menu that reads user input withScanner.
Starter Code (you’ll flesh it out)
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.
Extend the app with:
- A priority field (enum
LOW/MEDIUM/HIGH) and sort the output by priority. - Use
java.time.LocalDateto add a due‑date, and display overdue tasks. - Write JUnit tests for the
Taskclass and the persistence logic.