Checkpoint: Objects & Data

Reviewed & published by Brayan K

You've learned switch, wrapper classes, access modifiers, packages, dates, Comparable/Comparator and records — let's combine them. This checkpoint has no new theory; it's where the last seven lessons click together into something you build yourself.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You've Learned

A quick recap of the toolkit you're about to wield together:

Map one value to a result with the arrow form: case "A" -> 3; — no break, returns a value.

Integer, Double… box primitives for collections; compare with .equals(), parse with parseInt.

private by default; expose only what callers need. Helpers stay hidden.

Namespaces for classes; import to borrow types; java.lang is automatic.

Immutable LocalDate; measure gaps with ChronoUnit.DAYS.between.

One natural order via compareTo; many ad-hoc orders via Comparator.comparing(...).

One line gives immutable fields, accessors, and equals/hashCode/toString — the perfect data model to sort and print.

How This Checkpoint Works

First a short warm-up that stitches a few features together so you can see them cooperate. Then the main event: a multi-step build challenge with a starter scaffold. Try it yourself before revealing the solution, and finish with a quick quiz to confirm it all stuck.

🔥 Warm-Up — Four Features at Once

Read this before running it. Notice how a record models each sale, a Comparator orders them, and a switch expression picks a weight — three lessons in twenty lines.

import java.util.*;

public class Main {
    // A warm-up that touches FOUR of the things you've learned:
    // a record, a switch expression, a wrapper utility, and a comparator.
    record Sale(String item, String tier, int amount) {}

    public static void main(String[] args) {
        List<Sale> sales = new ArrayList<>(List.of(
            new Sale("Pen",   "B", 30),
            new Sale("Bag",   "A", 90),
            new Sale("Pad",   "C", 15)
        ));

        // switch expression turns a tier letter into a multiplier
        // comparator sorts by amount descending
        sales.sort(Comparator.comparingInt(Sale::amount).reversed());

        for (Sale s : sales) {
            int weight = switch (s.tier()) {
                case "A" -> 3;
                case "B" -> 2;
                default  -> 1;
            };
            // Integer wrapper utility: parse-free, but use compare for clarity
            int weighted = s.amount() * weight;
            System.out.printf("%-4s tier %s -> weighted %d%n", s.item(), s.tier(), weighted);
        }
    }
}

Build Challenge — Library Catalogue

Build a small library catalogue that brings together a record, access modifiers, a switch expression, a Comparator, a LocalDate calculation, and clean formatted output. Work through the four numbered steps in the starter. Don't reveal the solution until you have a running version (or you're truly stuck).

1. Declare record Loan(String title, String category, LocalDate due).

2. A private daysLeft(loan, today) using ChronoUnit.DAYS.between.

3. A private fee(category) using a switch expression.

4. In main: build the list, sort by due date with a Comparator, print each line.

import java.util.*;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class Main {
    // ============================================================
    // BUILD CHALLENGE — a tiny library catalogue
    // ============================================================
    // Combine: record + access modifiers + a switch expression +
    // a Comparator + LocalDate + a wrapper utility.
    //
    // STEP 1: declare a record  Loan(String title, String category, LocalDate due)
    //
    // STEP 2: write a method  int daysLeft(Loan loan, LocalDate today)
    //         returning whole days from today until loan.due()
    //         (use ChronoUnit.DAYS.between).
    //
    // STEP 3: write a method  String fee(String category)
    //         using a SWITCH EXPRESSION:
    //           "BOOK" -> "$0.25/day"
    //           "DVD"  -> "$1.00/day"
    //           default -> "$0.50/day"
    //
    // STEP 4: in main, build a List<Loan>, sort it by due date ASCENDING
    //         with a Comparator, then print each line as:
    //           <title> (<category>, fee <fee>) due in <n> days
    //
    // Expected output (today = 2026-06-17):
    //   Algorithms (BOOK, fee $0.25/day) due in 3 days
    //   Dune (DVD, fee $1.00/day) due in 8 days
    //   Atlas (MAP, fee $0.50/day) due in 20 days

    public static void main(String[] args) {
        LocalDate today = LocalDate.of(2026, 6, 17);
        // your code here
    }
}

