PriceRules.PriceRules
private PriceRules() {
Records, classes, enums, sealed interfaces, generics, pricing rules, validation, sorting, grouping, and domain invariants.
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
63 pages generated for Java functions in this lesson.
Audience: CS graduates who already know another language and need the Java object model, not introductory programming.
| Minutes | Activity |
|---|---|
| 0-10 | Compare Java classes, interfaces, records, and sealed types with familiar ADTs or protocol systems. |
| 10-30 | Run scripts/run.sh lesson_1 demo and identify each dispatch point. |
| 30-50 | Read the Money, Order, and PaymentMethod types and discuss invariants. |
| 50-75 | Extend the demo in playground or add another payment method mentally before checking sealed type restrictions. |
| 75-100 | Implement exercise functions in PriceRules. |
| 100-115 | Apply answers and compare implementation choices. |
| 115-120 | Summarize when records, classes, interfaces, and sealed hierarchies are appropriate. |
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:
subtotalitemCountdiscountedTotaltaxableTotalshippingCentsmostExpensiveNamenamesSortedByLineTotalDescendingquantitiesByNameThe 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.
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.
Optional represents "no result" without returning null.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:
Open src/demo/java/tutorial/lesson1/demo/OopDemo.java.
Trace the code in this order:
CustomerOrder<LineItem>LineItemDiscountPolicy.tieredLoyalty()PaymentMethodCreditCard.chargePause on each type and decide whether it is data, behavior, or both. Then inspect Money.java and answer:
Money.plus reject different currencies?Money.times return a new object instead of changing the old one?Money a record but Order is a class?Open src/exercise/java/tutorial/lesson1/exercise/PriceRules.java.
Read every TODO before editing. Group the functions by skill:
subtotal, itemCount, discountedTotal, taxableTotalshippingCentsmostExpensiveNamenamesSortedByLineTotalDescendingquantitiesByNameFor each function, write one normal case and one edge case in a comment or notebook.
Round 1:
subtotal.itemCount.scripts/run.sh lesson_1 exercise.Round 2:
discountedTotal.0, 10, and 100 percent discounts.-1 and 101.Round 3:
taxableTotal.825 means 8.25%.Round 4:
shippingCents.0.0.Round 5:
mostExpensiveName.Optional.empty() for empty input.Round 6:
unitCents * quantity.Round 7:
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:
Create your own cart examples in ExerciseRunner.java or a scratch file in playground.
Test at least five cases:
0, 100, and invalid boundariesFinish by resetting:
scripts/reset_exercises.sh lesson_1
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.
By the end of 120 minutes, learners should be able to:
OopDemo.main to a concrete PaymentMethod.Money validates currency and why Order<T extends LineItem> uses a bound.Optional, validation, and simple arithmetic.Run:
scripts/reset_exercises.sh lesson_1
scripts/run.sh lesson_1 demo
scripts/run.sh lesson_1 exercise
Know the key files:
src/demo/java/tutorial/lesson1/demo/OopDemo.javasrc/demo/java/tutorial/lesson1/demo/Money.javasrc/demo/java/tutorial/lesson1/demo/Order.javasrc/demo/java/tutorial/lesson1/demo/PaymentMethod.javasrc/exercise/java/tutorial/lesson1/exercise/PriceRules.java| Time | Segment | Instructor Moves |
|---|---|---|
| 0-10 | Orientation | Ask learners what tools their usual language provides for domain modeling. Map those answers to Java's records, classes, enums, and interfaces. |
| 10-20 | Run the demo | Run scripts/run.sh lesson_1 demo. Ask learners to describe the domain before reading source. |
| 20-35 | Entry point reading | Open OopDemo.java. Trace object creation, order.add, subtotal calculation, discount policy, and payment dispatch. |
| 35-50 | Data and invariants | Open Money.java, LineItem.java, and Customer.java. Discuss compact record constructors and validation. |
| 50-65 | Polymorphism | Open PaymentMethod.java, CreditCard.java, BankTransfer.java, and Wallet.java. Explain sealed interfaces and permitted implementations. |
| 65-75 | Generics | Open Order.java. Explain T extends LineItem, List.copyOf, and why items is private mutable state. |
| 75-95 | Exercise work | Learners implement the staged PriceRules pricing, validation, sorting, and grouping functions from workbook.md. |
| 95-108 | Solution compare | Run scripts/apply_answers.sh lesson_1, then scripts/run.sh lesson_1 exercise. Compare solution choices. |
| 108-118 | Extension | Ask learners to sketch a GiftCard payment method or a VolumeDiscountPolicy. Discuss what changes are allowed by sealed types. |
| 118-120 | Recap | Summarize: records for values, classes for stateful behavior, interfaces for contracts, sealed interfaces for closed sets, generics for reusable constraints. |
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:
currency() and cents().plus return new Money values.compareTo is legal only after checking currencies.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:
ArrayList internally.items() returns List.copyOf(items) to protect representation.LineItem subtypes without losing the methods it needs.subtotal() currently picks the first item's currency, which is enough for a teaching demo but would need stronger rules in production.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."
Learners edit PriceRules.java.
Recommended order:
subtotal with a loop first.itemCount.discountedTotal by calling subtotal.discountPercent.taxableTotal and discuss basis points.shippingCents and threshold boundaries.mostExpensiveName using either a loop or stream.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:
Optional.empty() is the explicit no-result value."CartItem::unitCents."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
null instead of Optional.empty().quantity.discountPercent as 0.10 rather than 10.item.unitCents instead of item.unitCents().Money allow negative values?Order reject mixed currencies at add time or subtotal time?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.
private PriceRules() {
public static int subtotal(List<CartItem> items) {
public static int itemCount(List<CartItem> items) {
public static int discountedTotal(List<CartItem> items, int discountPercent) {
public static int taxableTotal(List<CartItem> items, int taxBasisPoints) {
public static int shippingCents(List<CartItem> items, int freeThresholdCents, int standardShippingCents) {
public static Optional<String> mostExpensiveName(List<CartItem> items) {
public static List<String> namesSortedByLineTotalDescending(List<CartItem> items) {
public static Map<String, Integer> quantitiesByName(List<CartItem> items) {
public BankTransfer(String routingAlias) {
public PaymentReceipt charge(Money amount) {
public PaymentReceipt charge(Money amount) {
public Customer {
Money discountFor(Order<? extends LineItem> order, Money subtotal);
static DiscountPolicy none() {
static DiscountPolicy tieredLoyalty() {
public LineItem {
public Money total() {
public Money {
public Money plus(Money other) {
public Money minus(Money other) {
public Money times(int quantity) {
public Money multiply(double factor) {
public String format() {
public int compareTo(Money other) {
private void requireSameCurrency(Money other) {
private OopDemo() {
public static void main(String[] args) {
public Order(String id, Customer customer) {
public String id() {
public Customer customer() {
public void add(T item) {
public List<T> items() {
public Money subtotal() {
public Money totalAfter(DiscountPolicy policy) {
PaymentReceipt charge(Money amount);
public Wallet(String accountId) {
public PaymentReceipt charge(Money amount) {
private AnswerVerifier() {
public static void main(String[] args) {
private static void expectEquals(Object expected, Object actual, String label) {
private static void expectThrows(Runnable action, String label) {
public CartItem {
private ExerciseRunner() {
public static void main(String[] args) {
private PriceRules() {
public static int subtotal(List<CartItem> items) {
public static int itemCount(List<CartItem> items) {
public static int discountedTotal(List<CartItem> items, int discountPercent) {
public static int taxableTotal(List<CartItem> items, int taxBasisPoints) {
public static int shippingCents(List<CartItem> items, int freeThresholdCents, int standardShippingCents) {
public static Optional<String> mostExpensiveName(List<CartItem> items) {
public static List<String> namesSortedByLineTotalDescending(List<CartItem> items) {
public static Map<String, Integer> quantitiesByName(List<CartItem> items) {
private PriceRules() {
public static int subtotal(List<CartItem> items) {
public static int itemCount(List<CartItem> items) {
public static int discountedTotal(List<CartItem> items, int discountPercent) {
public static int taxableTotal(List<CartItem> items, int taxBasisPoints) {
public static int shippingCents(List<CartItem> items, int freeThresholdCents, int standardShippingCents) {
public static Optional<String> mostExpensiveName(List<CartItem> items) {
public static List<String> namesSortedByLineTotalDescending(List<CartItem> items) {
public static Map<String, Integer> quantitiesByName(List<CartItem> items) {