2026-04-05

Java Basics to Advanced: A Practical Guide for Students

Who this is for: students (or anyone) who want a practical path from zero to productive Java developer.

What you will get: short explanations, runnable examples, exercises, and a small CLI mini‑project. Examples target modern Java (17+), but most code works on 11+.


0) Setup and Running Code

  • Install JDK 17 or later (Temurin or Oracle JDK).
  • Verify: java -version and javac -version.

Compile and run a single file:

javac Main.java
java Main

Minimal Gradle build (optional):

plugins {
    id 'java'
}
repositories {
    mavenCentral()
}
dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
}
test {
    useJUnitPlatform()
}

Project structure (Gradle/Maven convention):

src/
  main/java/... your .java files
  test/java/... your test files

1) First Java Program

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello, Java!");
    }
}
  • public static void main(String[] args) is the entry point.
  • System.out.println(...) prints a line.

Try: change the message, recompile, rerun.


2) Variables, Types, and Operators

Primitives: int, long, double, boolean, char. Reference types: String, arrays, classes you define.

int age = 20;
double gpa = 3.7;
boolean enrolled = true;
char grade = 'A';
String name = "Ada";
final double PI = 3.14159; // constant
var count = 5;              // local type inference (Java 10+)

Operators: + - * / %, comparisons == != < <= > >=, logical && || !.

Watch out: integer division truncates: 5 / 2 == 2.


3) Control Flow

// if / else
int n = 7;
if (n % 2 == 0) {
    System.out.println("even");
} else {
    System.out.println("odd");
}

// switch (enhanced)
import java.time.DayOfWeek;
DayOfWeek day = DayOfWeek.MONDAY;
String type = switch (day) {
    case SATURDAY, SUNDAY -> "WEEKEND";
    default -> "WEEKDAY";
};
System.out.println(type);

// loops
for (int i = 0; i < 3; i++) System.out.println(i);
int k = 3;
while (k > 0) { k--; }
for (int x : new int[]{1,2,3}) System.out.println(x);

4) Methods (Functions)

static int add(int a, int b) { return a + b; }
static double mean(int a, int b, int c) { return (a + b + c) / 3.0; }

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

Tip: keep methods short and single‑purpose.


5) Classes and Objects

Define a simple Student type with identity, behavior, and invariants.

import java.util.*;

public class Student {
    private final String id;          // identity, immutable
    private String name;              // mutable
    private final List<Integer> scores = new ArrayList<>();

    public Student(String id, String name) {
        if (id == null || id.isBlank()) throw new IllegalArgumentException("id required");
        this.id = id;
        this.name = name;
    }

    public String getId() { return id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public void addScore(int s) {
        if (s < 0 || s > 100) throw new IllegalArgumentException("0..100 only");
        scores.add(s);
    }

    public double average() {
        return scores.stream().mapToInt(Integer::intValue).average().orElse(0.0);
    }

    @Override public String toString() {
        return "Student{" + id + ", name=" + name + ", avg=" + average() + "}";
    }

    @Override public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student other)) return false;
        return id.equals(other.id);
    }

    @Override public int hashCode() { return id.hashCode(); }
}

Notes:

  • Use equals/hashCode for logical equality (here: same id).
  • Use final for fields that should not change.

6) Collections and Generics

import java.util.*;

List<Student> students = new ArrayList<>();
students.add(new Student("s1", "Ada"));
students.add(new Student("s2", "Grace"));

Set<String> courses = new HashSet<>();
courses.add("CS101"); courses.add("CS101"); // still size 1 because a Set is unique

Map<String, Integer> credits = new HashMap<>();
credits.put("CS101", 4);
credits.put("MATH", 3);

A tiny generic type:

class Box<T> {
    private T value;
    public void set(T v) { value = v; }
    public T get() { return value; }
}

Box<Integer> b = new Box<>();
b.set(42);
int x = b.get();

7) Inheritance, Interfaces, and Polymorphism

Prefer composition. Use inheritance for true “is‑a” relationships.

interface Gradable { double score(); }

abstract class Person {
    private final String id;
    protected Person(String id) { this.id = id; }
    public String id() { return id; }
}

class Undergraduate extends Person implements Gradable {
    private final int midterm, fin;
    public Undergraduate(String id, int midterm, int fin) {
        super(id);
        this.midterm = midterm; this.fin = fin;
    }
    @Override public double score() { return midterm * 0.4 + fin * 0.6; }
}

class Graduate extends Person implements Gradable {
    private final int research, defense;
    public Graduate(String id, int research, int defense) {
        super(id);
        this.research = research; this.defense = defense;
    }
    @Override public double score() { return research * 0.7 + defense * 0.3; }
}

List<Gradable> list = List.of(
    new Undergraduate("u1", 80, 90),
    new Graduate("g1", 95, 85)
);
for (Gradable g : list) System.out.println(g.score());

8) Exceptions and Resource Safety

try {
    Student s = new Student("s3", "Lin");
    s.addScore(120); // will throw
} catch (IllegalArgumentException e) {
    System.out.println("Bad input: " + e.getMessage());
}

Try‑with‑resources auto‑closes files/streams:

import java.nio.file.*;
import java.io.*;

Path p = Path.of("notes.txt");
try (BufferedWriter w = Files.newBufferedWriter(p)) {
    w.write("study hard");
}