Here's one clean way to wire it all together. Notice the helpers are private static (access modifiers), the data is a record, fee is a switch expression, the list is ordered by a Comparator keyed on Loan::due, and daysLeft uses ChronoUnit.

import java.util.*;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class Main {
    // STEP 1: an immutable data holder
    record Loan(String title, String category, LocalDate due) {}

    // STEP 2: days from today until the due date
    private static long daysLeft(Loan loan, LocalDate today) {
        return ChronoUnit.DAYS.between(today, loan.due());
    }

    // STEP 3: fee chosen by a switch expression
    private static String fee(String category) {
        return switch (category) {
            case "BOOK" -> "$0.25/day";
            case "DVD"  -> "$1.00/day";
            default     -> "$0.50/day";
        };
    }

    public static void main(String[] args) {
        LocalDate today = LocalDate.of(2026, 6, 17);

        List<Loan> loans = new ArrayList<>(List.of(
            new Loan("Atlas",      "MAP",  LocalDate.of(2026, 7, 7)),
            new Loan("Algorithms", "BOOK", LocalDate.of(2026, 6, 20)),
            new Loan("Dune",       "DVD",  LocalDate.of(2026, 6, 25))
        ));

        // STEP 4: sort by due date ascending, then print
        loans.sort(Comparator.comparing(Loan::due));

        for (Loan l : loans) {
            long n = daysLeft(l, today);
            System.out.println(l.title() + " (" + l.category()
                + ", fee " + fee(l.category()) + ") due in " + n + " days");
        }
    }
}

Why it produces that order:

The list is built with Atlas/Algorithms/Dune, but Comparator.comparing(Loan::due) sorts by due date ascending: 20 June (Algorithms), 25 June (Dune), 7 July (Atlas). The day counts come from ChronoUnit.DAYS.between(today, due) with today = 17 June, giving 3, 8, and 20 days.

📝 Checkpoint Quiz

Answer each in your head, then reveal. Six questions across all seven topics.

Answer: It returns a value you can assign to a variable, running only the matching branch with no fall-through. A single-expression branch (case A -> 5;) returns automatically; a multi-line branch uses a { } block ending in yield value; to hand the value back.

Answer: 1000 is outside Java's Integer cache (-128..127), so a and b are separate objects and == compares references. Use a.equals(b) to compare wrapper values — it's always correct.

Answer: Only classes in the same package — that's package-private (the default), not public. It's narrower than protected, which also reaches subclasses in other packages.

Answer: Still 2026-01-01. LocalDate is immutable, so plusDays returns a new date that's thrown away here. You must capture it: d = d.plusDays(10);.

Comparator.comparingInt(Player::score).reversed()
          .thenComparing(Player::name)

Answer: Reverse the primary (score) comparator for descending, then chain thenComparing(Player::name) for the alphabetical tie-breaker. The tie-breaker is consulted only when scores are equal.

Answer: Any three of: private final fields x/y; a canonical constructor; accessors x() and y() (no "get" prefix); and value-based equals(), hashCode(), and toString(). All immutable, all from one line.

Pitfalls When You Combine Them

Going Further

Checkpoint Complete!

You just combined seven separate topics — switch, wrapper classes, access modifiers, packages, dates, Comparable/Comparator, and records — into one working program. That's a genuine milestone: you're no longer learning isolated features, you're assembling them into software.

Practice quiz

A switch EXPRESSION (the arrow form, case A -> 5;) primarily lets you:

  • Fall through to the next case
  • Loop over values
  • Return a value you can assign, running only the matching branch
  • Replace a for loop

Answer: Return a value you can assign, running only the matching branch. A switch expression returns a value with no fall-through; a multi-line branch uses a block ending in yield.

