Java Tutorial

Lesson 1: Advanced OOP

Records, classes, enums, sealed interfaces, generics, pricing rules, validation, sorting, grouping, and domain invariants.

Run

scripts/run.sh lesson_1 demo
scripts/run.sh lesson_1 exercise
scripts/apply_answers.sh lesson_1
scripts/run.sh lesson_1 verify
scripts/reset_exercises.sh lesson_1

Function Pages

63 pages generated for Java functions in this lesson.

View related functions

Lesson README

Lesson 1: Advanced OOP In Modern Java

Audience: CS graduates who already know another language and need the Java object model, not introductory programming.

120-Minute Plan

MinutesActivity
0-10Compare Java classes, interfaces, records, and sealed types with familiar ADTs or protocol systems.
10-30Run scripts/run.sh lesson_1 demo and identify each dispatch point.
30-50Read the Money, Order, and PaymentMethod types and discuss invariants.
50-75Extend the demo in playground or add another payment method mentally before checking sealed type restrictions.
75-100Implement exercise functions in PriceRules.
100-115Apply answers and compare implementation choices.
115-120Summarize when records, classes, interfaces, and sealed hierarchies are appropriate.

Exercise

Use workbook.md for the full two-hour learner path. It includes tracing tasks, implementation rounds, and edge-case drills.

Edit src/exercise/java/tutorial/lesson1/exercise/PriceRules.java.

Functions to implement:

The file compiles before implementation. Use scripts/apply_answers.sh lesson_1 and scripts/reset_exercises.sh lesson_1 to switch between completed and stub code.

After applying answers, run scripts/run.sh lesson_1 verify to check normal cases, empty input, invalid percentages, shipping thresholds, grouping, sorting, and validation behavior.

Learner Workbook

Learner Workbook: Lesson 1 Advanced OOP

This workbook is designed to take about two hours. Do not try to finish by reading quickly. The point is to use the code as a Java lab: run it, trace it, predict behavior, edit small functions, and test edge cases.

What You Should Understand By The End

0-15 Minutes: Run Before Reading

Run:

scripts/reset_exercises.sh lesson_1
scripts/run.sh lesson_1 demo
scripts/run.sh lesson_1 exercise

Write down answers to these questions before opening the files:

15-35 Minutes: Trace The Demo

Open src/demo/java/tutorial/lesson1/demo/OopDemo.java.

Trace the code in this order:

Pause on each type and decide whether it is data, behavior, or both. Then inspect Money.java and answer:

35-55 Minutes: Read The Exercise API

Open src/exercise/java/tutorial/lesson1/exercise/PriceRules.java.

Read every TODO before editing. Group the functions by skill:

For each function, write one normal case and one edge case in a comment or notebook.

55-95 Minutes: Implement In Rounds

Round 1:

Round 2:

Round 3:

Round 4:

Round 5:

Round 6:

Round 7:

95-110 Minutes: Verify Answers

Run:

scripts/apply_answers.sh lesson_1
scripts/run.sh lesson_1 verify
scripts/reset_exercises.sh lesson_1

If your solution differs from the answers but passes the same behavior, that is fine. Compare the tradeoffs:

110-120 Minutes: Edge-Case Drill

Create your own cart examples in ExerciseRunner.java or a scratch file in playground.

Test at least five cases:

Finish by resetting:

scripts/reset_exercises.sh lesson_1

Instructor Notes

Instructor Notes: Lesson 1 Advanced OOP

Session Goal

Help experienced developers understand how modern Java models domain behavior with records, classes, interfaces, enums, sealed hierarchies, generics, and collection APIs. Learners should leave able to read a small Java domain model and reason about invariants, polymorphism, and type constraints.

Learning Outcomes

By the end of 120 minutes, learners should be able to:

Before Class

Run:

scripts/reset_exercises.sh lesson_1
scripts/run.sh lesson_1 demo
scripts/run.sh lesson_1 exercise

Know the key files:

Minute-By-Minute Plan

