Programming Bootcamp
Introduction to Programming

What is Programming?

Learn how computers think, what programming languages are, and how code goes from human-readable text to something a CPU can actually run.

🖥️ Hardware 💡 Concepts 🌐 Languages 🚀 Hello World
Lesson 1 — Section 1: Hardware

What Does a Computer Actually Do?

At its core, every computer has three key parts that work together.

🧠

CPU

The brain. Performs calculations and runs instructions — billions per second. Think of it as a chef following a recipe.

RAM

Short-term memory. Super fast, but temporary — everything in RAM disappears when you turn off the computer.

💾

Storage (SSD/HDD)

Long-term memory. Slower, but permanent — where your files and programs live when not running.

🎮 Think about it: When you open a game, it travels from Storage → RAM → CPU. The CPU runs the game logic; RAM keeps the current state; Storage holds saved files.
Lesson 1 — Section 1: Hardware

How the CPU Runs Instructions

The CPU repeats this cycle billions of times per second. It's fast, not smart.

📥 FETCH Get next instruction from RAM 🔍 DECODE Figure out what it means (add, jump...) EXECUTE Do the instruction, store the result ...repeat billions of times/sec
🤖 Key insight: Computers are fast, not smart. They mindlessly repeat this loop, doing exactly what they're told. If you write the wrong instruction — you get the wrong result.
Lesson 1 — Section 1: Hardware

Software = A List of Instructions

A program is just a very long, very precise recipe for the computer to follow.

🍕 Pizza Recipe 1 Make the dough 2 Add sauce 3 Add toppings 4 Bake at 220°C 5 Enjoy! 🎉

📋 Programs are recipes

Just like a recipe, skip a step or do them out of order and you get something unexpected — or nothing at all!

🐛 Bugs = wrong instructions

Computers do exactly what you tell them. Bugs happen because you wrote the wrong step — not because the computer made a mistake.

🎯 Precision matters

Every comma, bracket, and letter counts. Programming teaches you to think clearly and precisely.

Lesson 1 — Section 1: Hardware

The OS: The Middleman

Your program never talks directly to hardware. There's a layer in between — the Operating System.

Your Program (Java, Python, a game, a browser...) asks the OS for help Operating System 🪟 Windows 🍎 macOS 🐧 Linux talks to drivers & hardware Hardware 🧠 CPU ⚡ RAM 💾 Storage 🖥️ Display println() → OS → driver → screen

🧱 What the OS does

Manages CPU time, RAM, storage, and devices so multiple programs can share hardware without crashing into each other.

🤝 Why you don't have to worry

When Java prints to the screen, it asks the OS, which tells the display driver, which controls the monitor. Java handles this chain — you just write println().

Lesson 1 — Section 2: Programming Languages

Why Do Programming Languages Exist?

CPUs only understand 0s and 1s — programming languages bridge the gap between humans and machines.

Binary 01001000 01100101 01101100 01101100... Assembly MOV EAX, 1 ADD EAX, EBX C Language int x = a + b; printf("%d", x); ☕ Java int x = a + b; System.out.println(x); ← Human readable! ← harder to read · more control easier to read · more abstraction →

☕ Java was born in 1995

Created by James Gosling at Sun Microsystems to solve a big problem: code written for one OS wouldn't run on another.

✈️ Write Once, Run Anywhere

Java compiles to bytecode, which the JVM (Java Virtual Machine) can run on any computer — Windows, Mac, Linux.

🌍 Used Everywhere

Android apps, Minecraft, bank systems, government software — and it's the language used in AP Computer Science A!

Lesson 1 — Section 2: Programming Languages

Java: A Hybrid Approach

Different languages take different paths to the CPU. Java's approach is a middle ground — next slide we'll compare it to C and Python.

✏️ Main.java You write this javac ⚙️ Main.class Bytecode file JVM 🖥️ JVM Translates per OS Your CPU Runs the program! 🪟 Windows 🍎 Mac 🐧 Linux "Write Once, Run Anywhere"
💡 Java doesn't compile to raw machine code (like C) and isn't interpreted line-by-line (like Python). It hits a middle point: bytecode that any machine can run as long as it has a JVM installed — "Write Once, Run Anywhere."
Lesson 1 — Section 2: Programming Languages

How Different Languages Reach Your CPU

C, Python, and Java each take a completely different path from source code to running program.

C hello.c gcc hello.exe Native machine code ⚡ CPU Fast. But tied to one OS. Python hello.py interpreter Read line by line No compile step! ⚡ CPU Easy to run. Slower at runtime. Java Main.java javac Main.class Bytecode (portable) JVM JVM Translates per OS ⚡ CPU Portable. Middle-ground speed.

C — Native Compile

Fastest at runtime. Compiles to machine code your OS runs directly. Not portable — you recompile per platform.

Python — Interpreted

No compile step. The interpreter reads and runs each line. Great for quick scripts; slower at runtime than compiled languages.

Java — Bytecode + JVM

Compiles once to portable bytecode. The JVM handles the final step on each platform. Best of both worlds for most use cases.

Lesson 1 — Section 2: Types of Languages

Compiled vs. Interpreted

How does code actually run? Two very different approaches.

📦

Compiled — C, Java, Go

The entire program is translated before it runs. The compiler checks for errors first — nothing runs until it's clean.

source compile binary run Output

✅ Catches errors early · Often faster

📜

Interpreted — Python, JavaScript

Instructions are read and run one line at a time as the program executes. More flexible, but errors only show up when that line runs.

code.py line by line Interpreter Out

✅ Easier to test · No compile step

🤔 Think: Which catches mistakes earlier — compiled or interpreted? Compiled! The compiler checks everything before a single line runs.
Lesson 1 — Section 2: Types of Languages

Static vs. Dynamic Typing

Java makes you label what type of data a variable holds. Python figures it out on the fly.

☕ Java — Statically Typed

int score = 100; String name = "Jesus"; double price = 9.99; boolean isWinner = true; // ❌ This won't even compile: int x = "hello";

You must declare the type. The compiler enforces it before running.

🐍 Python — Dynamically Typed

score = 100 name = "Jesus" price = 9.99 is_winner = True # This is fine in Python: x = "hello" x = 42 # type changed!

No type labels needed. More flexible but errors show up later.

😤 Heads up: Java's strictness will feel annoying at first — but it catches entire categories of bugs before your program even runs. It makes you a more precise thinker.
Lesson 1 — Section 2: Errors

The Three Types of Errors

You will see all three this course. Here's what each one means.

🚫

Compile-Time Error

Caught before the program runs. Java won't even create a .class file. Syntax mistakes, type mismatches.

Easiest to fix — the compiler points right at it.

💥

Runtime Error

The program starts, but crashes mid-run. Examples: dividing by zero, accessing something that doesn't exist.

The error message shows where it crashed.

🤡

Logic Error

The program runs fine and produces output — but the output is wrong. Nothing tells you something broke.

Hardest to find — requires testing and thinking.

🧠 Remember: Getting errors is normal. Every programmer gets them every day. The skill is learning to read them and fix them.
Lesson 1 — Section 3: A First Look at Java

What Java Code Looks Like

We'll set up and run this in Lesson 4 — for now, let's read it and understand every piece.

public class Main { public static void main(String[] args) { System.out.println("Hello, World!"); } }
public class Main — defines a class. All Java code lives inside a class. The file must be named Main.java.
public static void main — the entry point. Java always starts here when you run a program.
System.out.println() — prints a line to the console. Verbose? Yes. You'll type it hundreds of times and it becomes second nature!
{ } — curly braces define the boundaries of a class or method. Everything inside belongs to it.
Lesson 1 — Section 3: A First Look at Java

From Code to Output — Conceptually

You don't need to run this yet. Just understand the journey a Java program takes.

✏️ You write Main.java javac ⚙️ Compiler produces Main.class (bytecode) JVM 🖥️ JVM runs it on your OS 🖨️ Hello, World! printed to screen
📌 Coming in Lesson 4: We'll install Java, set up our editor, and actually run this. For now the important thing is understanding what each step does — not the commands themselves.
Lesson 2 — Variables & Data Types

Variables & Data Types

How programs remember information — and why the type of data matters.

score 100 int name "Jesus" String gpa 3.8 double isWinner true boolean RAM — labeled boxes holding values
📦 Variables 🔢 Primitives 🧮 Arithmetic 🔤 Strings
Lesson 2 — Variables

What is a Variable?

A variable is a named slot in RAM that your program can read or change while it runs.

RAM score 0 lives 3 playerName "Alex" isGameOver false Each box has a name, a type, and a value

Three things every variable has

Name — what you call it (score, lives)
Type — what kind of data it holds
Value — the actual data inside

Naming rules

Use camelCase: playerScore, isGameOver. Can't start with a number. Can't use Java keywords like int or class.

Values can change

