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