TimeSegmentInstructor Moves
0-10OrientationAsk learners what tools their usual language provides for domain modeling. Map those answers to Java's records, classes, enums, and interfaces.
10-20Run the demoRun scripts/run.sh lesson_1 demo. Ask learners to describe the domain before reading source.
20-35Entry point readingOpen OopDemo.java. Trace object creation, order.add, subtotal calculation, discount policy, and payment dispatch.
35-50Data and invariantsOpen Money.java, LineItem.java, and Customer.java. Discuss compact record constructors and validation.
50-65PolymorphismOpen PaymentMethod.java, CreditCard.java, BankTransfer.java, and Wallet.java. Explain sealed interfaces and permitted implementations.
65-75GenericsOpen Order.java. Explain T extends LineItem, List.copyOf, and why items is private mutable state.
75-95Exercise workLearners implement the staged PriceRules pricing, validation, sorting, and grouping functions from workbook.md.
95-108Solution compareRun scripts/apply_answers.sh lesson_1, then scripts/run.sh lesson_1 exercise. Compare solution choices.
108-118ExtensionAsk learners to sketch a GiftCard payment method or a VolumeDiscountPolicy. Discuss what changes are allowed by sealed types.
118-120RecapSummarize: records for values, classes for stateful behavior, interfaces for contracts, sealed interfaces for closed sets, generics for reusable constraints.

Teaching Script

Start with the problem, not the syntax:

"This lesson is a miniature order/payment domain. The interesting question is not how to print a string. The question is which states are legal, where validation belongs, and how Java makes illegal states harder to construct."

When opening Money.java, point out:

Checkpoint question:

"If this were represented as a tuple or plain map in another language, where would the currency check live?"

When opening Order.java, emphasize:

When opening PaymentMethod.java, use this explanation:

"An interface says what operations exist. A sealed interface says the implementation set is intentionally closed. That gives readers and tools a stronger guarantee than a normal interface."

Exercise Facilitation

Learners edit PriceRules.java.

Recommended order:

Do not require streams. A loop-based answer is valid and often clearer for learners new to Java. Use streams in the answer discussion to show idiomatic collection processing, not to imply every collection task needs streams.

Hints to give without solving:

Expected Answer Behavior

After applying answers:

scripts/apply_answers.sh lesson_1
scripts/run.sh lesson_1 verify

Expected important lines:

lesson_1 answers verified

Reset afterward:

scripts/reset_exercises.sh lesson_1

Common Mistakes

Discussion Prompts

Extension Challenge

Ask fast learners to add a new discount policy in playground/src/java. They can use the demo package or create a tiny independent class. The goal is to preserve the DiscountPolicy signature while changing only the behavior.

Functions In This Lesson

PriceRules.PriceRules

lesson_1/answers/java/tutorial/lesson1/exercise/PriceRules.java:11 | constructor | answer