That's why they're called variables. A game score starts at 0 and goes up — same box, different value each time.

Lesson 2 — Variables

The Four Core Primitive Types

Java has 8 primitive types — these four cover nearly everything you'll need.

🔢

int

Whole numbers. Scores, counts, ages.

int score = 100; int lives = 3;

⚠️ 7 / 2 gives 3, not 3.5 — decimals get chopped!

🌡️

double

Decimal numbers. Prices, averages, measurements.

double gpa = 3.8; double price = 9.99;

8 bytes; tiny rounding quirks at extreme precision.

boolean

Exactly two values: true or false.

boolean isWinner = true; boolean gameOver = false;

Powers every if statement and loop you'll ever write.

🔤

char

A single character in single quotes.

char grade = 'A'; char init = 'J';

Single quotes = char. Double quotes = String.

Lesson 2 — Variables

The String Type

String is not a primitive — it's a class. But it's so essential it gets its own slide.

String name = "Jesus"; String greeting = "Hello, " + name; // greeting = "Hello, Jesus" String msg = "Score: " + 42; // msg = "Score: 42" // ⚠️ Watch out: String tricky = "1" + 2 + 3; // tricky = "123", not 6!

Concatenation with +

The + operator joins strings together. Mix a String with any other type and Java converts it automatically.

Capital S matters

String is capitalized because it's a class, not a primitive. Java is case-sensitive — string won't work.

Comparing Strings

Never use == to compare Strings. Use .equals() instead. We'll cover why in Lesson 9.

Lesson 2 — Variables

Declaring, Assigning & Printing

Three things you'll do with every variable — understand the difference between each.

// Declaration — reserves the box int score; // Initialization — puts a value in score = 0; // Both at once (most common) int lives = 3; // Reassignment — change the value lives = 2; // no "int" keyword again! // Print a variable System.out.println(lives); // 2 System.out.println("Lives: " + lives); // Lives: 2

Declaration vs. Initialization

Declaration creates the box. Initialization puts something in it. Java will error if you try to use a variable before it has a value.

Common mistake

Writing int lives = 2; a second time causes "variable already defined." Drop the type keyword on reassignment.

println vs. print

println adds a newline at the end. print doesn't — useful for printing things side by side.

Lesson 2 — Variables

Arithmetic Operators

Same math you know — with a few programming twists.

int a = 10, b = 3; System.out.println(a + b); // 13 System.out.println(a - b); // 7 System.out.println(a * b); // 30 System.out.println(a / b); // 3 ← not 3.33! System.out.println(a % b); // 1 ← remainder // Force decimal division: System.out.println(10.0 / b); // 3.333... // Shortcuts a += 5; // a = a + 5 → 15 a++; // a = a + 1 → 16

⚠️ Integer division

10 / 3 gives 3 — the decimal is thrown away. Make one number a double to get 3.333.

% — The Remainder

10 % 3 = 1 because 10 ÷ 3 = 3 remainder 1. Classic use: n % 2 == 0 checks if a number is even.

Shortcuts save typing

+= -= *= and ++ / -- are everywhere in Java code — get comfortable with them now.

Lesson 2 — Operators Deep Dive

Increment (++) & Decrement (--)

The most common shortcuts in Java — used everywhere. Master these and you'll recognize half the code you see.

int lives = 3; // Post-increment (++ after) System.out.println(lives++); // prints 3 System.out.println(lives); // prints 4 // Pre-increment (++ before) System.out.println(++lives); // prints 5 // Decrement (same idea) System.out.println(lives--); // prints 5 System.out.println(--lives); // prints 3 // Usually in a loop: for (int i = 0; i < 5; i++) { System.out.println(i); }

Post-increment (x++)

Uses the current value first, then increments. Like: "use it, then bump it."

int x = 5; int y = x++; // y = 5, x = 6

Pre-increment (++x)

Increments first, then uses the new value. Like: "bump it, then use it."

int x = 5; int y = ++x; // y = 6, x = 6

The practical difference

In loops, it doesn't matter — i++ and ++i do the same thing. But when assigning: y = x++ behaves very differently from y = ++x!

Lesson 2 — Operators Deep Dive

Compound Assignment Operators

These combine an operation and an assignment into one shorthand — they're everywhere in real code.

int score = 100; // Compound assignments score += 10; // same as: score = score + 10 score -= 5; // same as: score = score - 5 score *= 2; // same as: score = score * 2 score /= 4; // same as: score = score / 4 score %= 3; // same as: score = score % 3 // Practical example: health bar int health = 100; health -= 25; // take damage System.out.println(health); // 75

Why use them?

Shorter to write and read. Compare: playerScore += 50 vs. playerScore = playerScore + 50. The first is clearer and faster to type.

Works with all basic operators

+= -= *= /= %=
Each one is equivalent to: x = x ⊕ value

Game dev example

Increment a counter, decrement health, multiply by a power-up multiplier. These shortcuts make game logic clean and readable.

Lesson 2 — Operators Deep Dive

Modulus (%) — The Remainder Operator

The most underrated operator. It's not just for math class — it's essential for real-world programming problems.

// Basic examples System.out.println(10 % 3); // 1 System.out.println(20 % 7); // 6 System.out.println(15 % 5); // 0 // Check if even or odd int num = 17; if (num % 2 == 0) { System.out.println("Even"); } else { System.out.println("Odd"); } // Cycle through values (wrapping) int day = 7; int nextDay = (day % 7) + 1; // if day = 7 (Saturday), nextDay = 1

What % really does

10 % 3 asks: "If I divide 10 by 3, what's left over?" Answer: 10 = (3 × 3) + 1 → remainder is 1.

Real-world uses

Even/odd check: n % 2 == 0
Every nth iteration: if (i % 5 == 0) runs every 5 steps
Wrapping indices: (index % arrayLength)

Game dev example

A deck of cards cycles: cardIndex % 52 wraps back to card 0 after card 51. A timer loop: frame % 10 triggers every 10 frames.

Lesson 2 — Practice

Operator Cheat Sheet & Common Patterns

A quick reference for everything you've learned about operators — use this as a bookmark!

Increment & Decrement

int x = 5;
x++ → use, then +1
++x → +1, then use
x-- → use, then -1
--x → -1, then use

Compound Assignment

All equivalent to: x = x ⊕ value

+= add
-= subtract
*= multiply
/= divide
%= mod

Modulus Patterns

n % 2 == 0 even?
n % 2 == 1 odd?
i % 5 == 0 every 5th?
index % size wrap around
Pro tip: These operators will feel awkward at first. But by Lesson 4 (loops), you'll use i++ dozens of times per program. Comfort comes with repetition.
Lesson 2 — Arrays

What is an Array?

So far we've stored one value per variable. But what if we need 100 test scores or a deck of 52 cards? Enter arrays.

scores array (int[]) [0] 85 [1] 92 [2] 78 [3] 88 ... scores (the array name) One variable holds many values ❌ Without arrays: int score1, score2, score3... (tedious!) ✅ With arrays: int[] scores (clean!)

Arrays hold multiple values

Same type, accessed by index (position). scores[0] is the first value, scores[1] is the second.

Index starts at 0

This is crucial — the first element is always at index 0, not 1. A common source of "off-by-one" bugs!

Fixed size

When you create an array, you decide how many slots it has. You can't add more later (not in this course, anyway).

Single type

An int[] holds only integers. A String[] holds only strings.

Lesson 2 — Arrays

Creating Arrays

The syntax looks odd at first, but you'll type it hundreds of times and it becomes automatic.

// Declare and create (empty) int[] scores = new int[5]; // 5 slots, all start at 0 // Declare and initialize (with values) int[] grades = {85, 92, 78, 88}; // 4 slots with specific values // For Strings String[] names = {"Alice", "Bob", "Carol"}; // For booleans boolean[] flags = new boolean[10]; // 10 slots, all start as false // Get the length int size = grades.length; // 4

The [] syntax

Read "int square-bracket" as "array of int." The [] goes after the type, not the variable name.

Using new

The new keyword creates the array in RAM. The number in brackets is the size.

Default values

int[] → 0, double[] → 0.0, boolean[] → false, String[] → null

The .length property

Every array has a .length (not a method!). Perfect for loops: i < array.length

Lesson 2 — Arrays

Using Arrays — Getting & Setting Values

You access elements by index — the same syntax you saw in the diagrams.

int[] scores = {85, 92, 78}; // GET (read) a value int first = scores[0]; // 85 int second = scores[1]; // 92 int last = scores[2]; // 78 // SET (write) a value scores[0] = 90; // change first to 90 scores[1]++; // increment second to 93 // Print individual elements System.out.println(scores[0]); // 90 System.out.println(scores[1]); // 93 // Use a variable as index int idx = 2; System.out.println(scores[idx]); // 78

Index must exist

