CompSci.rocks
FRQcsapa

Advance / StudentAdvance: 2005 FRQ 2

A step-by-step solution to the 2005 AP CSA FRQ 2 (Ticket/Advance/StudentAdvance), covering inheritance, overriding getPrice() and toString(), and polymorphic method dispatch in Java.

A three-level class hierarchy for theater tickets is the backdrop for this AP Computer Science A free-response question — you write two subclasses that each override just enough behavior to get a different price and a different printed ticket, while reusing everything else from their parent.

What This FRQ Tests

  • AP CSA units: Unit 9 (Inheritance) and Unit 5 (Writing Classes)
  • Core skill: extending a class, overriding an abstract method, and calling super.getPrice() / super.toString() to build on inherited behavior instead of duplicating it
  • Secondary skill: recognizing that a method called from inside an inherited method (like getPrice() being called from Ticket's toString()) still dispatches to the most specific override at runtime — this is what lets StudentAdvance print its halved price without Ticket's toString() ever being rewritten
  • Official category: "Classes" — this one does match the modern FRQ 2 slot (Classes/Inheritance), and in this case the 2005 exam's own printed "2." happens to agree. Treat that as this particular year's outcome rather than a guaranteed rule for every pre-2019 exam — the fixed FRQ-number-to-category ordering wasn't standardized until the 2019-2020 Course and Exam Description redesign.

The Setup

  • The given Ticket class (abstract, not to modify) provides:
    • private int serialNumber — assigned automatically in the constructor
    • public Ticket() — sets serialNumber using an already-implemented helper
    • public abstract double getPrice() — every subclass must implement this
    • public String toString() — already implemented as "Number: " + serialNumber + "\nPrice: " + getPrice()
  • Walkup (given, described only — you don't write it) extends Ticket and always costs a flat $50.
  • You're asked to write two complete class declarations:
    • Advance extends Ticket — constructor takes daysInAdvance; costs $30 if purchased 10 or more days ahead, $40 otherwise
    • StudentAdvance extends Advance — constructor also takes daysInAdvance; always costs half of whatever the equivalent Advance ticket would cost, and its toString() adds a "(student ID required)" line
  • The catch for StudentAdvance: it has to keep computing the correct halved price even if Advance's pricing rule changes later, without touching StudentAdvance's own code.

Part (a): Building the Advance Class

The Rule, Broken Down

  1. Advance needs one new piece of information: how many days in advance it was purchased.
  2. If that's 10 or more, the price is $30; otherwise, it's $40.
  3. Advance doesn't need its own toString() — the inherited one from Ticket already produces "Number: <serialNumber>\nPrice: <price>", which matches the sample output exactly.

Step-by-Step Approach

  1. Declare Advance as public class Advance extends Ticket.
  2. Give it one field to hold the computed price (there's no need to keep daysInAdvance itself around once the price is decided).
  3. In the constructor, check daysInAdvance against the 10-day cutoff and store the resulting price.
  4. Implement the required abstract method, getPrice(), returning that stored price.

The Code

public class Advance extends Ticket
{
    private double price;

    public Advance(int daysInAdvance)
    {
        if (daysInAdvance >= 10)
        {
            price = 30;
        }
        else
        {
            price = 40;
        }
    }

    public double getPrice()
    {
        return price;
    }
}

Why Each Piece Matters

  • No explicit super(...) call is needed. Java automatically inserts a call to the no-argument Ticket() constructor at the start of Advance's constructor, which is exactly what assigns serialNumber.
  • price is computed once, in the constructor — since an Advance ticket's price never changes after it's purchased, there's no reason to recompute the if/else every time getPrice() is called.
  • daysInAdvance >= 10, not > 10 — the rule specifically says "ten or more days," so exactly 10 days must land in the cheaper bracket.
  • No toString() override hereTicket's existing toString() already calls getPrice(), which now correctly returns Advance's price. Rewriting the formatting would just duplicate logic that already works.

Tracing the Example

Using the question's own sample output for Advance, "Number: 357\nPrice: 40" — a ticket bought fewer than 10 days ahead:

  • new Advance(5)5 >= 10 is falseprice = 40
  • .toString() (inherited from Ticket) → "Number: " + serialNumber + "\nPrice: " + getPrice()"Number: 357\nPrice: 40"

The serial number itself (357) comes from Ticket's own serial-number generator, which isn't something this solution controls or needs to reproduce — only the Price: 40 portion is being verified here, and it matches.

Common Mistakes to Avoid

  • Using > 10 instead of >= 10. A ticket bought exactly 10 days ahead must get the $30 price, not the $40 one.
  • Overriding toString() unnecessarily. It works without doing anything wrong, but it's extra code that duplicates what Ticket already provides — and it isn't needed to match the sample output.
  • Storing daysInAdvance and recomputing the price inside getPrice() every call. Not incorrect, just more work than needed, since the price never changes after construction.

Part (b): Building the StudentAdvance Class

The Rule, Broken Down

  1. StudentAdvance extends Advance, not Ticket directly — it inherits Advance's pricing rule as its starting point.
  2. Its price is always half of what the same daysInAdvance would produce as a plain Advance ticket.
  3. Its toString() needs everything Advance's version already prints, plus one extra line: "(student ID required)".
  4. The halving must be based on calling Advance's actual pricing logic — not on rewriting the $30/$40 rule a second time — so that a future change to Advance's prices automatically flows through.

Step-by-Step Approach

  1. Declare public class StudentAdvance extends Advance.
  2. Write a constructor that takes daysInAdvance and forwards it with super(daysInAdvance) — this is required here, since Advance has no no-argument constructor for Java to call automatically.
  3. Override getPrice() to return super.getPrice() / 2 — calling the parent's version instead of re-deriving the $30/$40 rule.
  4. Override toString() to return super.toString() + "\n(student ID required)".

The Code

public class StudentAdvance extends Advance
{
    public StudentAdvance(int daysInAdvance)
    {
        super(daysInAdvance);
    }

    public double getPrice()
    {
        return super.getPrice() / 2;
    }

    public String toString()
    {
        return super.toString() + "\n(student ID required)";
    }
}

Why Each Piece Matters

  • super(daysInAdvance) is mandatory here, unlike in Advance. Advance only has a one-argument constructor, so Java has no no-argument version to call automatically — leaving this out would fail to compile.
  • super.getPrice() / 2 — this calls Advance's own pricing logic (the $30/$40 rule) and halves that result, rather than repeating the day-count comparison inside StudentAdvance. If the $30/$40 amounts ever changed inside Advance, StudentAdvance would automatically charge half of the new amounts with no code changes of its own — exactly what the problem requires.
  • super.toString() inside the override — reuses Advance's (really Ticket's) existing "Number: ...\nPrice: ..." formatting instead of retyping it, then appends the one new line this class needs.
  • The polymorphism underneath all of this: Ticket.toString() calls getPrice() on this. Since this is actually a StudentAdvance object, that call dispatches to StudentAdvance's overridden getPrice() — even though the call is physically written inside Ticket's code. That's the only reason the printed price comes out halved without Ticket.toString() ever needing to know StudentAdvance exists.

Tracing the Example

Using the question's own sample output for StudentAdvance, "Number: 134\nPrice: 15\n(student ID required)" — which implies the underlying Advance price was $30 (half of it is $15), i.e. purchased 10 or more days ahead:

Step Call Result
1 new StudentAdvance(12) super(12)Advance's price field is set to 30
2 .getPrice() super.getPrice() returns 30, halved to 15
3 .toString() super.toString()Ticket.toString() runs, calling getPrice() on this (a StudentAdvance) → gets 15 → produces "Number: 134\nPrice: 15"
4 (still in toString()) append "\n(student ID required)" → final result "Number: 134\nPrice: 15\n(student ID required)"

That matches the sample exactly — again, the serial number 134 is whatever Ticket's generator assigned and isn't something this solution computes.

Common Mistakes to Avoid

  • Forgetting super(daysInAdvance) in the constructor. Since Advance has no no-argument constructor, omitting this line is a compile error, not just a logic bug.
  • Recomputing the $30/$40 rule inside StudentAdvance instead of calling super.getPrice(). This would produce the same answer today, but breaks the problem's explicit requirement that a future pricing change in Advance should "just work" for StudentAdvance with no code modifications.
  • Rewriting the full ticket text in toString() instead of calling super.toString() first. It's easy to accidentally drop the "Number:" line or introduce a formatting mismatch by retyping it from scratch.
  • Appending "(student ID required)" without the leading "\n", which would jam it onto the same line as the price instead of putting it on its own line as the sample shows.

Key Takeaways

  • Calling super.methodName() and building on the result — rather than reimplementing that logic — is what keeps a subclass automatically correct if the parent's rule ever changes.
  • A subclass only needs an explicit super(...) call when the parent has no no-argument constructor; otherwise Java inserts one automatically.
  • A method invoked from inside inherited code still resolves to the most specific override on the actual object at runtime — this is the mechanism that lets one small override (getPrice()) silently change the behavior of a much bigger inherited method (toString()).

Related FRQs