Learn how computers think, what programming languages are, and how code goes from human-readable text to something a CPU can actually run.
At its core, every computer has three key parts that work together.
The brain. Performs calculations and runs instructions — billions per second. Think of it as a chef following a recipe.
Short-term memory. Super fast, but temporary — everything in RAM disappears when you turn off the computer.
Long-term memory. Slower, but permanent — where your files and programs live when not running.
The CPU repeats this cycle billions of times per second. It's fast, not smart.
A program is just a very long, very precise recipe for the computer to follow.
Just like a recipe, skip a step or do them out of order and you get something unexpected — or nothing at all!
Computers do exactly what you tell them. Bugs happen because you wrote the wrong step — not because the computer made a mistake.
Every comma, bracket, and letter counts. Programming teaches you to think clearly and precisely.
Your program never talks directly to hardware. There's a layer in between — the Operating System.
Manages CPU time, RAM, storage, and devices so multiple programs can share hardware without crashing into each other.
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().
CPUs only understand 0s and 1s — programming languages bridge the gap between humans and machines.
Created by James Gosling at Sun Microsystems to solve a big problem: code written for one OS wouldn't run on another.
Java compiles to bytecode, which the JVM (Java Virtual Machine) can run on any computer — Windows, Mac, Linux.
Android apps, Minecraft, bank systems, government software — and it's the language used in AP Computer Science A!
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.
C, Python, and Java each take a completely different path from source code to running program.
Fastest at runtime. Compiles to machine code your OS runs directly. Not portable — you recompile per platform.
No compile step. The interpreter reads and runs each line. Great for quick scripts; slower at runtime than compiled languages.
Compiles once to portable bytecode. The JVM handles the final step on each platform. Best of both worlds for most use cases.
How does code actually run? Two very different approaches.
The entire program is translated before it runs. The compiler checks for errors first — nothing runs until it's clean.
✅ Catches errors early · Often faster
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.
✅ Easier to test · No compile step
Java makes you label what type of data a variable holds. Python figures it out on the fly.
☕ Java — Statically Typed
You must declare the type. The compiler enforces it before running.
🐍 Python — Dynamically Typed
No type labels needed. More flexible but errors show up later.
You will see all three this course. Here's what each one means.
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.
The program starts, but crashes mid-run. Examples: dividing by zero, accessing something that doesn't exist.
The error message shows where it crashed.
The program runs fine and produces output — but the output is wrong. Nothing tells you something broke.
Hardest to find — requires testing and thinking.
We'll set up and run this in Lesson 4 — for now, let's read it and understand every piece.
Main.java.
You don't need to run this yet. Just understand the journey a Java program takes.
How programs remember information — and why the type of data matters.
A variable is a named slot in RAM that your program can read or change while it runs.
Name — what you call it (score, lives)
Type — what kind of data it holds
Value — the actual data inside
Use camelCase: playerScore, isGameOver. Can't start with a number. Can't use Java keywords like int or class.
That's why they're called variables. A game score starts at 0 and goes up — same box, different value each time.
Java has 8 primitive types — these four cover nearly everything you'll need.
Whole numbers. Scores, counts, ages.
⚠️ 7 / 2 gives 3, not 3.5 — decimals get chopped!
Decimal numbers. Prices, averages, measurements.
8 bytes; tiny rounding quirks at extreme precision.
Exactly two values: true or false.
Powers every if statement and loop you'll ever write.
A single character in single quotes.
Single quotes = char. Double quotes = String.
String is not a primitive — it's a class. But it's so essential it gets its own slide.
The + operator joins strings together. Mix a String with any other type and Java converts it automatically.
String is capitalized because it's a class, not a primitive. Java is case-sensitive — string won't work.
Never use == to compare Strings. Use .equals() instead. We'll cover why in Lesson 9.
Three things you'll do with every variable — understand the difference between each.
Declaration creates the box. Initialization puts something in it. Java will error if you try to use a variable before it has a value.
Writing int lives = 2; a second time causes "variable already defined." Drop the type keyword on reassignment.
println adds a newline at the end. print doesn't — useful for printing things side by side.
Same math you know — with a few programming twists.
10 / 3 gives 3 — the decimal is thrown away. Make one number a double to get 3.333.
10 % 3 = 1 because 10 ÷ 3 = 3 remainder 1. Classic use: n % 2 == 0 checks if a number is even.
+= -= *= and ++ / -- are everywhere in Java code — get comfortable with them now.
The most common shortcuts in Java — used everywhere. Master these and you'll recognize half the code you see.
Uses the current value first, then increments. Like: "use it, then bump it."
Increments first, then uses the new value. Like: "bump it, then use it."
In loops, it doesn't matter — i++ and ++i do the same thing. But when assigning: y = x++ behaves very differently from y = ++x!
These combine an operation and an assignment into one shorthand — they're everywhere in real code.
Shorter to write and read. Compare: playerScore += 50 vs. playerScore = playerScore + 50. The first is clearer and faster to type.
+= -= *= /= %=
Each one is equivalent to: x = x ⊕ value
Increment a counter, decrement health, multiply by a power-up multiplier. These shortcuts make game logic clean and readable.
The most underrated operator. It's not just for math class — it's essential for real-world programming problems.
10 % 3 asks: "If I divide 10 by 3, what's left over?" Answer: 10 = (3 × 3) + 1 → remainder is 1.
Even/odd check: n % 2 == 0
Every nth iteration: if (i % 5 == 0) runs every 5 steps
Wrapping indices: (index % arrayLength)
A deck of cards cycles: cardIndex % 52 wraps back to card 0 after card 51. A timer loop: frame % 10 triggers every 10 frames.
A quick reference for everything you've learned about operators — use this as a bookmark!
x++ → use, then +1++x → +1, then usex-- → use, then -1--x → -1, then useAll equivalent to: x = x ⊕ value
+= add-= subtract*= multiply/= divide%= modn % 2 == 0 even?n % 2 == 1 odd?i % 5 == 0 every 5th?index % size wrap aroundi++ dozens of times per program. Comfort comes with repetition.
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.
Same type, accessed by index (position). scores[0] is the first value, scores[1] is the second.
This is crucial — the first element is always at index 0, not 1. A common source of "off-by-one" bugs!
When you create an array, you decide how many slots it has. You can't add more later (not in this course, anyway).
An int[] holds only integers. A String[] holds only strings.
The syntax looks odd at first, but you'll type it hundreds of times and it becomes automatic.
[] syntaxRead "int square-bracket" as "array of int." The [] goes after the type, not the variable name.
The new keyword creates the array in RAM. The number in brackets is the size.
int[] → 0, double[] → 0.0, boolean[] → false, String[] → null
Every array has a .length (not a method!). Perfect for loops: i < array.length
You access elements by index — the same syntax you saw in the diagrams.
If the array has 3 elements (indices 0, 1, 2), asking for scores[3] crashes with ArrayIndexOutOfBoundsException.
You can use variables, math, or even method calls inside brackets: scores[i+1] or scores[random()].
Once created, you can change individual elements as many times as you want, but you can't resize the array.
This is why loops exist! In Lesson 2, you'll loop through arrays with for (int i = 0; i < scores.length; i++).
A cheat sheet for everything arrays. Bookmark this one!
int[] whole numbersdouble[] decimalsString[] textboolean[] true/falsePrograms become useful when they can react to the user — read what they type and decide what to do next.
Read what the user types from the keyboard.
Run different code depending on a condition.
Compare values: equal, greater, less than.
Combine conditions: AND, OR, NOT.
The Scanner class lets your program pause and wait for the user to type something.
Scanner lives in the java.util package. You must import it at the top before you can use it.
nextLine() → String
nextInt() → int
nextDouble() → double
Just like System.out sends to the screen, System.in reads from the keyboard.
The most fundamental decision-making tool in any programming language.
true.
These are the building blocks of every condition you'll ever write.
Comparison operators — return true or false
Logical operators — combine conditions
&& = AND (both), || = OR (either), ! = NOT (flip it). You read ! as "not" — so !isGameOver means "game is not over."
Putting it all together — Scanner + if/else in one small program.
Variables (int secret, int guess), Scanner to read input, and if/else to react.
The player only gets one chance. In Lesson 4 we'll add a loop so they can keep guessing until they get it right.
Can you modify it to also tell the player how far off their guess was? Hint: subtraction and Math.abs().
We'll use Terminal to create the project folder, then use mise to pin Java 21 to it.
Press ⌘ Space, type Terminal, press Enter. A black or white window with a command prompt opens.
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.
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.
You should see:
If you see a different version or an error, make sure you ran cd into the project folder first.
Still in Terminal — one command opens the whole project in VS Code.
The . means "this folder." VS Code opens with lab1 loaded in the Explorer panel on the left.
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.
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.
.mise.toml file mise created — it locks Java 21 to this project so anyone who opens it gets the right version automatically.
The Java extension adds a ▶ Run button above your main method — no terminal commands needed.
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.
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.
Open the terminal with ⌃ ` and run it yourself:
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.
Variables are named RAM slots. Primitives: int, double, boolean, char. Strings concatenate with +. Integer division truncates. % gives the remainder.
Scanner reads from the keyboard. if/else if/else branches on a condition. Comparison operators produce booleans. && / || / ! combine them.
Loops — while, do-while, for, and for-each — and we'll finally set up Java and run real code.
Computers are great at repeating things — millions of times without getting tired. Loops are how you tell them to do it.
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:
✅ With a loop — same result, one idea:
Every game runs a loop — check input, update state, draw screen — thousands of times per second.
Go through every student's grade, every order in a store, every message in an inbox.
Keep asking the user for a valid answer until they give one — a natural loop.
Repeat a block of code as long as a condition stays true. Check the condition before each iteration.
Like a while loop — but the body runs at least once before the condition is ever checked.
If the condition starts false, the body never runs. Use when you might not need to run at all.
Body always runs at least once, then checks. Use when you need to do something before you can decide to continue.
Menu systems, input validation, game rounds — anywhere the user must act before the program can decide what to do next.
These will happen to you. Knowing what they look like makes them easy to fix.
🔴 Infinite Loop — never stops
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
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.
Update your guessing game so the player can keep guessing until they get it right.
Added a while loop around the guess logic. The loop keeps running until guess == secret.
attempts++ increments a counter each iteration — the loop naturally counts how many tries it took.
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.
When you know exactly how many times to repeat, for packs the setup, condition, and update into one tidy line.
Creates the loop variable before the first iteration. Usually int i = 0. The variable only exists inside the loop.
If false, the loop ends immediately. Same as a while loop's condition.
Usually i++ to move forward, but can be any expression — i--, i += 2, etc.
Both can do the same job — but one is usually a better fit depending on what you know upfront.
You know exactly how many times to loop — iterating over a list, counting from A to B, repeating N times.
You don't know how many times — keep going until some event happens (user input, a condition changes, a file ends).
A loop inside a loop. The inner loop runs completely for every single iteration of the outer loop.
Two keywords that give you fine-grained control over what a loop does mid-iteration.
⛔ break — exit the loop immediately
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
You want to skip certain items but keep looping — filtering out invalid data, skipping blank lines, ignoring certain values in a calculation.
A cleaner syntax for looping over every item in a collection — no index variable needed.
The variable takes on each value in the collection one by one. You declare its type on the left side of the colon.
When you just need each element's value and don't care about the index — reading, printing, summing, searching.
When you need the index — modifying elements in place, going backwards, skipping by 2s, comparing adjacent elements.
A classic programming challenge. Simple rules, great practice for loops and conditions working together.
Write a program that prints every number from 1 to 100, but:
Use the % (modulo) operator to check divisibility. n % 3 == 0 means n is divisible by 3. Check FizzBuzz first — order matters!
Your starter code:
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"
~/Development/labs/lab2/ — use labs → mkdir lab2 && cd lab2 → code .
Checks condition first. Runs zero or more times. Use when the count is unknown — keep going until something happens.
Runs the body first, then checks. Guarantees at least one execution. Ideal for input validation and menus.
Init, condition, update — all in one line. Use when you know the count. Can count up, down, or by any step size.
Cleanest syntax for visiting every element in a collection. No index to manage — just the value.
Infinite loop — forgot to update the loop variable. Off-by-one — used < when you needed <=.
break exits the loop entirely. continue skips the rest of this iteration and moves to the next.
Inner loop runs completely for each outer iteration. Total executions = outer count × inner count. Great for grids and tables.
Methods — breaking code into reusable named blocks, parameters, return types, and scope.
Stop repeating yourself. Package logic into a reusable, named block you can call again and again — with different data each time.
Without methods, every calculation gets copy-pasted everywhere you need it. Methods let you write the logic once.
😩 Without a method — repeated logic
✅ With a method — write once, reuse
Write the logic once, call it as many times as you need — with different data each time.
A well-named method like calculateTax() explains itself — no comment needed.
One bug in one place — fix it once and every caller benefits, instead of hunting down every copy.
Every method you write has the same five parts, no matter what it does.
What kind of value comes back out — int, double, String... or void if nothing comes back.
camelCase, just like variables. Should be a verb — add, calculateAverage, printReceipt.
The inputs the method needs, in parentheses. Can be zero, one, or many — separated by commas.
The code that runs when the method is called, wrapped in { }.
Defining a method doesn't run it — you have to call it by name for its code to execute.
add(5, 3) pauses main, runs add's body completely, then main resumes exactly where it left off.
Parameters let the same method work with completely different data every time it's called.
Parameters are the placeholders in the definition (int a, int b). Arguments are the real values you pass in when calling (5, 3).
Arguments are matched to parameters by position, not by name. The 1st argument fills the 1st parameter, and so on.
Inside the method, a and b behave like regular variables — you can even reassign them without affecting the caller's originals.
A method can hand a value back to whoever called it — or hand nothing back at all, using void.
Returns a value
Returns nothing — void
return exits immediatelyThe moment Java hits return, the method ends — any code after it in that path never runs.
If the method says int, you must return an int. Returning a String from an int method is a compile-time error.
A void method does something (prints, changes something) but doesn't hand back a value you can store.
If a method promises int, every possible branch (if/else) must eventually hit a return — or it won't compile.
Where you declare a variable decides where it's visible — and where it disappears.
Declared inside a method. Only exist while that method is running — created fresh on each call, destroyed when it returns.
Declared outside any method, directly in the class. Shared and visible across every method in that class.
Trying to use a local variable from one method inside a different method — Java says "cannot find symbol." It simply doesn't exist there.
Keep variables as local as possible. Only make something a field if multiple methods genuinely need to share it.
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.
The simplest version of the problem, answered directly with no further recursive call. Without one, recursion never stops.
Calls itself with a smaller or simpler input, moving one step closer to the base case each time.
Each call waits, paused, for the one below it to finish — then they all resolve in reverse order, like stacked plates.
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
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
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).
Rebuild a basic calculator, but this time every operation is its own method.
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)Call all four methods from main with the same two numbers, and print each labeled result with println.
Your starter code:
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
~/Development/labs/lab3/ — use labs → mkdir lab3 && cd lab3 → code .
Write logic once, reuse it anywhere. Improves readability and makes bugs easier to fix — one place, not many.
Return type, name, parameters, body — static <type> name(params) { }.
Inputs passed by position. Local to the method — separate from the caller's original variables.
return sends a value back and exits immediately. void means no value comes back.
Local variables live and die inside one method call. Class-level fields are shared across every method.
A method that calls itself needs a base case (stops it) and a recursive case (moves toward the base case).
Recursion's version of an infinite loop — a missing or unreachable base case means the calls never stop piling up.
Built a calculator with a dedicated method per operation — the same pattern real programs use everywhere.
ArrayLists — a resizable, more powerful alternative to arrays, plus the Collections framework.
Arrays are powerful, but they can't grow. Meet ArrayList — a resizable, more flexible way to store a list of data.
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
✅ ArrayList — grows as needed
Grows and shrinks automatically as you add or remove elements — no more planning the exact size ahead of time.
Comes with helpers like add(), remove(), contains() — arrays give you none of this.
ArrayList is one of many collection types in Java — a family of tools built for storing and organizing groups of data.
ArrayList lives in java.util and uses generics — angle brackets that say what type it holds.
The angle brackets tell Java (and you) exactly what kind of object this list holds — every element must match.
Generics can't use primitives. Use the wrapper class instead: int → Integer, double → Double, boolean → Boolean.
Writing scores.add(90) just works — Java quietly "autoboxes" the int into an Integer for you.
This is where ArrayList really shines — no shifting arrays around by hand.
Inserting in the middle automatically pushes later elements over — no manual index math like you'd need with an array.
remove(1) on an ArrayList<Integer> removes by index. To remove the value 1 itself, use remove(Integer.valueOf(1)).
set(i, val) replaces what's already at index i. add(i, val) inserts a brand new element there.
Reading elements looks a lot like arrays — but with .get() and .size() instead of [ ] and .length.
ArrayList isn't built on square-bracket syntax — you always retrieve elements through the .get(index) method.
Arrays use the .length field. ArrayList uses the .size() method — note the parentheses!
When you just need each value (not the index), for (int score : scores) is cleaner and less error-prone.
A quick reference for the methods you'll reach for constantly. Bookmark this one!
contains(val) → is it in the list?indexOf(val) → position, or -1isEmpty() → true if size() == 0clear() → removes everythingset(i, val) → replace at indexremove(i) → delete by indexarr[i] · arr.length · fixed sizelist.get(i) · list.size() · resizableArrayList 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.
Use an ArrayList to store a variable number of grades, then compute stats about them.
Store grades in an ArrayList<Integer>, then write code that:
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:
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
~/Development/labs/lab4/ — use labs → mkdir lab4 && cd lab4 → code .
Resizable, comes with built-in helper methods — solves the "I don't know the size in advance" problem arrays can't.
ArrayList<Type> needs an object type — use wrapper classes like Integer for primitives.
add(), remove(), set() — no manual index shifting required.
.get(i) and .size() replace [i] and .length.
Built a grade tracker that stores a variable number of grades and computes an average and a max — real ArrayList usage.
Strings In Depth — String methods, immutability, comparing Strings correctly, and StringBuilder.
You've used Strings since Lesson 1. Now let's unlock what they really are — and the powerful methods built into every one.
Once a String is created, it can never change. Every "modification" actually creates a brand new String.
Methods like toUpperCase() don't alter the original — they return a new String with the result.
Calling name.toUpperCase(); alone does nothing useful — the result is thrown away unless you store it: name = name.toUpperCase();
Since Strings can't change, it's always safe to share one between multiple variables — no risk one place accidentally changes it for everyone.
Three of the most-used String methods — for finding out how long a String is and pulling pieces out of it.
Unlike arrays' .length, Strings use .length() — with parentheses. Easy to mix up!
Just like arrays — the first character is at index 0, the last is at index length() - 1.
substring(1, 3) grabs indices 1 and 2 — not 3. A very common off-by-one trap.
These methods change how a String looks, or break it apart into pieces.
toUpperCase() / toLowerCase() normalize case; trim() strips leading/trailing spaces — great for cleaning user input.
contains() asks yes/no. indexOf() tells you exactly where — or -1 if it's not found.
Breaks a String into a String[] wherever it finds the separator. Perfect for parsing CSV-style data.
This is the #1 String bug beginners hit. Here's finally the full explanation we foreshadowed back in Lesson 2.
== asks "are these the exact same object in memory?" — not "do they contain the same letters?"
Always use .equals() to compare what's inside two Strings. This is the rule: never == for Strings.
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
✅ StringBuilder — one object, mutated in place
Unlike String, calling .append() changes the same object in place — no new object created each time.
Building a big String piece by piece? Use StringBuilder. The difference becomes huge as the loop count grows.
When you're done appending, call .toString() once to get a regular, immutable String.
Put String methods to work — count how many words are in a sentence.
Take a sentence and print:
Use .split(" ") to break the sentence into a String[] of words, then loop through it with a for-each.
Your starter code:
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()
~/Development/labs/lab5/ — use labs → mkdir lab5 && cd lab5 → code .
Strings never change in place. Methods like toUpperCase() return a brand new String.
length(), charAt(), substring(), split(), and more.
== compares identity. .equals() compares content. Always use .equals() for Strings.
Mutable, efficient String building — use it whenever you're concatenating inside a loop.
Built a word counter using split(), loops, and character counting — real-world text processing.
Classes & Objects — the start of Object-Oriented Programming. Modeling real things in code.
The start of Object-Oriented Programming — the biggest shift yet in how you think about writing code.
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
✅ With OOP — bundled together
Variables that live inside a class describe the data every object of that type carries around.
Functions defined inside a class describe the behavior every object of that type is capable of.
Writing class Dog doesn't create a dog — it describes what any dog will look like.
A class bundles fields (data) and methods (behavior) that belong together into one blueprint.
Declared just like variables, but at the class level. Every object built from this class gets its own copy of each field.
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().
You write Dog once. Every actual dog in your program — Rex, Bella, a hundred more — is built from this same blueprint.
A constructor runs automatically the moment an object is created — it's where you set the object's starting values.
A constructor is always named identically to its class — Dog(...) for class Dog. No exceptions.
Constructors skip the return type entirely. Writing void Dog(...) makes it a regular method by mistake, not a constructor.
The constructor fires the instant new Dog(...) executes — never again for that same object.
The class is the blueprint. new is what actually builds a real object from it.
new Dog("Rex", 3) allocates memory for a fresh Dog, then immediately runs the constructor with those arguments.
myDog.name reads a field. myDog.bark() calls a method. Same dot, different purpose.
myDog and yourDog each have their own name and age — changing one never touches the other.
Inside a class, this means "the current object" — the one whose method is running right now.
🔴 Without this — a silent bug
✅ this.field = parameter
When a parameter and a field share a name (very common — and good style!), this tells Java which one you mean.
If you call myDog.bark(), then inside bark(), this refers to myDog.
You can technically skip this when names don't clash — but many developers use it everywhere for clarity.
Changing one object never affects another — even though they were built from the exact same class.
Every BankAccount has its own balance in memory — they're not shared or linked.
The same deposit() code runs for every account, but each call only touches the balance of the object it was called on.
Alice's account and Bob's account behave identically — but they're genuinely independent, just like two real bank accounts would be.
Build a class from scratch — fields, a constructor, and methods that operate on this object's own data.
Create a BankAccount class with:
owner (String), balance (double)deposit(amount) — adds to the balancewithdraw(amount) — subtracts, but refuses if it would go negativeprintStatement() — prints owner and current balanceIn withdraw(), use an if to check amount > balance before subtracting anything.
Your starter code:
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
~/Development/labs/lab6/ — use labs → mkdir lab6 && cd lab6 → code .
A class models a real thing by bundling its data (fields) and behavior (methods) together in one blueprint.
Fields describe what an object knows. Methods describe what it can do — and can freely use its own fields.
Same name as the class, no return type, runs once at creation to set up the object's starting values.
new builds an object and calls its constructor. this means "the current object."
Every object gets its own copy of the fields — the same methods, but separate data per object.
Built a BankAccount class with deposit/withdraw logic — real encapsulated behavior around private state.
Encapsulation & Access Modifiers — public vs. private, getters/setters, and static fields and methods.
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.
In Lesson 8, every field was directly accessible from outside the class. That's convenient — and dangerous.
😱 No protection — anything goes
If any code, anywhere, can set balance directly, nothing can guarantee it stays sensible — no negative-balance rule, nothing.
A well-designed BankAccount should be the only thing allowed to change its own balance — through controlled methods like deposit().
Hide the fields. Expose only the specific, safe operations you want to allow. This is one of the core ideas of OOP.
Access modifiers control who is allowed to see or use a field or method.
A private field or method can only be used inside the class that declares it. Outside code can't even see it.
A public field or method is visible and usable from anywhere — other classes, main, everywhere.
Make fields private by default. Make constructors and the methods meant to be used from outside public. Everything else, keep as private as possible.
If fields are private, how does outside code read or change them safely? Through dedicated methods.
getX()Returns the current value of a private field. Naming convention: get + the field name, capitalized.
setX(value)Updates a private field's value. Naming convention: set + the field name, capitalized.
A field with only a getter is effectively read-only from outside the class — sometimes that's exactly what you want.
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
If age were public, s.age = -5; would just... happen. There's no place to intercept it.
Every path that changes age goes through setAge() — one single place to enforce the rules, forever.
If you later store age differently (say, a birthdate instead), only the getter/setter need to change — every caller keeps working exactly as before.
You've used static since Lesson 1's main. Now let's understand what it actually means.
A static field is shared by every object of that class — there's only ever one copy, not one per object.
Student.getTotalStudents() — no object needed. That's why Math.random() never needs new Math().
Tracking "how many objects exist," or a helper method that doesn't need any particular object's data — both are classic static use cases.
Every field and method is either tied to one specific object, or shared across the whole class.
Belongs to one object. Every object gets its own separate copy.
Accessed via: object.field
Belongs to the class itself. One single copy, shared everywhere.
Accessed via: ClassName.field
public static void main — main is static because Java needs to run it before any object exists — there's nothing to call it on yet.
Bring it all together — a properly encapsulated class, plus a static counter, used by a small roster program.
Build a Student class with:
name, grade (0–100)getName(), getGrade()setGrade() that rejects values outside 0–100Increment the static counter inside the constructor — every time a new Student is built, the shared count goes up by one.
Your starter code:
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
~/Development/labs/lab7/ — use labs → mkdir lab7 && cd lab7 → code .
Wide-open fields have no rules. Hiding them behind methods lets the class protect and control its own data.
private = this class only. public = accessible everywhere.
Controlled read/write access to private fields — and a place to validate before accepting a change.
Belongs to the class, not any one object. One shared copy — great for counters and utility methods.
Built an encapsulated Student class with a validating setter and a static counter — a real class roster foundation.
Inheritance & Polymorphism — reduce duplication with extends, and treat subclasses as their parent type.