private PriceRules() {

PriceRules.subtotal

lesson_1/answers/java/tutorial/lesson1/exercise/PriceRules.java:14 | method | answer

public static int subtotal(List<CartItem> items) {

PriceRules.itemCount

lesson_1/answers/java/tutorial/lesson1/exercise/PriceRules.java:20 | method | answer

public static int itemCount(List<CartItem> items) {

PriceRules.discountedTotal

lesson_1/answers/java/tutorial/lesson1/exercise/PriceRules.java:26 | method | answer

public static int discountedTotal(List<CartItem> items, int discountPercent) {

PriceRules.taxableTotal

lesson_1/answers/java/tutorial/lesson1/exercise/PriceRules.java:34 | method | answer

public static int taxableTotal(List<CartItem> items, int taxBasisPoints) {

PriceRules.shippingCents

lesson_1/answers/java/tutorial/lesson1/exercise/PriceRules.java:42 | method | answer

public static int shippingCents(List<CartItem> items, int freeThresholdCents, int standardShippingCents) {

PriceRules.mostExpensiveName

lesson_1/answers/java/tutorial/lesson1/exercise/PriceRules.java:56 | method | answer

public static Optional<String> mostExpensiveName(List<CartItem> items) {

PriceRules.quantitiesByName

lesson_1/answers/java/tutorial/lesson1/exercise/PriceRules.java:71 | method | answer

public static Map<String, Integer> quantitiesByName(List<CartItem> items) {

BankTransfer.BankTransfer

lesson_1/src/demo/java/tutorial/lesson1/demo/BankTransfer.java:9 | constructor | demo

public BankTransfer(String routingAlias) {

BankTransfer.charge

lesson_1/src/demo/java/tutorial/lesson1/demo/BankTransfer.java:14 | method | demo

public PaymentReceipt charge(Money amount) {

CreditCard.charge

lesson_1/src/demo/java/tutorial/lesson1/demo/CreditCard.java:7 | method | demo

public PaymentReceipt charge(Money amount) {

Customer.Customer

lesson_1/src/demo/java/tutorial/lesson1/demo/Customer.java:6 | compact constructor | demo

public Customer {

DiscountPolicy.discountFor

lesson_1/src/demo/java/tutorial/lesson1/demo/DiscountPolicy.java:5 | interface method | demo

Money discountFor(Order<? extends LineItem> order, Money subtotal);

DiscountPolicy.none

lesson_1/src/demo/java/tutorial/lesson1/demo/DiscountPolicy.java:7 | method | demo

static DiscountPolicy none() {

DiscountPolicy.tieredLoyalty

lesson_1/src/demo/java/tutorial/lesson1/demo/DiscountPolicy.java:11 | method | demo

static DiscountPolicy tieredLoyalty() {

LineItem.LineItem

lesson_1/src/demo/java/tutorial/lesson1/demo/LineItem.java:6 | compact constructor | demo

public LineItem {

LineItem.total

lesson_1/src/demo/java/tutorial/lesson1/demo/LineItem.java:21 | method | demo

public Money total() {

Money.Money

lesson_1/src/demo/java/tutorial/lesson1/demo/Money.java:6 | compact constructor | demo

public Money {

Money.plus

lesson_1/src/demo/java/tutorial/lesson1/demo/Money.java:13 | method | demo

public Money plus(Money other) {

Money.minus

lesson_1/src/demo/java/tutorial/lesson1/demo/Money.java:18 | method | demo

public Money minus(Money other) {

Money.times

lesson_1/src/demo/java/tutorial/lesson1/demo/Money.java:23 | method | demo

public Money times(int quantity) {

Money.multiply

lesson_1/src/demo/java/tutorial/lesson1/demo/Money.java:30 | method | demo

public Money multiply(double factor) {

Money.format

lesson_1/src/demo/java/tutorial/lesson1/demo/Money.java:37 | method | demo

public String format() {

Money.compareTo

lesson_1/src/demo/java/tutorial/lesson1/demo/Money.java:42 | method | demo

public int compareTo(Money other) {

Money.requireSameCurrency

lesson_1/src/demo/java/tutorial/lesson1/demo/Money.java:47 | method | demo

private void requireSameCurrency(Money other) {

OopDemo.OopDemo

lesson_1/src/demo/java/tutorial/lesson1/demo/OopDemo.java:4 | constructor | demo

private OopDemo() {

OopDemo.main

lesson_1/src/demo/java/tutorial/lesson1/demo/OopDemo.java:7 | method | demo

public static void main(String[] args) {

Order.Order

lesson_1/src/demo/java/tutorial/lesson1/demo/Order.java:12 | constructor | demo

public Order(String id, Customer customer) {

Order.id

lesson_1/src/demo/java/tutorial/lesson1/demo/Order.java:17 | method | demo

public String id() {

Order.customer

lesson_1/src/demo/java/tutorial/lesson1/demo/Order.java:21 | method | demo

public Customer customer() {

Order.add

lesson_1/src/demo/java/tutorial/lesson1/demo/Order.java:25 | method | demo

public void add(T item) {

Order.items

lesson_1/src/demo/java/tutorial/lesson1/demo/Order.java:29 | method | demo

public List<T> items() {

Order.subtotal

lesson_1/src/demo/java/tutorial/lesson1/demo/Order.java:33 | method | demo

public Money subtotal() {

Order.totalAfter

lesson_1/src/demo/java/tutorial/lesson1/demo/Order.java:44 | method | demo

public Money totalAfter(DiscountPolicy policy) {

PaymentMethod.charge

lesson_1/src/demo/java/tutorial/lesson1/demo/PaymentMethod.java:4 | interface method | demo

PaymentReceipt charge(Money amount);

Wallet.Wallet

lesson_1/src/demo/java/tutorial/lesson1/demo/Wallet.java:9 | constructor | demo

public Wallet(String accountId) {

Wallet.charge

lesson_1/src/demo/java/tutorial/lesson1/demo/Wallet.java:14 | method | demo

public PaymentReceipt charge(Money amount) {

AnswerVerifier.AnswerVerifier

lesson_1/src/exercise/java/tutorial/lesson1/exercise/AnswerVerifier.java:8 | constructor | exercise

private AnswerVerifier() {

AnswerVerifier.main

lesson_1/src/exercise/java/tutorial/lesson1/exercise/AnswerVerifier.java:11 | method | exercise

public static void main(String[] args) {

AnswerVerifier.expectEquals

lesson_1/src/exercise/java/tutorial/lesson1/exercise/AnswerVerifier.java:51 | method | exercise

private static void expectEquals(Object expected, Object actual, String label) {

AnswerVerifier.expectThrows

lesson_1/src/exercise/java/tutorial/lesson1/exercise/AnswerVerifier.java:57 | method | exercise

private static void expectThrows(Runnable action, String label) {

CartItem.CartItem

lesson_1/src/exercise/java/tutorial/lesson1/exercise/CartItem.java:4 | compact constructor | exercise

public CartItem {

ExerciseRunner.ExerciseRunner

lesson_1/src/exercise/java/tutorial/lesson1/exercise/ExerciseRunner.java:6 | constructor | exercise

private ExerciseRunner() {

ExerciseRunner.main

lesson_1/src/exercise/java/tutorial/lesson1/exercise/ExerciseRunner.java:9 | method | exercise

public static void main(String[] args) {

PriceRules.PriceRules

lesson_1/src/exercise/java/tutorial/lesson1/exercise/PriceRules.java:8 | constructor | exercise

private PriceRules() {

PriceRules.subtotal

lesson_1/src/exercise/java/tutorial/lesson1/exercise/PriceRules.java:11 | method | exercise

public static int subtotal(List<CartItem> items) {

PriceRules.itemCount

lesson_1/src/exercise/java/tutorial/lesson1/exercise/PriceRules.java:17 | method | exercise

public static int itemCount(List<CartItem> items) {

PriceRules.discountedTotal

lesson_1/src/exercise/java/tutorial/lesson1/exercise/PriceRules.java:22 | method | exercise

public static int discountedTotal(List<CartItem> items, int discountPercent) {

PriceRules.taxableTotal

lesson_1/src/exercise/java/tutorial/lesson1/exercise/PriceRules.java:28 | method | exercise

public static int taxableTotal(List<CartItem> items, int taxBasisPoints) {

PriceRules.shippingCents

lesson_1/src/exercise/java/tutorial/lesson1/exercise/PriceRules.java:34 | method | exercise

public static int shippingCents(List<CartItem> items, int freeThresholdCents, int standardShippingCents) {

PriceRules.mostExpensiveName

lesson_1/src/exercise/java/tutorial/lesson1/exercise/PriceRules.java:40 | method | exercise

public static Optional<String> mostExpensiveName(List<CartItem> items) {

PriceRules.namesSortedByLineTotalDescending

lesson_1/src/exercise/java/tutorial/lesson1/exercise/PriceRules.java:46 | method | exercise

public static List<String> namesSortedByLineTotalDescending(List<CartItem> items) {

PriceRules.quantitiesByName

lesson_1/src/exercise/java/tutorial/lesson1/exercise/PriceRules.java:52 | method | exercise

public static Map<String, Integer> quantitiesByName(List<CartItem> items) {

PriceRules.PriceRules

lesson_1/stubs/java/tutorial/lesson1/exercise/PriceRules.java:8 | constructor | stub

private PriceRules() {

PriceRules.subtotal

lesson_1/stubs/java/tutorial/lesson1/exercise/PriceRules.java:11 | method | stub

public static int subtotal(List<CartItem> items) {

PriceRules.itemCount

lesson_1/stubs/java/tutorial/lesson1/exercise/PriceRules.java:17 | method | stub

public static int itemCount(List<CartItem> items) {

PriceRules.discountedTotal

lesson_1/stubs/java/tutorial/lesson1/exercise/PriceRules.java:22 | method | stub

public static int discountedTotal(List<CartItem> items, int discountPercent) {

PriceRules.taxableTotal

lesson_1/stubs/java/tutorial/lesson1/exercise/PriceRules.java:28 | method | stub

public static int taxableTotal(List<CartItem> items, int taxBasisPoints) {

PriceRules.shippingCents

lesson_1/stubs/java/tutorial/lesson1/exercise/PriceRules.java:34 | method | stub

public static int shippingCents(List<CartItem> items, int freeThresholdCents, int standardShippingCents) {

PriceRules.mostExpensiveName

lesson_1/stubs/java/tutorial/lesson1/exercise/PriceRules.java:40 | method | stub

public static Optional<String> mostExpensiveName(List<CartItem> items) {

PriceRules.quantitiesByName

lesson_1/stubs/java/tutorial/lesson1/exercise/PriceRules.java:52 | method | stub

public static Map<String, Integer> quantitiesByName(List<CartItem> items) {