Why is Integer a = 1000, b = 1000; a == b false?

  • 1000 is outside the Integer cache (-128..127), so a and b are different objects
  • == always returns false for Integer
  • 1000 is too large for an Integer
  • Autoboxing is disabled

Answer: 1000 is outside the Integer cache (-128..127), so a and b are different objects. Values outside -128..127 are not cached, so == compares two distinct references. Use a.equals(b).

For comparing wrapper values like Integer reliably, you should use:

  • ==
  • the - operator
  • compareTo only
  • .equals()

Answer: .equals(). .equals() compares the wrapped values and is always correct; == may compare references for uncached values.

A field written with NO access keyword is visible to:

  • Everyone (public)
  • Only classes in the same package (package-private)
  • Subclasses anywhere
  • Only the same class

Answer: Only classes in the same package (package-private). No keyword means package-private — same package only. That is narrower than protected.

After LocalDate d = LocalDate.of(2026,1,1); d.plusDays(10); what is d?

  • Still 2026-01-01
  • 2026-01-11
  • A NullPointerException
  • 2026-01-10

Answer: Still 2026-01-01. LocalDate is immutable. plusDays returns a NEW date that's discarded here; you must write d = d.plusDays(10).

Which measures whole days between two LocalDates?

  • today.minus(due)
  • due - today
  • ChronoUnit.DAYS.between(today, due)
  • Period.now()

Answer: ChronoUnit.DAYS.between(today, due). ChronoUnit.DAYS.between(start, end) returns the whole-day gap between two temporal values.

To sort by score DESCENDING, breaking ties by name ascending, you write:

  • Comparator.comparing(Player::name).reversed()
  • Comparator.comparingInt(Player::score).reversed().thenComparing(Player::name)
  • Comparator.comparingInt(Player::score).thenComparing(Player::name).reversed()
  • Comparator.naturalOrder()

Answer: Comparator.comparingInt(Player::score).reversed().thenComparing(Player::name). Reverse the score comparator for descending, then chain thenComparing(name) for the ascending tie-breaker.

Comparable vs Comparator: which is correct?

  • A type can have many Comparable orders
  • Comparator must be implemented by the class itself
  • They are interchangeable in every case
  • Comparable defines one natural order via compareTo; Comparator gives many external orders

Answer: Comparable defines one natural order via compareTo; Comparator gives many external orders. Comparable is the single natural order baked into the class; Comparators are external and you can have many.

For a record Point(int x, int y), the accessor for x is called:

  • getX()
  • x()
  • getx()
  • valueX()

Answer: x(). Records generate accessors named after the component with no 'get' prefix, so it is x(), not getX().

Which does a record Point(int x, int y) {} NOT automatically generate?

  • A canonical constructor
  • equals() and hashCode() by value
  • Mutable setters like setX()
  • toString()

Answer: Mutable setters like setX(). Records are immutable — no setters. They give you final fields, a constructor, accessors, equals/hashCode/toString.

Continue this course

Frequently asked questions

Why combine so many features in one checkpoint?

Because real programs never use one concept in isolation. A single small feature — a library catalogue, an order list, a leaderboard — naturally pulls in records for the data, a comparator for ordering, a switch for branching logic, dates for deadlines, and access modifiers for structure. Practising them together is the bridge between 'I understand each topic' and 'I can build something'.

Should I make helper methods private or public?

Default to private. A helper like fee(category) or daysLeft(loan, today) is internal machinery, not part of the program's public interface, so private keeps your surface small and your intent clear. Only widen to public (or package-private) when another class genuinely needs to call it. The habit 'as private as possible' from the access-modifiers lesson applies directly here.

When should I reach for a record versus a regular class in a build like this?

Use a record when the type is just its data — Loan is title + category + due date and nothing more, so a record is ideal: immutable, with equals/hashCode/toString and accessors for free. Switch to a regular class only when you need mutability, inheritance, or behaviour beyond holding values. For the data models in this checkpoint, records are the natural fit.