If the array has 3 elements (indices 0, 1, 2), asking for scores[3] crashes with ArrayIndexOutOfBoundsException.

Use expressions as indices

You can use variables, math, or even method calls inside brackets: scores[i+1] or scores[random()].

Arrays are mutable

Once created, you can change individual elements as many times as you want, but you can't resize the array.

Arrays with indices

This is why loops exist! In Lesson 2, you'll loop through arrays with for (int i = 0; i < scores.length; i++).

Lesson 2 — Arrays

Arrays: Quick Reference

A cheat sheet for everything arrays. Bookmark this one!

Declaration Patterns

int[] whole numbers
double[] decimals
String[] text
boolean[] true/false

Key Facts

  • 📍 0-indexed: First element is [0], last is [length - 1]
  • 📍 Fixed size: Declared once, can't grow or shrink
  • 📍 .length property: Always tells you the size
  • 📍 Mutable: Change elements, not the array itself
🔗 Why arrays matter: You can't meaningfully loop through data without arrays. Lesson 2 will teach you loops — and arrays + loops are where programming gets powerful. Master this foundation now!
Lesson 3 — Input & Decisions

Getting Input & Making Decisions

Programs become useful when they can react to the user — read what they type and decide what to do next.

⌨️

Scanner

Read what the user types from the keyboard.

🔀

if / else

Run different code depending on a condition.

⚖️

Comparison operators

Compare values: equal, greater, less than.

🔗

Logical operators

Combine conditions: AND, OR, NOT.

Lesson 3 — Input

Reading User Input with Scanner

The Scanner class lets your program pause and wait for the user to type something.

import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.print("Enter your name: "); String name = sc.nextLine(); System.out.print("Enter your age: "); int age = sc.nextInt(); System.out.println("Hi " + name + ", you are " + age + "!"); } }

import statement

Scanner lives in the java.util package. You must import it at the top before you can use it.

Reading different types

nextLine() → String
nextInt() → int
nextDouble() → double

System.in

Just like System.out sends to the screen, System.in reads from the keyboard.

Lesson 3 — Decisions

if / else if / else

The most fundamental decision-making tool in any programming language.

int score = 85; if (score >= 90) { System.out.println("A"); } else if (score >= 80) { System.out.println("B"); } else if (score >= 70) { System.out.println("C"); } else { System.out.println("Study more!"); } // Prints: B
score = 85 ≥ 90? no yes → "A" ≥ 80? print "B" yes no → ... "A"
Java checks conditions top to bottom and stops at the first one that's true.
Lesson 3 — Decisions

Comparison & Logical Operators

These are the building blocks of every condition you'll ever write.

Comparison operators — return true or false

int x = 5; x == 5 // true — equal to x != 3 // true — not equal to x > 3 // true — greater than x < 3 // false — less than x >= 5 // true — greater or equal x <= 4 // false — less or equal

Logical operators — combine conditions

int age = 17; boolean hasTicket = true; // AND — both must be true age >= 18 && hasTicket // false // OR — at least one true age >= 18 || hasTicket // true // NOT — flips true/false !hasTicket // false
🧠 Memory trick: && = AND (both), || = OR (either), ! = NOT (flip it). You read ! as "not" — so !isGameOver means "game is not over."
Lesson 3 — Mini-Project

Mini-Project: Number Guessing Game

Putting it all together — Scanner + if/else in one small program.

import java.util.Scanner; public class GuessingGame { public static void main(String[] args) { int secret = 42; Scanner sc = new Scanner(System.in); System.out.print("Guess a number: "); int guess = sc.nextInt(); if (guess == secret) { System.out.println("🎉 Correct!"); } else if (guess < secret) { System.out.println("Too low!"); } else { System.out.println("Too high!"); } } }

What this uses

Variables (int secret, int guess), Scanner to read input, and if/else to react.

What's missing?

The player only gets one chance. In Lesson 4 we'll add a loop so they can keep guessing until they get it right.

Challenge: extend it

Can you modify it to also tell the player how far off their guess was? Hint: subtraction and Math.abs().

Mini-Project Setup — Step 1 of 3

Open Terminal & Set Up Java

We'll use Terminal to create the project folder, then use mise to pin Java 21 to it.

① Open Terminal

Press ⌘ Space, type Terminal, press Enter. A black or white window with a command prompt opens.

② Jump to labs & create the lab1 folder

labs mkdir lab1 cd lab1

labs is your alias for ~/Development/labs. mkdir lab1 creates this lab's folder, and cd lab1 moves you into it. Your prompt should now end with lab1.

③ Pin Java 21 to this project with mise

mise use java@21

This creates a .mise.toml file in your project folder that tells mise which Java version to use here. If Java 21 isn't installed yet, mise will install it automatically.

④ Confirm Java is ready

java --version

You should see:

openjdk 21.0.x 2024-...

If you see a different version or an error, make sure you ran cd into the project folder first.

Mini-Project Setup — Step 2 of 3

Open VS Code & Create the File

Still in Terminal — one command opens the whole project in VS Code.

① Open VS Code from Terminal

code .

The . means "this folder." VS Code opens with lab1 loaded in the Explorer panel on the left.

② Create GuessingGame.java

In VS Code's Explorer panel (left sidebar), hover over the folder name and click the New File icon (📄). Type GuessingGame.java and press Enter.

③ Paste in the code

Click inside the new file and paste the GuessingGame code from the previous slide. Save with ⌘ S.

VS Code will highlight the code with colors — that's syntax highlighting telling you the code was parsed correctly.

lab1 — VS Code
📁 lab1
📄 GuessingGame.java
📄 .mise.toml
public class GuessingGame {
public static void main(...) {
// your code here
}
}
Notice the .mise.toml file mise created — it locks Java 21 to this project so anyone who opens it gets the right version automatically.
Mini-Project Setup — Step 3 of 3

Run It in VS Code

The Java extension adds a ▶ Run button above your main method — no terminal commands needed.

① Click ▶ Run above main

Look just above the line public static void main — VS Code shows a small ▶ Run link. Click it. VS Code compiles and runs your program automatically.

② The Terminal panel opens

VS Code opens the integrated terminal at the bottom of the screen and runs your program there. You'll see the prompt Guess a number: — click in the terminal and type your guess.

③ Prefer the command line? That works too

