Truly learn Java in two weeks
This is not a dry textbook. It is a carefully designed interactive learning path — every lesson comes with runnable examples, instantly-feedback quizzes and hands-on exercises, taking you from zero to the Java level of a second-year computer science student.
Progressive learning
From variables to generics over 14 days, focusing on one core topic each day.
Instant feedback
Every topic comes with a code example and output preview, then a quiz to reinforce what you learned.
Hands-on exercises
Each lesson ends with fill-in-the-blank coding exercises to test your understanding in practice.
Java Intro & the First Program
Understand Java's features and write your first line of code.
What is Java
Java is an object-oriented, cross-platform programming language. Its core philosophy is "Write Once, Run Anywhere" — compiled bytecode runs on any platform equipped with a JVM (Java Virtual Machine).
Your First Program: Hello World
Every programmer's starting point. Here is Hello World in Java:
public class HelloWorld {
// The program entry point, the main method
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
•
public class HelloWorld — declare a public class; the class name must match the file name•
public static void main(String[] args) — the program entry point, a fixed form•
System.out.println(...) — prints a line to the console•
// — a single-line comment, ignored by the compilerCompile & Run
Java is a compiled language. A .java source file must first be compiled into .class bytecode, which is then executed by the JVM:
# 1. Compile the source file
javac HelloWorld.java
# 2. Run the bytecode
java HelloWorld
Output Methods Compared
| Method | Effect | Example |
|---|---|---|
System.out.println() | Prints and then starts a new line | println("Hi") → Hi |
System.out.print() | Prints without a newline | print("Hi") → Hi |
System.out.printf() | Formatted output | printf("%.2f", 3.14) |
public class MyFirst {
public static void main(String[] args) {
System.out.("Java is fun!");
}
} Day 1 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Variables & Data Types
Variables are containers for data; data types determine the container's size and purpose.
Primitive Data Types
Java is a strongly typed language; every variable must declare its type first. Java has 8 primitive types:
| Type | Keyword | Size | Range / Example |
|---|---|---|---|
| Integer | byte | 1 byte | -128 ~ 127 |
| Integer | short | 2 bytes | -32768 ~ 32767 |
| Integer | int | 4 bytes | about ±2.1 billion (most common) |
| Integer | long | 8 bytes | very large; add L suffix |
| Float | float | 4 bytes | add f suffix |
| Float | double | 8 bytes | default floating-point type |
| Char | char | 2 bytes | a single Unicode character |
| Boolean | boolean | 1 bit | true / false |
public class Types {
public static void main(String[] args) {
int age = 20;
double price = 19.99;
char grade = 'A';
boolean isStudent = true;
long population = 7900000000L;
float pi = 3.14f;
System.out.println("Age: " + age);
System.out.println("Price: " + price);
System.out.println("Grade: " + grade);
System.out.println("Is student: " + isStudent);
}
}
Price: 19.99
Grade: A
Is student: true
long values need an L, float values need an f, otherwise compilation fails. Use single quotes 'A' for characters and double quotes "Hi" for strings.Variable Naming Rules
Java variable naming follows camelCase:
| Rule | Valid example | Invalid example |
|---|---|---|
| letters/digits/underscore/$; cannot start with a digit | userName | 2name |
| cannot be a keyword | myClass | class |
| case-sensitive | age ≠ Age | — |
| camelCase, meaning from its name | studentCount | sc |
Type Conversion
A small type can be automatically converted to a larger type (implicit); converting a larger type to a smaller one requires an explicit cast (which may lose precision):
public class Cast {
public static void main(String[] args) {
// automatic conversion: int → double
int i = 100;
double d = i;
System.out.println("Automatic: " + d);
// explicit cast: double → int
double pi = 3.99;
int n = (int) pi; // truncates the fractional part
System.out.println("Explicit: " + n);
}
}
Explicit: 3
The String Type
String is not a primitive type but a reference type (a class), although it is used extremely often:
public class StringDemo {
public static void main(String[] args) {
String name = "Alice";
String greeting = "Hello";
// string concatenation uses +
System.out.println(greeting + ", " + name + "!");
// common methods
System.out.println("Length: " + name.length());
System.out.println("Uppercase: " + "hello".toUpperCase());
System.out.println("Concat: " + "a".concat("b"));
}
}
Length: 5
Uppercase: HELLO
Concat: ab
public class Vars {
public static void main(String[] args) {
count = 100;
active = true;
System.out.println(count + " " + active);
}
} Day 2 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Operators & Expressions
Operators are the symbols that operate on data; mastering them is basic programming skill.
Arithmetic Operators
public class Arithmetic {
public static void main(String[] args) {
int a = 17, b = 5;
System.out.println("Add: " + (a + b)); // 22
System.out.println("Sub: " + (a - b)); // 12
System.out.println("Mul: " + (a * b)); // 85
System.out.println("Div: " + (a / b)); // 3 (integer division, truncated)
System.out.println("Mod: " + (a % b)); // 2
}
}
Sub: 12
Mul: 85
Div: 3
Mod: 2
17 / 5 gives 3, not 3.4! Dividing two integers produces an integer (truncated). To get a decimal, cast one to double: (double)a / b.Increment & Decrement
public class IncDec {
public static void main(String[] args) {
int i = 5;
System.out.println(i++); // 5 (uses, then +1)
System.out.println(i); // 6
System.out.println(++i); // 7 (+1, then uses)
}
}
6
7
i++ uses then increments; ++i increments then uses. To avoid confusion in real code, prefer writing i++; on its own line.
Relational & Logical Operators
| Category | Operator | Meaning |
|---|---|---|
| Relational | == | equal to |
!= | not equal to | |
> < | greater than / less than | |
>= <= | greater/less than or equal | |
| returns a boolean value | ||
== compares values for primitives and addresses for objects | ||
| Logical | && | AND (short-circuit): true only if both are true |
|| | OR (short-circuit): true if either is true | |
! | NOT: inverts | |
public class Logic {
public static void main(String[] args) {
int score = 75;
// logical AND
boolean pass = score >= 60 && score <= 100;
System.out.println("Pass: " + pass);
// logical OR
boolean holiday = false;
boolean weekend = true;
boolean canRest = holiday || weekend;
System.out.println("Can rest: " + canRest);
// short-circuit behavior
int x = 0;
if (x != 0 && 10 / x > 1) {
System.out.println("this line never runs");
}
System.out.println("Short-circuit avoided division by zero");
}
}
Can rest: true
Short-circuit avoided division by zero
&&, if the left side is false the right side is not evaluated; with ||, if the left side is true the right side is not evaluated. This can avoid errors like null pointers.Assignment & Ternary Operator
public class Assign {
public static void main(String[] args) {
int n = 10;
n += 5; // equivalent to n = n + 5
System.out.println("After +=: " + n); // 15
// ternary: condition ? trueValue : falseValue
int age = 17;
String status = age >= 18 ? "Adult" : "Underage";
System.out.println(status);
}
}
Underage
public class Ternary {
public static void main(String[] args) {
int score = 85;
String result = score 60 ? "Pass" : "Fail";
System.out.println(result);
}
} Day 3 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Conditional Statements
Let the program learn to "make choices" — run different code under different conditions.
if-else Statements
Conditions are evaluated from top to bottom; once one matches, its branch runs and the rest are not checked.
public class IfElse {
public static void main(String[] args) {
int score = 85;
if (score >= 90) {
System.out.println("Excellent");
} else if (score >= 80) {
System.out.println("Good");
} else if (score >= 60) {
System.out.println("Pass");
} else {
System.out.println("Fail");
}
}
}
Nested if
public class Nested {
public static void main(String[] args) {
int age = 20;
boolean hasLicense = true;
if (age >= 18) {
if (hasLicense) {
System.out.println("Can drive");
} else {
System.out.println("Adult, but no license");
}
} else {
System.out.println("Underage, cannot drive");
}
}
}
The switch Statement
When comparing a variable against several fixed values, switch is clearer than if-else:
public class Switch {
public static void main(String[] args) {
int day = 3;
switch (day) {
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
break;
case 3:
System.out.println("Wednesday");
break;
default:
System.out.println("Other");
}
}
}
break, the program "falls through" and keeps executing the following cases until a break or the end of the switch. This is usually a bug.New switch Syntax (Java 14+)
Java 14 introduced an enhanced switch using arrow syntax that does not fall through automatically:
public class SwitchNew {
public static void main(String[] args) {
int day = 3;
String name = switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
case 3 -> "Wednesday";
default -> "Other";
};
System.out.println(name);
}
}
public class ColorSwitch {
public static void main(String[] args) {
int color = 1;
switch (color) {
1:
System.out.println("Red");
;
default:
System.out.println("Unknown");
}
}
} Day 4 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Loops
Let the program repeat automatically and say goodbye to copy-paste.
The for Loop
When you know the number of iterations, the for loop is the first choice:
for (init; condition; update) — three parts separated by semicolons. The loop ends when the condition is false.
public class ForLoop {
public static void main(String[] args) {
// print 1 to 5
for (int i = 1; i <= 5; i++) {
System.out.println("Iteration " + i);
}
}
}
Iteration 2
Iteration 3
Iteration 4
Iteration 5
The while Loop
When you care only about a condition rather than a fixed count, use while:
public class WhileLoop {
public static void main(String[] args) {
int n = 1024;
int count = 0;
// keep dividing by 2 until n becomes 0
while (n > 0) {
n = n / 2;
count++;
}
System.out.println("Looped " + count + " times");
}
}
The do-while Loop
do-while executes once before checking the condition, guaranteeing at least one execution:
public class DoWhile {
public static void main(String[] args) {
int i = 10;
do {
System.out.println("i = " + i);
i++;
} while (i < 5); // condition is false, but the body ran once
}
}
break & continue
public class BreakContinue {
public static void main(String[] args) {
// break: exit the whole loop
System.out.println("=== break example ===");
for (int i = 1; i <= 10; i++) {
if (i == 5) break; // stop at 5
System.out.println(i);
}
// continue: skip this iteration, move to the next
System.out.println("=== continue example ===");
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) continue; // skip even numbers
System.out.println(i);
}
}
}
1
2
3
4
=== continue example ===
1
3
5
7
9
Nested Loops: the 9×9 Multiplication Table
public class Multiplication {
public static void main(String[] args) {
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
System.out.printf("%d×%d=%-4d", j, i, i * j);
}
System.out.println();
}
}
}
1×2=2 2×2=4
1×3=3 2×3=6 3×3=9
1×4=4 2×4=8 3×4=12 4×4=16
1×5=5 2×5=10 3×5=15 4×5=20 5×5=25
1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36
1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49
1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64
1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81
%d integer, %-4d left-aligned over 4 chars, %n newline. This is a classic interview question — make sure you understand the execution order of nested loops.public class Sum {
public static void main(String[] args) {
int sum = 0;
for (int i = 1; i 100; i++) {
sum i;
}
System.out.println("Total: " + sum);
}
} Day 5 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Methods
Methods are the basic unit for organizing code — wrap repeated logic, then call it by just a name.
Defining and Calling Methods
public class MethodBasic {
// method definition: modifier returnType methodName(parameters)
public static int add(int a, int b) {
return a + b;
}
// void for no return value
public static void greet(String name) {
System.out.println("Hello, " + name + "!");
}
public static void main(String[] args) {
int result = add(3, 5); // call a method with a return value
System.out.println("3 + 5 = " + result);
greet("Alice"); // call a method with no return value
}
}
Hello, Alice!
return returns a result and ends the method; a void method needs no return.Method Overloading
A class can have several methods with the same name as long as their parameter lists differ (count, types, order). The compiler picks the right one from the call arguments:
public class Overload {
// two ints added
public static int add(int a, int b) {
return a + b;
}
// three ints added
public static int add(int a, int b, int c) {
return a + b + c;
}
// two doubles added
public static double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
System.out.println(add(1, 2)); // calls the first
System.out.println(add(1, 2, 3)); // calls the second
System.out.println(add(1.5, 2.5)); // calls the third
}
}
6
4.0
Pass by Value
Java method parameters are passed by value — changing a primitive parameter inside a method does not affect the outside:
public class PassByValue {
public static void change(int x) {
x = 100; // only changes the copy
}
public static void main(String[] args) {
int n = 10;
change(n);
System.out.println("n = " + n); // still 10
}
}
Variable Scope
A variable is valid only within the {} where it is declared:
public class Scope {
public static void main(String[] args) {
int x = 10; // main method scope
if (x > 5) {
int y = 20; // if-block scope
System.out.println(x + y); // 30
}
// System.out.println(y); // compile error! y is not visible here
}
}
Recursion
A method calling itself is recursion. It must have a termination condition, otherwise the stack overflows. A classic example — factorial:
public class Recursion {
// n! = n * (n-1)!
public static int factorial(int n) {
if (n <= 1) return 1; // base case
return n * factorial(n - 1); // recursive call
}
public static void main(String[] args) {
System.out.println("5! = " + factorial(5)); // 120
}
}
StackOverflowError.public class MaxMethod {
public static max(int a, int b) {
if (a > b) {
a;
}
return b;
}
public static void main(String[] args) {
System.out.println(max(3, 7));
}
} Day 6 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Arrays
An array is a container storing multiple values of the same type — the most basic data structure.
Creating & Accessing
public class ArrayBasic {
public static void main(String[] args) {
// declare and initialize
int[] scores = {90, 85, 78, 92, 88};
// access elements (index starts at 0)
System.out.println("First: " + scores[0]);
System.out.println("Third: " + scores[2]);
// array length
System.out.println("Length: " + scores.length);
// modify an element
scores[1] = 95;
System.out.println("After change: " + scores[1]);
}
}
Third: 78
Length: 5
After change: 95
scores[5] throws ArrayIndexOutOfBoundsException.Other Ways to Create
public class ArrayCreate {
public static void main(String[] args) {
// way 1: declare, then allocate space
int[] a = new int[5]; // all default to 0
a[0] = 10;
// way 2: direct initialization
int[] b = {1, 2, 3};
// way 3: new + initialization
String[] names = new String[]{"Alice", "Bob", "Carol"};
System.out.println(a[0] + " " + a[1]); // 10 0
System.out.println(names.length);
}
}
3
Iterating Arrays
for-each is more concise, but it does not give you the index and cannot modify elements.
public class ArrayLoop {
public static void main(String[] args) {
int[] nums = {10, 20, 30, 40, 50};
// way 1: a normal for loop
System.out.print("for: ");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
// way 2: the enhanced for (for-each)
System.out.print("for-each: ");
for (int num : nums) {
System.out.print(num + " ");
}
System.out.println();
}
}
for-each: 10 20 30 40 50
In Practice: Finding the Max
public class ArrayMax {
public static void main(String[] args) {
int[] nums = {3, 7, 2, 9, 5, 1, 8};
int max = nums[0]; // assume the first is the largest
for (int i = 1; i < nums.length; i++) {
if (nums[i] > max) {
max = nums[i];
}
}
System.out.println("Max: " + max);
}
}
2D Arrays
public class TwoD {
public static void main(String[] args) {
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// iterate the 2D array
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
}
}
4 5 6
7 8 9
public class ArraySum {
public static void main(String[] args) {
int[] nums = {10, 20, 30};
int sum = 0;
for (int n nums) {
sum n;
}
System.out.println("Total: " + sum);
}
} Day 7 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
OOP Basics: Classes & Objects
Object-oriented programming (OOP) is the soul of Java — describe things with "classes" and operate instances as "objects".
Classes & Objects
Object = an instance of a class, created with
newFor example: Dog is a class; the dog in my home called "Buddy" is an object.
Defining Your First Class
// Student class
public class Student {
// attributes (fields / member variables)
String name;
int age;
double score;
// methods (behavior)
public void study() {
System.out.println(name + " is studying");
}
public void introduce() {
System.out.println("I am " + name + ", " + age + " years old");
}
}
public class Main {
public static void main(String[] args) {
// create an object
Student s1 = new Student();
s1.name = "Alice";
s1.age = 20;
s1.score = 92.5;
// call methods
s1.introduce();
s1.study();
// create another object
Student s2 = new Student();
s2.name = "Bob";
s2.age = 21;
s2.introduce();
}
}
Alice is studying
I am Bob, 21 years old
Member Variables vs Local Variables
| Aspect | Member variable (field) | Local variable |
|---|---|---|
| Position | Inside the class, outside methods | Inside a method |
| Default value | Yes (int→0, reference→null) | None; must initialize |
| Scope | The whole class | Inside the method |
| Storage | Heap (with the object) | Stack (with the method) |
The this Keyword
this refers to the current object and is often used to distinguish a member variable from a parameter with the same name:
public class Book {
String title;
double price;
public void setInfo(String title, double price) {
this.title = title; // this.title is the member variable
this.price = price; // the right-side title is the parameter
}
public void show() {
System.out.println(title + " price: " + price);
}
public static void main(String[] args) {
Book b = new Book();
b.setInfo("Java Basics", 59.9);
b.show();
}
}
The static Modifier
Members marked static belong to the class itself and can be used without creating an object:
public class MathUtil {
// static variable: shared by all objects
static int count = 0;
// static method: called directly with the class name
public static int square(int n) {
return n * n;
}
public MathUtil() {
count++; // each object created increments count
}
public static void main(String[] args) {
System.out.println("Square of 3: " + MathUtil.square(3));
new MathUtil();
new MathUtil();
System.out.println("Created " + MathUtil.count + " objects");
}
}
Created 2 objects
public class Test {
public static void main(String[] args) {
s = new Student();
s. = "Carol";
}
} Day 8 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Encapsulation & Constructors
Encapsulation is one of the three pillars of OOP; constructors are the entry point for creating objects.
Encapsulation: Hide Details, Expose Interfaces
The core of encapsulation: make fields private and control access through public getters/setters. The benefit is that you can add data validation in the setter:
public class Account {
private String owner;
private double balance;
// getter
public double getBalance() {
return balance;
}
// setter: adds validation logic
public void setBalance(double balance) {
if (balance < 0) {
System.out.println("Balance cannot be negative!");
return;
}
this.balance = balance;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
public class AccountTest {
public static void main(String[] args) {
Account acc = new Account();
acc.setOwner("Alice");
acc.setBalance(1000);
System.out.println(acc.getOwner() + " balance: " + acc.getBalance());
acc.setBalance(-500); // will be rejected
System.out.println(acc.getOwner() + " balance: " + acc.getBalance());
}
}
Balance cannot be negative!
Alice balance: 1000.0
public (anyone can access), private (this class only), protected (same package + subclasses), default (same package). Fields are usually private; methods are usually public.Constructors
A constructor is a special method called automatically when new creates an object, used to initialize fields. Its traits: same name as the class, no return type.
public class Person {
private String name;
private int age;
// no-arg constructor
public Person() {
this.name = "Unknown";
this.age = 0;
}
// parameterized constructor (overload)
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println(name + ", " + age + " years old");
}
public static void main(String[] args) {
Person p1 = new Person(); // uses the no-arg constructor
Person p2 = new Person("Bob", 25); // uses the parameterized one
p1.show();
p2.show();
}
}
Bob, 25 years old
Constructor Chaining: this()
Inside one constructor you can call another with this(...) to avoid duplicated code:
public class Car {
String brand;
String color;
int year;
public Car(String brand) {
this(brand, "White", 2024); // calls the three-arg constructor
}
public Car(String brand, String color, int year) {
this.brand = brand;
this.color = color;
this.year = year;
}
public void info() {
System.out.println(year + " " + color + " " + brand);
}
public static void main(String[] args) {
Car c = new Car("Toyota");
c.info();
}
}
The Standard JavaBean Pattern
In practice, entity classes usually follow the JavaBean convention: private fields + no-arg constructor + getters/setters:
public class Product {
private int id;
private String name;
private double price;
// no-arg constructor (required)
public Product() {}
// all-args constructor (recommended)
public Product(int id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
}
public class Dog {
private String name;
public Dog(String name) {
.name = ;
}
} Day 9 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Inheritance
Inheritance lets a subclass reuse its parent's code — the second pillar of OOP.
Basic Inheritance Syntax
Inherit a class with the extends keyword. A subclass automatically gets the parent's non-private members:
// parent class (base class)
class Animal {
String name;
public void eat() {
System.out.println(name + " is eating");
}
public void sleep() {
System.out.println(name + " is sleeping");
}
}
// subclass inheriting Animal
class Dog extends Animal {
public void bark() {
System.out.println(name + " barks!");
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.name = "Buddy";
d.eat(); // inherited from the parent
d.bark(); // the subclass's own method
}
}
Buddy barks!
Object; ③ a subclass cannot inherit the parent's private members or constructors.Method Overriding
A subclass can redefine a parent method; this is called overriding. The method name and parameter list must match the parent's:
class Shape {
public double area() {
return 0;
}
}
class Circle extends Shape {
double radius;
public Circle(double r) { this.radius = r; }
@Override // override annotation; helps the compiler check
public double area() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
double width, height;
public Rectangle(double w, double h) {
this.width = w;
this.height = h;
}
@Override
public double area() {
return width * height;
}
}
public class Main {
public static void main(String[] args) {
Circle c = new Circle(5);
Rectangle r = new Rectangle(4, 6);
System.out.printf("Circle area: %.2f%n", c.area());
System.out.printf("Rect area: %.2f%n", r.area());
}
}
Rect area: 24.00
@Override lets the compiler verify that you are overriding correctly. If you misspell a method name, the compiler reports an error. Strongly recommended on every override.The super Keyword
super accesses the parent's members, including calling the parent constructor:
class Vehicle {
String brand;
public Vehicle(String brand) {
this.brand = brand;
System.out.println("Vehicle constructed");
}
public void run() {
System.out.println(brand + " is driving");
}
}
class Car extends Vehicle {
int wheels;
public Car(String brand, int wheels) {
super(brand); // calls the parent constructor; must be the first line
this.wheels = wheels;
System.out.println("Car constructed");
}
@Override
public void run() {
super.run(); // calls the parent method
System.out.println(brand + " has " + wheels + " wheels");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car("Mercedes", 4);
car.run();
}
}
Car constructed
Mercedes is driving
Mercedes has 4 wheels
super(...) must be the first statement of the subclass constructor.The final Keyword
| Target | Effect |
|---|---|
final class | cannot be inherited (e.g. String) |
final method | cannot be overridden |
final variable | can be assigned only once (a constant) |
class Cat Animal {
@Override
public void () {
System.out.println("Meow!");
}
} Day 10 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Polymorphism & Interfaces
Polymorphism is the most powerful trait of OOP; interfaces are the key to flexible design.
Polymorphism: One Call, Different Behaviors
Three prerequisites for polymorphism: ① an inheritance/implementation relationship; ② method overriding; ③ a parent reference pointing to a subclass object.
class Animal {
public void speak() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
public void speak() { System.out.println("Woof!"); }
}
class Cat extends Animal {
public void speak() { System.out.println("Meow!"); }
}
public class Main {
// a parent reference as a parameter — the power of polymorphism
public static void makeSpeak(Animal a) {
a.speak(); // the runtime decides which subclass method runs
}
public static void main(String[] args) {
// parent reference pointing to subclass objects
Animal a1 = new Dog(); // upcasting
Animal a2 = new Cat();
makeSpeak(a1); // Woof!
makeSpeak(a2); // Meow!
}
}
Meow!
Downcasting & instanceof
Turning a parent reference back into a subclass type is called downcasting, and should be guarded by instanceof:
Java 16+ supports instanceof pattern matching all in one step:
Animal a = new Dog(); // upcasting
// a.bark(); // compile error! Animal has no bark method
if (a instanceof Dog) {
Dog d = (Dog) a; // downcasting
d.bark(); // now Dog-specific methods can be called
}
if (a instanceof Dog d) {
d.bark(); // d is automatically declared and cast
}
Abstract Classes
A class marked abstract cannot be instantiated and may contain abstract methods (declaration only, no implementation). Subclasses must implement all abstract methods:
abstract class Shape {
// abstract method: declaration only, no implementation
public abstract double area();
// a concrete method: allowed in an abstract class
public void display() {
System.out.printf("Area = %.2f%n", area());
}
}
class Triangle extends Shape {
double base, height;
public Triangle(double b, double h) {
this.base = b; this.height = h;
}
@Override
public double area() {
return 0.5 * base * height;
}
}
public class Main {
public static void main(String[] args) {
Shape s = new Triangle(3, 4);
s.display();
}
}
Interfaces
An interface is a collection of method contracts, defined with interface. A class implements it with implements, and one class can implement multiple interfaces:
| Aspect | Abstract class | Interface |
|---|---|---|
| Keyword | abstract class | interface |
| Inheritance | single inheritance (extends) | multiple implementation (implements) |
| Fields | any type | implicitly public static final |
| Methods | can have concrete methods | abstract by default (Java 8+ may have default) |
| Constructor | yes | no |
| Usage | expresses a "is-a" relationship | expresses a "can-do" capability |
// define an interface
interface Flyable {
void fly(); // public abstract by default
}
interface Swimmable {
void swim();
}
// implement multiple interfaces
class Duck implements Flyable, Swimmable {
public void fly() {
System.out.println("Duck flies");
}
public void swim() {
System.out.println("Duck swims");
}
}
public class Main {
public static void main(String[] args) {
Duck d = new Duck();
d.fly();
d.swim();
// interface references can also be polymorphic
Flyable f = new Duck();
f.fly();
}
}
Duck swims
Duck flies
Comparable {
int (Comparable other);
} Day 11 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Exception Handling
Programs will inevitably fail at runtime; exception handling lets a program cope with surprises gracefully.
The Exception Hierarchy
The inheritance structure of Java exceptions:
| Category | Base class | Feature | Examples |
|---|---|---|---|
| Checked exception | Exception | compiler enforces handling (try-catch or throws) | IOException, SQLException |
| Unchecked exception | RuntimeException | compiler does not enforce handling | NullPointerException, ArrayIndexOutOfBounds |
| Error | Error | serious problems, should not be caught | OutOfMemoryError, StackOverflowError |
try-catch-finally
public class TryCatch {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
try {
System.out.println(arr[5]); // out of bounds!
System.out.println("this line never runs");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Caught exception: " + e.getMessage());
} finally {
System.out.println("Finally always runs");
}
System.out.println("Program continues");
}
}
Finally always runs
Program continues
Multiple catch
Note: catch blocks must be ordered from subclass to superclass, otherwise there is a compile error (a superclass first would "swallow" all exceptions).
try {
String s = null;
System.out.println(s.length()); // a null pointer
} catch (NullPointerException e) {
System.out.println("NullPointerException");
} catch (Exception e) { // parent exceptions go last
System.out.println("Other exception");
}
throws & throw
| Aspect | throws | throw |
|---|---|---|
| Position | after the method signature | inside the method body |
| Role | declares exceptions that may be thrown | actually throws an exception object |
| Count | can declare several (comma-separated) | throws one per statement |
public class ThrowsDemo {
// throws declares the exceptions a method may throw
public static int divide(int a, int b) throws ArithmeticException {
if (b == 0) {
// throw actively throws an exception
throw new ArithmeticException("divisor cannot be zero");
}
return a / b;
}
public static void main(String[] args) {
try {
System.out.println(divide(10, 0));
} catch (ArithmeticException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
Custom Exceptions
// inherit Exception to create a checked exception
class AgeInvalidException extends Exception {
public AgeInvalidException(String msg) {
super(msg);
}
}
public class CustomException {
public static void checkAge(int age) throws AgeInvalidException {
if (age < 0 || age > 150) {
throw new AgeInvalidException("Invalid age: " + age);
}
System.out.println("Valid age: " + age);
}
public static void main(String[] args) {
try {
checkAge(200);
} catch (AgeInvalidException e) {
System.out.println("Caught: " + e.getMessage());
}
}
}
public class Safe {
public static void main(String[] args) {
int[] a = {1, 2};
{
System.out.println(a[10]);
} (ArrayIndexOutOfBoundsException e) {
System.out.println("Array out of bounds");
}
}
} Day 12 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
The Collections Framework
Arrays have a fixed size; the collections framework provides flexible data containers.
Collections Overview
| Interface | Common implementations | Features |
|---|---|---|
List | ArrayList, LinkedList | ordered, allows duplicates, indexed |
Set | HashSet, TreeSet | unordered, no duplicates |
Map | HashMap, TreeMap | key-value pairs, keys unique |
ArrayList
ArrayList is the most common list; underneath it is an array that grows dynamically:
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo {
public static void main(String[] args) {
// create an ArrayList (generics specify the element type)
List<String> fruits = new ArrayList<>();
// add elements
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
// get an element
System.out.println("Second: " + fruits.get(1));
// modify an element
fruits.set(0, "Fuji");
// iterate
for (String f : fruits) {
System.out.println(f);
}
// remove
fruits.remove("Orange");
System.out.println("Size after remove: " + fruits.size());
System.out.println("Contains Banana? " + fruits.contains("Banana"));
}
}
Fuji
Banana
Orange
Size after remove: 2
Contains Banana? true
HashMap
HashMap stores key-value pairs and looks up a value quickly by key:
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo {
public static void main(String[] args) {
// create a HashMap
Map<String, Integer> scores = new HashMap<>();
// add key-value pairs
scores.put("Zhang San", 90);
scores.put("Li Si", 85);
scores.put("Wang Wu", 95);
// get a value by key
System.out.println("Li Si score: " + scores.get("Li Si"));
// modify: putting the same key overwrites
scores.put("Zhang San", 88);
System.out.println("Zhang San new score: " + scores.get("Zhang San"));
// iterate
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// check whether a key exists
System.out.println("Has Wang Wu? " + scores.containsKey("Wang Wu"));
System.out.println("Size: " + scores.size());
}
}
Zhang San new score: 88
Li Si: 85
Zhang San: 88
Wang Wu: 95
Has Wang Wu? true
Size: 3
LinkedHashMap; for key-sorted order, use TreeMap.HashSet
HashSet stores unique elements, often used for deduplication:
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("Java");
set.add("Python");
set.add("Java"); // duplicate; will not be added
set.add("C++");
System.out.println("Size: " + set.size()); // 3
for (String s : set) {
System.out.println(s);
}
// remove
set.remove("C++");
System.out.println("Has C++ after remove? " + set.contains("C++"));
}
}
Java
C++
Python
Has C++ after remove? false
In Practice: Counting Word Occurrences
import java.util.HashMap;
import java.util.Map;
public class WordCount {
public static void main(String[] args) {
String[] words = {"java", "python", "java", "c++", "python", "java"};
Map<String, Integer> count = new HashMap<>();
for (String w : words) {
// getOrDefault: returns a default when the key is absent
count.put(w, count.getOrDefault(w, 0) + 1);
}
for (Map.Entry<String, Integer> e : count.entrySet()) {
System.out.println(e.getKey() + " appears " + e.getValue() + " times");
}
}
}
c++ appears 1 time
java appears 3 times
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<> list = new ArrayList<>();
list.("hello");
System.out.println(list.get(0));
}
} Day 13 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.
Generics & a Capstone Project
Generics make code safer and more reusable. Today you tie together all the knowledge of the previous 13 days with one project.
Why We Need Generics
Without generics, a collection can store any type (Object), and you must cast when retrieving, which is error-prone:
import java.util.ArrayList;
import java.util.List;
public class NoGeneric {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add(123); // can mix different types!
String s = (String) list.get(1); // runtime ClassCastException
System.out.println(s);
}
}
Generics Basics
Generics use angle brackets <T> to specify a type parameter, catching type errors at compile time:
import java.util.ArrayList;
import java.util.List;
public class Generic {
public static void main(String[] args) {
// the collection can only store String
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // compile error! blocked at compile time
String s = list.get(0); // no cast needed
System.out.println(s);
}
}
Custom Generic Classes
// a generic class: T is the type parameter
public class Box<T> {
private T item;
public void put(T item) {
this.item = item;
}
public T get() {
return item;
}
public static void main(String[] args) {
Box<String> strBox = new Box<>();
strBox.put("gift");
System.out.println(strBox.get());
Box<Integer> intBox = new Box<>();
intBox.put(42);
System.out.println(intBox.get());
}
}
42
Generic Methods
public class GenericMethod {
// a generic method: <T> declares the type parameter
public static <T> void printArray(T[] arr) {
for (T item : arr) {
System.out.print(item + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] nums = {1, 2, 3};
String[] strs = {"A", "B", "C"};
printArray(nums); // infers T = Integer
printArray(strs); // infers T = String
}
}
A B C
Wildcards
| Form | Meaning | Usage |
|---|---|---|
<?> | any type (unbounded wildcard) | read-only; cannot add |
<? extends Number> | Number and its subclasses (upper bound) | read; cannot write |
<? super Integer> | Integer and its supertypes (lower bound) | write; read as Object |
Capstone: a Student Management System
This project combines encapsulation, inheritance, collections, generics, and exception handling:
import java.util.ArrayList;
import java.util.List;
// Student class (encapsulation)
class Student {
private int id;
private String name;
private double score;
public Student(int id, String name, double score) {
this.id = id;
this.name = name;
this.score = score;
}
public int getId() { return id; }
public String getName() { return name; }
public double getScore() { return score; }
public String toString() {
return String.format("ID:%d Name:%s Score:%.1f", id, name, score);
}
}
// Student management system
class StudentManager {
private List<Student> students = new ArrayList<>();
// add a student
public void add(Student s) {
students.add(s);
System.out.println("Added: " + s.getName());
}
// delete by id
public void removeById(int id) {
students.removeIf(s -> s.getId() == id);
System.out.println("Deleted ID " + id);
}
// list all
public void listAll() {
if (students.isEmpty()) {
System.out.println("No students yet");
return;
}
for (Student s : students) {
System.out.println(s);
}
}
// compute the average score
public double average() {
if (students.isEmpty()) return 0;
double sum = 0;
for (Student s : students) sum += s.getScore();
return sum / students.size();
}
}
public class StudentSystem {
public static void main(String[] args) {
StudentManager mgr = new StudentManager();
mgr.add(new Student(1, "Alice", 90));
mgr.add(new Student(2, "Bob", 85));
mgr.add(new Student(3, "Carol", 78));
System.out.println("\n--- All students ---");
mgr.listAll();
System.out.println("\n--- Delete Bob ---");
mgr.removeById(2);
System.out.println("\n--- After deletion ---");
mgr.listAll();
System.out.printf("%nAverage: %.1f%n", mgr.average());
}
}
Added: Bob
Added: Carol
--- All students ---
ID:1 Name:Alice Score:90.0
ID:2 Name:Bob Score:85.0
ID:3 Name:Carol Score:78.0
--- Delete Bob ---
Deleted ID 2
--- After deletion ---
ID:1 Name:Alice Score:90.0
ID:3 Name:Carol Score:78.0
Average: 84.0
Your Next Steps
| Direction | Content |
|---|---|
| Advanced syntax | Enums, annotations, Lambda expressions, the Stream API |
| I/O and files | Byte/char streams, File, NIO |
| Multithreading | Thread, Runnable, the concurrency package |
| Network programming | Socket, HTTP, TCP/UDP |
| Databases | JDBC, connection pools, SQL |
| Frameworks | Spring Boot, MyBatis |
public class Box<> {
private T item;
public void put( item) { this.item = item; }
public T get() { return item; }
} Day 14 complete!
Congratulations on finishing this lesson. Click "Complete lesson" to record your progress, then continue with the next day.