Custom exception:

class InvalidScoreException extends RuntimeException {
    public InvalidScoreException(String msg) { super(msg); }
}

9) I/O in One File (NIO)

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

Path p = Path.of("students.txt");
Files.writeString(p, "s1,Ada\ns2,Grace\n");
List<String> lines = Files.readAllLines(p);
for (String line : lines) System.out.println(line);

10) Lambdas, Streams, and Optional

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

List<Student> students = List.of(
    new Student("s1", "Ada"),
    new Student("s2", "Grace"),
    new Student("s3", "Lin")
);

// Suppose we add some scores
students.get(0).addScore(100); students.get(0).addScore(90);
students.get(1).addScore(70); students.get(1).addScore(85);
students.get(2).addScore(88);

// Top performers (avg >= 90), sorted by name
List<String> top = students.stream()
    .filter(s -> s.average() >= 90)
    .sorted(Comparator.comparing(Student::getName))
    .map(Student::getName)
    .toList();
System.out.println(top);

// Optional
students.stream()
    .max(Comparator.comparingDouble(Student::average))
    .ifPresent(s -> System.out.println("Top: " + s.getName()));

11) Concurrency Basics

Use executors instead of raw threads.

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

ExecutorService pool = Executors.newFixedThreadPool(4);
try {
    List<Callable<Integer>> tasks = List.of(
        () -> { Thread.sleep(200); return 1; },
        () -> { Thread.sleep(150); return 2; },
        () -> { Thread.sleep(100); return 3; }
    );
    List<Future<Integer>> futures = pool.invokeAll(tasks);
    int sum = 0;
    for (Future<Integer> f : futures) sum += f.get();
    System.out.println(sum);
} finally {
    pool.shutdown();
}

CompletableFuture for composition:

import java.util.concurrent.*;

CompletableFuture<Integer> a = CompletableFuture.supplyAsync(() -> 40);
CompletableFuture<Integer> b = CompletableFuture.supplyAsync(() -> 2);
int result = a.thenCombine(b, Integer::sum).join();
System.out.println(result);

12) Packages and Organization

Create packages to avoid name clashes:

package com.example.school;

public class App { /* ... */ }

Folder must match: src/main/java/com/example/school/App.java.


13) Testing with JUnit 5

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

public class StudentTest {
    @Test void averageWorks() {
        Student s = new Student("s1", "Ada");
        s.addScore(100); s.addScore(80);
        assertEquals(90.0, s.average(), 0.0001);
    }
}

Run: ./gradlew test or your IDE’s test runner.


14) Mini Project: Gradebook CLI

A tiny loop that accepts commands: add, score, avg, top, quit.

import java.util.*;

public class Gradebook {
    private final Map<String, Student> byId = new HashMap<>();

    public void add(String id, String name) {
        byId.putIfAbsent(id, new Student(id, name));
    }

    public void score(String id, int s) {
        Student st = byId.get(id);
        if (st == null) { System.out.println("no such id"); return; }
        st.addScore(s);
    }

    public void avg(String id) {
        Student st = byId.get(id);
        System.out.println(st == null ? "no such id" : st.average());
    }

    public void top() {
        byId.values().stream()
            .max(Comparator.comparingDouble(Student::average))
            .ifPresent(st -> System.out.println(st.getId() + ": " + st.average()));
    }

    public static void main(String[] args) {
        Gradebook g = new Gradebook();
        Scanner sc = new Scanner(System.in);
        System.out.println("commands: add id name | score id n | avg id | top | quit");
        while (true) {
            System.out.print("> ");
            String cmd = sc.next();
            switch (cmd) {
                case "add" -> g.add(sc.next(), sc.next());
                case "score" -> g.score(sc.next(), sc.nextInt());
                case "avg" -> g.avg(sc.next());
                case "top" -> g.top();
                case "quit" -> { return; }
                default -> System.out.println("?");
            }
        }
    }
}

Try it: add students and scores, then check top.


15) Common Pitfalls

  • == vs equals: use equals for object content comparison (e.g., String), == for primitives and reference identity.
  • Integer division: cast to double if needed, e.g., (a + b) / 2.0.
  • Nulls: avoid returning null; prefer Optional or empty collections.
  • Mutability leaks: return unmodifiable views or copies, keep fields final when possible.
  • Concurrency: never share mutable state without synchronization; prefer immutable data and high‑level APIs.

16) Exercises

  • Basics: write a method that returns the factorial of n. Handle n < 0 with an exception.
  • Classes: extend Student to include Map<String,Integer> per‑course scores and compute per‑course averages.
  • Collections: read students.csv and build Map<String, List<Integer>> of scores by id.
  • Streams: given a list of students, produce the top 3 by average and print names.
  • Exceptions: create InvalidGradeException and use it when adding out‑of‑range grades.
  • Concurrency: run 4 tasks that each sleep a random time and return a number; sum results with CompletableFuture.allOf.
  • Testing: add tests for edge cases (empty scores, one score, very large inputs).

17) Next Steps

  • Learn about records (concise immutable data carriers), enums, and sealed classes.
  • Explore the Java Module System for larger apps.
  • Practice with a web stack: Spring Boot or a lightweight HTTP server.
  • Read Effective Java (Joshua Bloch) and Java Language Specification sections as you advance.

Keep building small programs, write tests, and refactor often. Happy coding!