Open the terminal with ⌃ ` and run it yourself:

javac GuessingGame.java java GuessingGame
Terminal (inside VS Code)
# VS Code ran this for you automatically:
~/Development/labs/lab1 $ java GuessingGame
Guess a number:
20
Too low!
Guess a number:
60
Too high!
Guess a number:
42
🎉 Correct!
🎉 Your first real Java program — running on your Mac.
Recap — Lessons 1, 2 & 3

What You've Covered

🖥️

Lesson 1 — How Computers Work

CPU, RAM, Storage, the OS layer. Fetch-Decode-Execute cycle. Programming languages bridge humans and binary. C, Python, Java each take a different path to the CPU.

📦

Lesson 2 — Variables & Data Types

Variables are named RAM slots. Primitives: int, double, boolean, char. Strings concatenate with +. Integer division truncates. % gives the remainder.

🔀

Lesson 3 — Input & Decisions

Scanner reads from the keyboard. if/else if/else branches on a condition. Comparison operators produce booleans. && / || / ! combine them.

🚀

Coming Up — Lesson 4

Loops — while, do-while, for, and for-each — and we'll finally set up Java and run real code.

Lesson 4 — Control Flow & Repetition

Loops

Computers are great at repeating things — millions of times without getting tired. Loops are how you tell them to do it.

condition still true? run the block false → stop
🔁 while 🔄 do-while 🔢 for ⛔ break & continue
Lesson 4 — while & do-while

Why Repetition Matters

Without loops, you'd have to write the same line of code thousands of times. Loops let you say it once and let the computer repeat it.

😩 Without a loop — printing 1 to 5:

System.out.println(1); System.out.println(2); System.out.println(3); System.out.println(4); System.out.println(5); // What if you needed 1 to 1,000,000?

✅ With a loop — same result, one idea:

int i = 1; while (i <= 5) { System.out.println(i); i++; } // Change 5 to 1_000_000 — done.
🎮

Game loops

Every game runs a loop — check input, update state, draw screen — thousands of times per second.

📋

Processing data

Go through every student's grade, every order in a store, every message in an inbox.

Waiting for input

Keep asking the user for a valid answer until they give one — a natural loop.

Lesson 4 — while loop

The while Loop

Repeat a block of code as long as a condition stays true. Check the condition before each iteration.

// Anatomy of a while loop while ( condition ) { // body — runs if condition is true // must eventually make condition false! } // Example: count down from 5 int count = 5; while (count > 0) { System.out.println(count); count--; // ← moves toward false } System.out.println("Blast off! 🚀");
Output: 5 → 4 → 3 → 2 → 1 → Blast off! 🚀
count = 5 count > 0 ? true println(count) count-- loop back false Blast off! 🚀
Lesson 4 — do-while loop

The do-while Loop

Like a while loop — but the body runs at least once before the condition is ever checked.

// Anatomy do { // body runs first, THEN condition checked } while ( condition ); // ← note semicolon! // Great for: "ask until valid" Scanner sc = new Scanner(System.in); int guess; do { System.out.print("Enter 1–10: "); guess = sc.nextInt(); } while (guess < 1 || guess > 10); System.out.println("Valid: " + guess);
The user must type something before we can check if it's valid — do-while is the natural fit here.

while — checks first

If the condition starts false, the body never runs. Use when you might not need to run at all.

do-while — runs first

Body always runs at least once, then checks. Use when you need to do something before you can decide to continue.

Real-world examples

Menu systems, input validation, game rounds — anywhere the user must act before the program can decide what to do next.

Lesson 4 — Common Bugs

Two Classic Loop Bugs

These will happen to you. Knowing what they look like makes them easy to fix.

🔴 Infinite Loop — never stops

int i = 1; while (i <= 5) { System.out.println(i); // ❌ forgot i++ ! } // prints 1 forever...

Symptoms

Program runs forever, terminal freezes, fan spins up. Fix: press ⌃ C to stop it, then make sure the loop variable actually changes inside the body.

🟡 Off-by-One — one too many or few

// Want to print 1 through 5 int i = 1; while (i < 5) { // ❌ should be <= System.out.println(i); i++; } // only prints 1 2 3 4 — misses 5! // Fix: while (i <= 5) { ... } // ✅

Symptoms

Loop runs one iteration too many or too few. Often caused by using < when you needed <= (or vice versa). Extremely common — even experienced developers make this mistake.

Lesson 4 — Exercise

Exercise: Guessing Game with Retries

Update your guessing game so the player can keep guessing until they get it right.

import java.util.Scanner; public class GuessingGame { public static void main(String[] args) { int secret = 42; Scanner sc = new Scanner(System.in); int guess = 0; int attempts = 0; while (guess != secret) { System.out.print("Guess: "); guess = sc.nextInt(); attempts++; if (guess < secret) System.out.println("Too low!"); else if (guess > secret) System.out.println("Too high!"); } System.out.println( "🎉 Got it in " + attempts + " tries!"); } }

What changed from Lesson 3?

Added a while loop around the guess logic. The loop keeps running until guess == secret.

Tracking attempts

attempts++ increments a counter each iteration — the loop naturally counts how many tries it took.

Challenge

Add a max of 7 guesses. If the player doesn't guess in 7 tries, reveal the answer. Hint: add && attempts < 7 to the condition.

Lesson 4 — for loop

The for Loop

When you know exactly how many times to repeat, for packs the setup, condition, and update into one tidy line.

for ( int i = 0 ① init ; i < 5 ② condition ; i++ ③ update ) { println(i); ④ body }
// Print 0 through 4 for (int i = 0; i < 5; i++) { System.out.println(i); } // Output: 0 1 2 3 4 // Count down for (int i = 10; i >= 1; i--) { System.out.print(i + " "); } // Output: 10 9 8 7 6 5 4 3 2 1 // Count by 2s for (int i = 0; i <= 10; i += 2) { System.out.print(i + " "); } // Output: 0 2 4 6 8 10

① Init — runs once

Creates the loop variable before the first iteration. Usually int i = 0. The variable only exists inside the loop.

② Condition — checked before each iteration

If false, the loop ends immediately. Same as a while loop's condition.

③ Update — runs after each iteration

Usually i++ to move forward, but can be any expression — i--, i += 2, etc.

Lesson 4 — for vs while

When to Use for vs while

Both can do the same job — but one is usually a better fit depending on what you know upfront.

🔢

Use for when you know the count

You know exactly how many times to loop — iterating over a list, counting from A to B, repeating N times.

// Print a 5-star rating for (int i = 0; i < 5; i++) { System.out.print("⭐"); } // Sum 1 through 100 int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } System.out.println(sum); // 5050

Use while when count is unknown

You don't know how many times — keep going until some event happens (user input, a condition changes, a file ends).

// Keep asking until "quit" String input = ""; while (!input.equals("quit")) { System.out.print("Command: "); input = sc.nextLine(); } // Read a file line by line while (scanner.hasNextLine()) { process(scanner.nextLine()); }
Lesson 4 — Nested Loops

Nested Loops

A loop inside a loop. The inner loop runs completely for every single iteration of the outer loop.

// Times table (1–5) for (int row = 1; row <= 5; row++) { for (int col = 1; col <= 5; col++) { System.out.printf("%4d", row * col); } System.out.println(); }
1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
col=1 col=2 col=3 col=4 col=5 row=1 row=2 row=3 row=4 row=5 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
For a 5×5 grid: outer runs 5 times, inner runs 5 times per outer iteration = 25 total executions of the body.
Lesson 4 — break & continue

break and continue

Two keywords that give you fine-grained control over what a loop does mid-iteration.

⛔ break — exit the loop immediately

// Stop when we find 7 for (int i = 1; i <= 10; i++) { if (i == 7) { System.out.println("Found 7!"); break; // jumps out of the loop } System.out.println(i); } // Output: 1 2 3 4 5 6 Found 7!

Use break when…

You found what you were looking for and there's no point continuing — searching a list, hitting a sentinel value, a game-over condition.

⏭️ continue — skip this iteration only

// Print only odd numbers for (int i = 1; i <= 10; i++) { if (i % 2 == 0) { continue; // skip evens } System.out.println(i); } // Output: 1 3 5 7 9

Use continue when…

You want to skip certain items but keep looping — filtering out invalid data, skipping blank lines, ignoring certain values in a calculation.

Lesson 4 — for-each

The for-each Loop

A cleaner syntax for looping over every item in a collection — no index variable needed.

// Regular for — needs index management int[] scores = {90, 85, 72, 95, 88}; for (int i = 0; i < scores.length; i++) { System.out.println(scores[i]); } // for-each — same result, cleaner for (int score : scores) { System.out.println(score); } // Read as: "for each score in scores" // Works on Strings too String[] names = {"Alice", "Bob", "Carol"}; for (String name : names) { System.out.println("Hello, " + name + "!"); }

Syntax: type variable : collection

The variable takes on each value in the collection one by one. You declare its type on the left side of the colon.

When to prefer for-each

When you just need each element's value and don't care about the index — reading, printing, summing, searching.

When to stick with for

When you need the index — modifying elements in place, going backwards, skipping by 2s, comparing adjacent elements.

Lesson 4 — Coding Exercise

FizzBuzz

A classic programming challenge. Simple rules, great practice for loops and conditions working together.

📋 The Rules

Write a program that prints every number from 1 to 100, but:

  • If the number is divisible by 3, print "Fizz" instead
  • If divisible by 5, print "Buzz" instead
  • If divisible by both, print "FizzBuzz"
  • Otherwise, just print the number

🧪 Expected output (first 20)

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz

💡 Hint

Use the % (modulo) operator to check divisibility. n % 3 == 0 means n is divisible by 3. Check FizzBuzz first — order matters!

Your starter code:

public class FizzBuzz { public static void main(String[] args) { for (int i = 1; i <= 100; i++) { // your logic here } } }

🏆 Stretch challenges

1. Add a counter — print how many FizzBuzzes there were at the end
2. Make it go 1 to N where N is entered by the user (Scanner)
3. Add a 4th rule: divisible by 7 prints "Bazz"

📁 Create this in ~/Development/labs/lab2/ — use labsmkdir lab2 && cd lab2code .
Recap — Lesson 4

What You've Covered

🔁

while

Checks condition first. Runs zero or more times. Use when the count is unknown — keep going until something happens.

🔄

do-while

Runs the body first, then checks. Guarantees at least one execution. Ideal for input validation and menus.

🔢

for

Init, condition, update — all in one line. Use when you know the count. Can count up, down, or by any step size.

🗂️

for-each

Cleanest syntax for visiting every element in a collection. No index to manage — just the value.

🐛

Common bugs

Infinite loop — forgot to update the loop variable. Off-by-one — used < when you needed <=.

⛔⏭️

break & continue

break exits the loop entirely. continue skips the rest of this iteration and moves to the next.

🔲

Nested loops

Inner loop runs completely for each outer iteration. Total executions = outer count × inner count. Great for grids and tables.

🚀

Coming up — Lesson 5

Methods — breaking code into reusable named blocks, parameters, return types, and scope.

Lesson 5 — Methods

Methods

Stop repeating yourself. Package logic into a reusable, named block you can call again and again — with different data each time.

5, 3 parameters add(a, b) runs the same logic every time it's called 8 return value
📦 Define 📞 Call ➡️ Parameters ↩️ Return
Lesson 5 — Why Methods

Why We Break Code Into Methods

Without methods, every calculation gets copy-pasted everywhere you need it. Methods let you write the logic once.

😩 Without a method — repeated logic

int a = 5, b = 3; int sumOne = a + b; System.out.println(sumOne); int c = 10, d = 20; int sumTwo = c + d; // copy-pasted logic System.out.println(sumTwo); int e = 7, f = 1; int sumThree = e + f; // again! System.out.println(sumThree);

✅ With a method — write once, reuse

static int add(int x, int y) { return x + y; } // now just call it, with any numbers System.out.println(add(5, 3)); System.out.println(add(10, 20)); System.out.println(add(7, 1)); // one place to fix bugs, one place to improve
♻️

Reusability

Write the logic once, call it as many times as you need — with different data each time.

📖

Readability

A well-named method like calculateTax() explains itself — no comment needed.

🔧

Easier debugging

One bug in one place — fix it once and every caller benefits, instead of hunting down every copy.

Lesson 5 — Defining a Method

The Anatomy of a Method

Every method you write has the same five parts, no matter what it does.

static int ① return type add ② name ( int x, int y ③ parameters ) { return x + y; ④ body }
public class Main { // ⑤ defined once, outside main() static int add(int x, int y) { return x + y; } public static void main(String[] args) { int result = add(5, 3); System.out.println(result); // 8 } }

① Return type

What kind of value comes back out — int, double, String... or void if nothing comes back.

② Name

camelCase, just like variables. Should be a verb — add, calculateAverage, printReceipt.

③ Parameters

The inputs the method needs, in parentheses. Can be zero, one, or many — separated by commas.

④ Body

The code that runs when the method is called, wrapped in { }.

Lesson 5 — Calling Methods

Calling a Method

Defining a method doesn't run it — you have to call it by name for its code to execute.

main() add(5, 3); jumps into add(x, y) x = 5, y = 3 return x + y; → 8 returns 8, resumes println(result) prints: 8
static int add(int x, int y) { return x + y; } public static void main(String[] args) { System.out.println("Before call"); int result = add(5, 3); // ← call! System.out.println("After call: " + result); } // Before call // After call: 8

Call by name + parentheses

add(5, 3) pauses main, runs add's body completely, then main resumes exactly where it left off.

Lesson 5 — Parameters

Parameters — Passing Data In

Parameters let the same method work with completely different data every time it's called.

// Two parameters, both int static int multiply(int a, int b) { return a * b; } // Different types are fine too static void greet(String name, int age) { System.out.println("Hi " + name + ", age " + age); } greet("Jesus", 17); // "Jesus" → name, 17 → age (order matters!) greet(17, "Jesus"); // ❌ compile error — wrong order

Parameters vs. arguments

Parameters are the placeholders in the definition (int a, int b). Arguments are the real values you pass in when calling (5, 3).

Order matters

Arguments are matched to parameters by position, not by name. The 1st argument fills the 1st parameter, and so on.

Parameters are local

Inside the method, a and b behave like regular variables — you can even reassign them without affecting the caller's originals.

Lesson 5 — Return Types

Return Types — Sending Data Back

A method can hand a value back to whoever called it — or hand nothing back at all, using void.

Returns a value

static double average(int a, int b) { return (a + b) / 2.0; } double result = average(10, 5); System.out.println(result); // 7.5

Returns nothing — void

static void printReceipt(String item, double price) { System.out.println(item + ": $" + price); // no return statement needed } printReceipt("Coffee", 4.50);

return exits immediately

The moment Java hits return, the method ends — any code after it in that path never runs.

Type must match

If the method says int, you must return an int. Returning a String from an int method is a compile-time error.

void = side effects only

A void method does something (prints, changes something) but doesn't hand back a value you can store.

Every path must return

If a method promises int, every possible branch (if/else) must eventually hit a return — or it won't compile.

Lesson 5 — Scope

Scope: Local vs. Class-Level Variables

Where you declare a variable decides where it's visible — and where it disappears.

public class Main { // class-level (a "field") — visible to // every method in this class static int totalCalls = 0; static int square(int n) { int result = n * n; // ← local to square() totalCalls++; // ← field, shared return result; } public static void main(String[] args) { System.out.println(square(4)); System.out.println(result); // ❌ error: result // doesn't exist here! } }

Local variables

Declared inside a method. Only exist while that method is running — created fresh on each call, destroyed when it returns.

Class-level variables (fields)

Declared outside any method, directly in the class. Shared and visible across every method in that class.

Common mistake

Trying to use a local variable from one method inside a different method — Java says "cannot find symbol." It simply doesn't exist there.

Rule of thumb

Keep variables as local as possible. Only make something a field if multiple methods genuinely need to share it.

Lesson 5 — Recursion

Recursion — A Method That Calls Itself

A recursive method solves a problem by breaking it into a smaller version of the same problem, until it's simple enough to answer directly.

static int factorial(int n) { if (n <= 1) { return 1; // ① base case } return n * factorial(n - 1); // ② recursive case } System.out.println(factorial(4)); // factorial(4) = 4 * factorial(3) // = 4 * 3 * factorial(2) // = 4 * 3 * 2 * factorial(1) // = 4 * 3 * 2 * 1 // = 24
factorial(4) factorial(3) factorial(2) factorial(1) 🛑 base case → returns 1 unwinds, multiplying back up result = 24
🛑

① Base case

The simplest version of the problem, answered directly with no further recursive call. Without one, recursion never stops.

🔁

② Recursive case

Calls itself with a smaller or simpler input, moving one step closer to the base case each time.

📚

The call stack

Each call waits, paused, for the one below it to finish — then they all resolve in reverse order, like stacked plates.

Lesson 5 — Recursion

Recursion: Pitfalls & When to Use It

Recursion is elegant for the right problems — but a missing base case is the loop-forgot-to-update-i of recursion.

🔴 Missing base case — crashes

static int factorial(int n) { return n * factorial(n - 1); // ❌ no base case — never stops! } // Exception in thread "main" // java.lang.StackOverflowError

StackOverflowError

Recursion's version of an infinite loop. Each call takes up space on the call stack — with no base case, Java eventually runs out and crashes.

✅ Another example — countdown

static void countdown(int n) { if (n == 0) { System.out.println("Liftoff! 🚀"); return; // base case } System.out.println(n); countdown(n - 1); // moves toward 0 } // countdown(3) → 3, 2, 1, Liftoff! 🚀

Recursion vs. loops

Anything recursion can do, a loop can also do — and loops usually use less memory. Reach for recursion when a problem is naturally self-similar (folders inside folders, branches of a tree).

🧠 Checklist for every recursive method: Does it have a base case? Does every recursive call move closer to that base case? If either answer is no, you have an infinite recursion bug waiting to happen.
Lesson 5 — Mini-Project

Mini-Project: A Method-Powered Calculator

Rebuild a basic calculator, but this time every operation is its own method.

📋 The Requirements

Write four methods, each taking two double parameters and returning a double result:

  • add(a, b)
  • subtract(a, b)
  • multiply(a, b)
  • divide(a, b)

🧪 Expected output

10.0 + 4.0 = 14.0 10.0 - 4.0 = 6.0 10.0 * 4.0 = 40.0 10.0 / 4.0 = 2.5

💡 Hint

Call all four methods from main with the same two numbers, and print each labeled result with println.

Your starter code:

public class Calculator { static double add(double a, double b) { // your logic here } // define subtract, multiply, divide... public static void main(String[] args) { double x = 10, y = 4; // call each method and print the result } }

🏆 Stretch challenges

1. Add a modulus(a, b) method for the remainder
2. Guard divide against dividing by zero — print an error instead of crashing
3. Add a Scanner so the user types their own two numbers

📁 Create this in ~/Development/labs/lab3/ — use labsmkdir lab3 && cd lab3code .
Recap — Lesson 5

What You've Covered

♻️

Why methods

Write logic once, reuse it anywhere. Improves readability and makes bugs easier to fix — one place, not many.

📦

Defining

Return type, name, parameters, body — static <type> name(params) { }.

➡️

Parameters

Inputs passed by position. Local to the method — separate from the caller's original variables.

↩️

Return types

return sends a value back and exits immediately. void means no value comes back.

🔭

Scope

Local variables live and die inside one method call. Class-level fields are shared across every method.

🌀

Recursion

A method that calls itself needs a base case (stops it) and a recursive case (moves toward the base case).

🐛

StackOverflowError

Recursion's version of an infinite loop — a missing or unreachable base case means the calls never stop piling up.

🧮

Mini-project

Built a calculator with a dedicated method per operation — the same pattern real programs use everywhere.

🚀

Coming up — Lesson 6

ArrayLists — a resizable, more powerful alternative to arrays, plus the Collections framework.

Lesson 6 — ArrayLists

ArrayLists & the Collections Framework

Arrays are powerful, but they can't grow. Meet ArrayList — a resizable, more flexible way to store a list of data.

int[] — fixed size 90 85 72 🔒 locked at 3 slots ArrayList — resizable 90 85 + ✅ add() grows it
📦 Create ➕ Add / Remove 🔁 Iterate 🧬 Generics
Lesson 6 — Why ArrayList

Why ArrayList Beats a Raw Array

Arrays have one big limitation: once you create them, their size is locked forever. Most real programs don't know their data size in advance.

😩 Arrays — fixed size is a problem

int[] grades = new int[3]; grades[0] = 90; grades[1] = 85; grades[2] = 72; // A new student enrolls... grades[3] = 95; // ❌ ArrayIndexOutOfBoundsException // there's no room — arrays can't grow!

✅ ArrayList — grows as needed

import java.util.ArrayList; ArrayList<Integer> grades = new ArrayList<>(); grades.add(90); grades.add(85); grades.add(72); // A new student enrolls... grades.add(95); // ✅ just works — size 4 now
📏

Resizable

Grows and shrinks automatically as you add or remove elements — no more planning the exact size ahead of time.

🧰

Built-in methods

Comes with helpers like add(), remove(), contains() — arrays give you none of this.

📚

Part of the Collections Framework

ArrayList is one of many collection types in Java — a family of tools built for storing and organizing groups of data.

Lesson 6 — Creating an ArrayList

Creating an ArrayList

ArrayList lives in java.util and uses generics — angle brackets that say what type it holds.

import java.util.ArrayList; // ArrayList of Strings ArrayList<String> names = new ArrayList<>(); // ArrayList of Integers ArrayList<Integer> scores = new ArrayList<>(); // ArrayList of Doubles ArrayList<Double> prices = new ArrayList<>(); // ❌ This will NOT compile: ArrayList<int> bad = new ArrayList<>(); // generics need an object type, not a primitive

<Type> is a generic

The angle brackets tell Java (and you) exactly what kind of object this list holds — every element must match.

Wrapper classes

Generics can't use primitives. Use the wrapper class instead: intInteger, doubleDouble, booleanBoolean.

Java converts automatically

Writing scores.add(90) just works — Java quietly "autoboxes" the int into an Integer for you.

Lesson 6 — Adding & Removing

Adding & Removing Elements

This is where ArrayList really shines — no shifting arrays around by hand.

ArrayList<String> names = new ArrayList<>(); names.add("Alice"); // [Alice] names.add("Bob"); // [Alice, Bob] names.add("Carol"); // [Alice, Bob, Carol] // insert at a specific index names.add(1, "Zoe"); // [Alice, Zoe, Bob, Carol] // remove by index names.remove(0); // [Zoe, Bob, Carol] // remove by value names.remove("Bob"); // [Zoe, Carol] // replace an element names.set(0, "Zed"); // [Zed, Carol]

add() shifts everything for you

Inserting in the middle automatically pushes later elements over — no manual index math like you'd need with an array.

remove() has two forms — a classic gotcha

remove(1) on an ArrayList<Integer> removes by index. To remove the value 1 itself, use remove(Integer.valueOf(1)).

set() vs. add()

set(i, val) replaces what's already at index i. add(i, val) inserts a brand new element there.

Lesson 6 — Accessing & Iterating

Accessing & Iterating Over an ArrayList

Reading elements looks a lot like arrays — but with .get() and .size() instead of [ ] and .length.

ArrayList<Integer> scores = new ArrayList<>(); scores.add(90); scores.add(85); scores.add(72); // Access one element int first = scores.get(0); // 90 // How many elements? int count = scores.size(); // 3 // Classic for loop for (int i = 0; i < scores.size(); i++) { System.out.println(scores.get(i)); } // for-each — cleaner, no index needed for (int score : scores) { System.out.println(score); }

.get(i) instead of [i]

ArrayList isn't built on square-bracket syntax — you always retrieve elements through the .get(index) method.

.size() instead of .length

Arrays use the .length field. ArrayList uses the .size() method — note the parentheses!

for-each is the natural fit

When you just need each value (not the index), for (int score : scores) is cleaner and less error-prone.

Lesson 6 — Method Reference

Common ArrayList Methods

A quick reference for the methods you'll reach for constantly. Bookmark this one!

Checking & searching

contains(val) → is it in the list?
indexOf(val) → position, or -1
isEmpty() → true if size() == 0

Modifying

clear() → removes everything
set(i, val) → replace at index
remove(i) → delete by index

ArrayList vs. array — cheat sheet

array: arr[i] · arr.length · fixed size
ArrayList: list.get(i) · list.size() · resizable
💡 Rule of thumb: Default to ArrayList unless you have a specific reason to need a fixed-size array. It's more flexible and rarely costs you anything in a beginner program.
Lesson 6 — Mini-Project

Mini-Project: Student Grade Tracker

Use an ArrayList to store a variable number of grades, then compute stats about them.

📋 The Requirements

Store grades in an ArrayList<Integer>, then write code that:

  • Adds at least 5 grades to the list
  • Prints every grade using a for-each loop
  • Calculates and prints the average
  • Finds and prints the highest grade

🧪 Expected output

Grades: 90, 85, 72, 95, 88 Average: 86.0 Highest: 95

💡 Hint

To find the highest, start a variable at the first grade, then loop through and update it whenever you see something bigger — same pattern as the min/max logic from arrays.

Your starter code:

import java.util.ArrayList; public class GradeTracker { public static void main(String[] args) { ArrayList<Integer> grades = new ArrayList<>(); // add 5 grades here // print each grade // calculate the average // find the highest grade } }

🏆 Stretch challenges

1. Also find the lowest grade
2. Let the user type in grades with Scanner, entering -1 to stop
3. Print each grade with its letter grade (A/B/C/D/F) next to it

📁 Create this in ~/Development/labs/lab4/ — use labsmkdir lab4 && cd lab4code .
Recap — Lesson 6

What You've Covered

📏

Why ArrayList

Resizable, comes with built-in helper methods — solves the "I don't know the size in advance" problem arrays can't.

🧬

Generics

ArrayList<Type> needs an object type — use wrapper classes like Integer for primitives.

Adding & removing

add(), remove(), set() — no manual index shifting required.

🔁

Accessing & iterating

.get(i) and .size() replace [i] and .length.

🧮

Mini-project

Built a grade tracker that stores a variable number of grades and computes an average and a max — real ArrayList usage.

🚀

Coming up — Lesson 7

Strings In Depth — String methods, immutability, comparing Strings correctly, and StringBuilder.

Lesson 7 — Strings In Depth

Strings In Depth

You've used Strings since Lesson 1. Now let's unlock what they really are — and the powerful methods built into every one.

" H e l l o " [0] [1] [2] [3] [4] a sequence of indexed characters
🔒 Immutable 🧰 Methods ⚖️ equals vs == 🏗️ StringBuilder
Lesson 7 — Immutability

Strings Are Immutable Objects

Once a String is created, it can never change. Every "modification" actually creates a brand new String.

String name = "Jesus"; name = name.toUpperCase(); // this does NOT change the original "Jesus" // it creates a NEW String "JESUS" and // reassigns name to point at it String a = "cat"; String b = a; // b points to the same String a = a + "fish"; // a now points to a NEW String System.out.println(a); // catfish System.out.println(b); // cat — unchanged!

Nothing in place ever changes

Methods like toUpperCase() don't alter the original — they return a new String with the result.

Common mistake

Calling name.toUpperCase(); alone does nothing useful — the result is thrown away unless you store it: name = name.toUpperCase();

Why immutability is useful

Since Strings can't change, it's always safe to share one between multiple variables — no risk one place accidentally changes it for everyone.

Lesson 7 — String Methods

Reading Strings: length, charAt, substring

Three of the most-used String methods — for finding out how long a String is and pulling pieces out of it.

String word = "Hello"; // length() — how many characters int len = word.length(); // 5 // charAt(i) — the character at index i char first = word.charAt(0); // 'H' char last = word.charAt(len - 1); // 'o' // substring(start) — from start to the end String part1 = word.substring(2); // "llo" // substring(start, end) — end is EXCLUSIVE String part2 = word.substring(1, 3); // "el"

length() is a method, not a field

Unlike arrays' .length, Strings use .length() — with parentheses. Easy to mix up!

charAt() is 0-indexed

Just like arrays — the first character is at index 0, the last is at index length() - 1.

substring's end is exclusive

substring(1, 3) grabs indices 1 and 2 — not 3. A very common off-by-one trap.

Lesson 7 — String Methods

Transforming Strings: case, search, split

These methods change how a String looks, or break it apart into pieces.

String s = " Hello, World! "; s.toUpperCase() // " HELLO, WORLD! " s.toLowerCase() // " hello, world! " s.trim() // "Hello, World!" (no edges) s.contains("World") // true s.indexOf("World") // 9 s.replace("World", "Java") // " Hello, Java! " String csv = "apple,banana,cherry"; String[] fruits = csv.split(","); // ["apple", "banana", "cherry"]

Case & whitespace

toUpperCase() / toLowerCase() normalize case; trim() strips leading/trailing spaces — great for cleaning user input.

Searching

contains() asks yes/no. indexOf() tells you exactly where — or -1 if it's not found.

split() turns one String into many

Breaks a String into a String[] wherever it finds the separator. Perfect for parsing CSV-style data.

Lesson 7 — Comparing Strings

String Comparison: equals() vs. ==

This is the #1 String bug beginners hit. Here's finally the full explanation we foreshadowed back in Lesson 2.

memory (conceptually) "cat" a → b → a == b → true (same object) "cat" (new) c = new String("cat") a == c → false (different object!)
String a = "cat"; String b = "cat"; String c = new String("cat"); System.out.println(a == b); // true (usually) System.out.println(a == c); // false! System.out.println(a.equals(c)); // true ✅

== checks identity, not content

== asks "are these the exact same object in memory?" — not "do they contain the same letters?"

equals() checks content

Always use .equals() to compare what's inside two Strings. This is the rule: never == for Strings.

Lesson 7 — StringBuilder

StringBuilder — Building Strings Efficiently

Because Strings are immutable, concatenating in a loop secretly creates a new String every single time. StringBuilder fixes that.

😩 String concatenation in a loop — slow

String result = ""; for (int i = 0; i < 1000; i++) { result = result + i; // new String each time! } // 1000 iterations = ~1000 throwaway Strings

✅ StringBuilder — one object, mutated in place

StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append(i); // modifies the SAME object } String result = sb.toString(); // convert once, at the end
🏗️

StringBuilder is mutable

Unlike String, calling .append() changes the same object in place — no new object created each time.

Much faster in loops

Building a big String piece by piece? Use StringBuilder. The difference becomes huge as the loop count grows.

🔁

.toString() converts back

When you're done appending, call .toString() once to get a regular, immutable String.

Lesson 7 — Exercise

Exercise: Word Counter

Put String methods to work — count how many words are in a sentence.

📋 The Rules

Take a sentence and print:

  • The total number of words
  • The total number of characters (no spaces)
  • The longest word in the sentence

🧪 Example

Input: "the quick brown fox jumps" Words: 5 Characters (no spaces): 21 Longest word: quick

💡 Hint

Use .split(" ") to break the sentence into a String[] of words, then loop through it with a for-each.

Your starter code:

public class WordCounter { public static void main(String[] args) { String sentence = "the quick brown fox jumps"; String[] words = sentence.split(" "); // count words (hint: words.length) // count characters, skipping spaces // find the longest word } }

🏆 Stretch challenges

1. Ignore punctuation like . and , when counting characters
2. Use a Scanner so the user types their own sentence
3. Bonus: build a simple Caesar cipher — shift every letter by 1 using charAt()

📁 Create this in ~/Development/labs/lab5/ — use labsmkdir lab5 && cd lab5code .
Recap — Lesson 7

What You've Covered

🔒

Immutability

Strings never change in place. Methods like toUpperCase() return a brand new String.

🧰

Core methods

length(), charAt(), substring(), split(), and more.

⚖️

equals() vs. ==

== compares identity. .equals() compares content. Always use .equals() for Strings.

🏗️

StringBuilder

Mutable, efficient String building — use it whenever you're concatenating inside a loop.

🔤

Exercise

Built a word counter using split(), loops, and character counting — real-world text processing.

🚀

Coming up — Lesson 8

Classes & Objects — the start of Object-Oriented Programming. Modeling real things in code.

Lesson 8 — Classes & Objects

Classes & Objects

The start of Object-Oriented Programming — the biggest shift yet in how you think about writing code.

class Dog name age bark() 📐 blueprint new Rex age = 3 📦 object Bella age = 5 📦 object many independent objects, one class
🏗️ Class 📦 Object 🔧 Constructor 👉 this
Lesson 8 — The Big Idea

Modeling Real Things as Objects

Everything you've written so far is a list of instructions. OOP asks a different question: what things exist in this program, and what do they know and do?

😩 Without OOP — loose, disconnected variables

String dog1Name = "Rex"; int dog1Age = 3; String dog2Name = "Bella"; int dog2Age = 5; // nothing ties "dog1Name" and "dog1Age" // together except a shared prefix in // their variable names — fragile!

✅ With OOP — bundled together

class Dog { String name; int age; } Dog dog1 = new Dog(); dog1.name = "Rex"; dog1.age = 3; // name and age are truly connected — // they both live inside the same Dog
🧬

Fields = what an object knows

Variables that live inside a class describe the data every object of that type carries around.

⚙️

Methods = what an object can do

Functions defined inside a class describe the behavior every object of that type is capable of.

📐

A class is a blueprint, not a thing

Writing class Dog doesn't create a dog — it describes what any dog will look like.

Lesson 8 — Defining a Class

The Anatomy of a Class

A class bundles fields (data) and methods (behavior) that belong together into one blueprint.

public class Dog { // ① fields — the data every Dog has String name; int age; // ② a method — behavior every Dog has void bark() { System.out.println(name + " says Woof!"); } }

① Fields — the object's data

Declared just like variables, but at the class level. Every object built from this class gets its own copy of each field.

② Methods — the object's behavior

Defined exactly like the methods you learned in Lesson 5 — but here they can freely use the class's own fields, like name inside bark().

One class, many objects

You write Dog once. Every actual dog in your program — Rex, Bella, a hundred more — is built from this same blueprint.

Lesson 8 — Constructors

Constructors — Setting Up a New Object

A constructor runs automatically the moment an object is created — it's where you set the object's starting values.

public class Dog { String name; int age; // the constructor — same name as the class Dog(String name, int age) { this.name = name; this.age = age; } void bark() { System.out.println(name + " says Woof!"); } } // using it: Dog myDog = new Dog("Rex", 3); // name and age are set immediately

Same name as the class

A constructor is always named identically to its class — Dog(...) for class Dog. No exceptions.

No return type — not even void

Constructors skip the return type entirely. Writing void Dog(...) makes it a regular method by mistake, not a constructor.

Runs exactly once, at creation

The constructor fires the instant new Dog(...) executes — never again for that same object.

Lesson 8 — Creating Objects

Creating Objects with new

The class is the blueprint. new is what actually builds a real object from it.

Dog myDog = new Dog("Rex", 3); Dog yourDog = new Dog("Bella", 5); // dot notation reads a field System.out.println(myDog.name); // Rex // dot notation calls a method myDog.bark(); // Rex says Woof! yourDog.bark(); // Bella says Woof!
myDog name="Rex", age=3 yourDog name="Bella", age=5 two separate objects, same class

new calls the constructor

new Dog("Rex", 3) allocates memory for a fresh Dog, then immediately runs the constructor with those arguments.

Dot notation — . — accesses everything

myDog.name reads a field. myDog.bark() calls a method. Same dot, different purpose.

Independent copies

myDog and yourDog each have their own name and age — changing one never touches the other.

Lesson 8 — The this Keyword

The this Keyword

Inside a class, this means "the current object" — the one whose method is running right now.

🔴 Without this — a silent bug

Dog(String name, int age) { name = name; // ❌ assigns parameter to itself! age = age; // the field "name" stays null } // Java sees two variables named "name" here: // the parameter, and the field. Without "this", // it always means the closer one — the parameter.

✅ this.field = parameter

Dog(String name, int age) { this.name = name; // this.name = the field this.age = age; // name = the parameter } // "this.name" unambiguously means // "this object's name field"
⚔️

Solves naming conflicts

When a parameter and a field share a name (very common — and good style!), this tells Java which one you mean.

🙋

"this object", specifically

If you call myDog.bark(), then inside bark(), this refers to myDog.

✍️

Optional when there's no conflict

You can technically skip this when names don't clash — but many developers use it everywhere for clarity.

Lesson 8 — Independent Objects

Each Object Has Its Own State

Changing one object never affects another — even though they were built from the exact same class.

public class BankAccount { String owner; double balance; BankAccount(String owner, double balance) { this.owner = owner; this.balance = balance; } void deposit(double amount) { balance += amount; } } BankAccount acc1 = new BankAccount("Alice", 100); BankAccount acc2 = new BankAccount("Bob", 50); acc1.deposit(50); System.out.println(acc1.balance); // 150 System.out.println(acc2.balance); // 50 — untouched!

Fields live per-object, not per-class

Every BankAccount has its own balance in memory — they're not shared or linked.

Methods operate on "this" object's data

The same deposit() code runs for every account, but each call only touches the balance of the object it was called on.

This is why OOP models the real world well

Alice's account and Bob's account behave identically — but they're genuinely independent, just like two real bank accounts would be.

Lesson 8 — Mini-Project

Mini-Project: A BankAccount Class

Build a class from scratch — fields, a constructor, and methods that operate on this object's own data.

📋 The Requirements

Create a BankAccount class with:

  • Fields: owner (String), balance (double)
  • A constructor that sets both fields
  • deposit(amount) — adds to the balance
  • withdraw(amount) — subtracts, but refuses if it would go negative
  • printStatement() — prints owner and current balance

🧪 Expected output

Alice's balance: $100.0 Alice's balance: $150.0 Withdrawal denied — insufficient funds. Alice's balance: $150.0

💡 Hint

In withdraw(), use an if to check amount > balance before subtracting anything.

Your starter code:

public class BankAccount { String owner; double balance; BankAccount(String owner, double balance) { // your code here } // deposit, withdraw, printStatement... public static void main(String[] args) { BankAccount acc = new BankAccount("Alice", 100); acc.printStatement(); acc.deposit(50); acc.printStatement(); acc.withdraw(500); acc.printStatement(); } }

🏆 Stretch challenges

1. Add a transfer(BankAccount other, double amount) method
2. Track every transaction in an ArrayList<String> for a full history
3. Create several accounts and store them in an ArrayList of their own

📁 Create this in ~/Development/labs/lab6/ — use labsmkdir lab6 && cd lab6code .
Recap — Lesson 8

What You've Covered

📐

The big idea

A class models a real thing by bundling its data (fields) and behavior (methods) together in one blueprint.

🏗️

Fields & methods

Fields describe what an object knows. Methods describe what it can do — and can freely use its own fields.

🔧

Constructors

Same name as the class, no return type, runs once at creation to set up the object's starting values.

📦

new & this

new builds an object and calls its constructor. this means "the current object."

🔀

Independent state

Every object gets its own copy of the fields — the same methods, but separate data per object.

🏦

Mini-project

Built a BankAccount class with deposit/withdraw logic — real encapsulated behavior around private state.

🚀

Coming up — Lesson 9

Encapsulation & Access Modifiers — public vs. private, getters/setters, and static fields and methods.

Lesson 9 — Encapsulation

Encapsulation & Access Modifiers

Not every field should be wide open. Learn how to protect an object's data — and control exactly how the outside world can touch it.

BankAccount 🔒 private balance 🔓 public deposit() 🔓 public getBalance() outside code ❌ blocked ✅ via method
🔒 private 🔓 public 🎯 Getters/Setters 🧊 static
Lesson 9 — Why Encapsulation

The Problem With Wide-Open Fields

In Lesson 8, every field was directly accessible from outside the class. That's convenient — and dangerous.

😱 No protection — anything goes

public class BankAccount { String owner; double balance; } BankAccount acc = new BankAccount(); acc.owner = "Alice"; acc.balance = 100; // ❌ nothing stops this: acc.balance = -99999; // or this: acc.balance = acc.balance + 1000000; // direct access = no rules enforced

No validation happens

If any code, anywhere, can set balance directly, nothing can guarantee it stays sensible — no negative-balance rule, nothing.

The object can't protect itself

A well-designed BankAccount should be the only thing allowed to change its own balance — through controlled methods like deposit().

Encapsulation is the fix

Hide the fields. Expose only the specific, safe operations you want to allow. This is one of the core ideas of OOP.

Lesson 9 — Access Modifiers

public vs. private

Access modifiers control who is allowed to see or use a field or method.

public class BankAccount { private String owner; private double balance; public BankAccount(String owner, double balance) { this.owner = owner; this.balance = balance; } public void deposit(double amount) { balance += amount; // ✅ allowed — we're inside the class } } BankAccount acc = new BankAccount("Alice", 100); acc.balance = -99999; // ❌ compile error — balance has private access

private — this class only

A private field or method can only be used inside the class that declares it. Outside code can't even see it.

public — everyone

A public field or method is visible and usable from anywhere — other classes, main, everywhere.

The convention

Make fields private by default. Make constructors and the methods meant to be used from outside public. Everything else, keep as private as possible.

Lesson 9 — Getters & Setters

Getters and Setters

If fields are private, how does outside code read or change them safely? Through dedicated methods.

public class Student { private String name; private int age; // getter — reads the value public String getName() { return name; } // setter — changes the value public void setName(String name) { this.name = name; } public int getAge() { return age; } } Student s = new Student(); s.setName("Jesus"); System.out.println(s.getName()); // Jesus

Getter — getX()

Returns the current value of a private field. Naming convention: get + the field name, capitalized.

Setter — setX(value)

Updates a private field's value. Naming convention: set + the field name, capitalized.

Not every field needs both

A field with only a getter is effectively read-only from outside the class — sometimes that's exactly what you want.

Lesson 9 — Why It Matters

Why Getters & Setters Actually Matter

A getter/setter isn't just a wrapper — it's a checkpoint where you can add rules a plain field never could.

✅ A setter can validate before accepting

public class Student { private int age; public void setAge(int age) { if (age < 0 || age > 120) { System.out.println("Invalid age — ignored."); return; } this.age = age; // only reached if valid } } Student s = new Student(); s.setAge(-5); // rejected — printed a warning s.setAge(17); // accepted

A plain public field can't do this

If age were public, s.age = -5; would just... happen. There's no place to intercept it.

The class stays in control

Every path that changes age goes through setAge() — one single place to enforce the rules, forever.

You can change the internals later

If you later store age differently (say, a birthdate instead), only the getter/setter need to change — every caller keeps working exactly as before.

Lesson 9 — static

Static Fields & Methods

You've used static since Lesson 1's main. Now let's understand what it actually means.

Math.random(); // you've called static methods Math.max(3, 9); // before — no "new Math()" needed! public class Student { private String name; // per-object static int totalStudents = 0; // shared by ALL Students public Student(String name) { this.name = name; totalStudents++; // every new Student bumps it } static int getTotalStudents() { return totalStudents; } } new Student("Alice"); new Student("Bob"); System.out.println(Student.getTotalStudents()); // 2

static = belongs to the class, not an object

A static field is shared by every object of that class — there's only ever one copy, not one per object.

Call it through the class name

Student.getTotalStudents() — no object needed. That's why Math.random() never needs new Math().

Great for shared counters & utilities

Tracking "how many objects exist," or a helper method that doesn't need any particular object's data — both are classic static use cases.

Lesson 9 — static vs. instance

static vs. Instance: The Full Picture

Every field and method is either tied to one specific object, or shared across the whole class.

📦

Instance (no static)

Belongs to one object. Every object gets its own separate copy.

private String name; public String getName() { return name; }

Accessed via: object.field

🧊

static

Belongs to the class itself. One single copy, shared everywhere.

static int totalStudents; static int getTotalStudents() { return totalStudents; }

Accessed via: ClassName.field

🧠 Remember from Lesson 5: public static void mainmain is static because Java needs to run it before any object exists — there's nothing to call it on yet.
Lesson 9 — Mini-Project

Mini-Project: Student Class & Roster

Bring it all together — a properly encapsulated class, plus a static counter, used by a small roster program.

📋 The Requirements

Build a Student class with:

  • Private fields: name, grade (0–100)
  • A constructor that sets both
  • getName(), getGrade()
  • setGrade() that rejects values outside 0–100
  • A static counter tracking how many Students exist

🧪 Expected output

Alice: 92 Bob: 78 Invalid grade — ignored. Total students: 2

💡 Hint

Increment the static counter inside the constructor — every time a new Student is built, the shared count goes up by one.

Your starter code:

public class Student { private String name; private int grade; static int totalStudents = 0; public Student(String name, int grade) { // your code here } // getName, getGrade, setGrade... public static void main(String[] args) { Student a = new Student("Alice", 92); Student b = new Student("Bob", 78); b.setGrade(150); // should be rejected System.out.println("Total students: " + Student.totalStudents); } }

🏆 Stretch challenges

1. Store every Student in an ArrayList<Student> to build a real roster
2. Add a static method that computes the class average from that list
3. Add a letterGrade() method that converts the grade into A–F

📁 Create this in ~/Development/labs/lab7/ — use labsmkdir lab7 && cd lab7code .
Recap — Lesson 9

What You've Covered

🔒

Why encapsulation

Wide-open fields have no rules. Hiding them behind methods lets the class protect and control its own data.

🔓

public vs. private

private = this class only. public = accessible everywhere.

🎯

Getters & setters

Controlled read/write access to private fields — and a place to validate before accepting a change.

🧊

static

Belongs to the class, not any one object. One shared copy — great for counters and utility methods.

🎓

Mini-project

Built an encapsulated Student class with a validating setter and a static counter — a real class roster foundation.

🚀

Coming up — Lesson 10

Inheritance & Polymorphism — reduce duplication with extends, and treat subclasses as their parent type.