CompSci.rocks
FRQcsapa

Pet Hierarchy: 2004 FRQ 2

A step-by-step solution to the 2004 AP CSA FRQ 2 (Pet / Cat / Dog / LoudDog / Kennel), covering subclassing an abstract class, overriding an abstract method, and polymorphism through a shared supertype in Java.

A whole hierarchy of pets that all know how to "speak" drives this AP Computer Science A free-response question — you extend an abstract class two different ways, then write a method that calls each pet's own version of a shared method without ever checking what kind of pet it actually is.

What This FRQ Tests

  • AP CSA units: Unit 9 (Inheritance) and Unit 5 (Writing Classes)
  • Core skill: extending an abstract class and supplying the one method it declares but doesn't implement
  • Secondary skill: polymorphism — calling an overridden method through a reference typed as the abstract supertype and getting the correct subclass's behavior automatically
  • Official category: "Classes" — this happens to be FRQ 2 on the 2004 exam too, matching where "Classes" sits in the fixed order used since the 2019–2020 Course and Exam Description redesign. That's a coincidence worth not over-trusting, though: 2004's own printed order put "Array/ArrayList" at FRQ 1 and "Methods and Control Structures" at FRQ 4 (with a since-retired Marine Biology Simulation question at FRQ 3), so the modern fixed sequence can't be assumed for an exam this old just because one slot happens to line up.

The Setup

  • The class hierarchy: Pet is the abstract root; Cat and Dog both extend Pet directly; LoudDog extends Dog.
  • The given (not written) abstract class Pet:
    • private String myName
    • public Pet(String name) — sets myName
    • public String getName() — returns myName
    • public abstract String speak() — declared, but with no implementation; every concrete subclass must supply its own
  • The given (partial, not written) class Dog extends Pet:
    • public Dog(String name) and public String speak() both exist, with their implementations not shown — treat them as already correct and complete.
  • You're asked to write three things:
    1. A complete Cat class, whose speak() returns "meow"
    2. A complete LoudDog class, whose speak() returns whatever Dog's own speak() returns, repeated twice
    3. The Kennel method allSpeak(), which loops over private ArrayList petList (holding Pet references) and prints each pet's name followed by its speak() result

Part (a): Building the Cat Class

Step-by-Step Approach

  1. Declare the class header as public class Cat extends Pet.
  2. Add no new fields — Cat doesn't need anything beyond what Pet already tracks (the name).
  3. Write a constructor that takes a name and forwards it straight to super(name), since there's no additional state of Cat's own to initialize.
  4. Override the abstract speak() method to return the literal String "meow".

The Code

public class Cat extends Pet
{
    public Cat(String name)
    {
        super(name);
    }

    public String speak()
    {
        return "meow";
    }
}

Why Each Piece Matters

  • super(name)myName is private to Pet, so Cat cannot set it directly; the only way to initialize it is through Pet's own constructor.
  • Overriding speak() is required, not optionalPet declares speak() as abstract, which means any non-abstract subclass (like Cat) must provide a real implementation, or the class won't compile.
  • No new fields — it's completely normal, and expected here, for a subclass to add nothing beyond what its parent already provides.

Common Mistakes to Avoid

  • Forgetting super(name) entirely. Pet has no no-argument constructor, so leaving this out is a compile error.
  • Declaring Cat as abstract instead of implementing speak(). The problem specifically requires calling speak() on a real Cat object, so it must be a concrete, instantiable class.
  • Returning anything other than exactly "meow" — when a literal return value is specified this precisely, match it exactly (no capitalization or punctuation changes).

Part (b): Building the LoudDog Class

Step-by-Step Approach

  1. Declare the class header as public class LoudDog extends Dog — not extends Pet directly, matching "a LoudDog is-a Dog" from the class hierarchy.
  2. Write a constructor that takes a name and forwards it to super(name), which reaches Dog's own constructor.
  3. Override speak(): call the parent's version with super.speak() to get dog-sound, then concatenate it to itself.

The Code

public class LoudDog extends Dog
{
    public LoudDog(String name)
    {
        super(name);
    }

    public String speak()
    {
        return super.speak() + super.speak();
    }
}

Why Each Piece Matters

  • extends Dog, not extends Pet — this is what makes LoudDog automatically inherit Dog's actual speak() behavior (whatever it happens to be) to build on top of, without LoudDog ever needing to know what that behavior is.
  • super.speak() + super.speak(), not some numeric "multiply"String has no built-in repeat operator, so writing the call twice and concatenating the results is the direct way to express "this sound, twice."
  • super(name) reaches Dog's constructor, which (per its own definition) reaches Pet'sLoudDog never touches myName directly, at any point in the chain.

Part (c): Writing allSpeak()

The Rule, Broken Down

  1. For every Pet stored in petList, print exactly one line.
  2. Each line contains that pet's name, followed by the result of calling its own speak() method.

The question doesn't specify an exact separator between the name and the speak result — no comma, no dash is given, just "its name followed by the result of a call to its speak method." A single space between them is the most natural reading, and any reasonable formatting choice matching that description would be correct.

Step-by-Step Approach

  1. Loop over every index of petList.
  2. Get the element and cast it to PetpetList is declared as a plain, non-generic ArrayList, so get(i) returns Object.
  3. Print that pet's getName(), a space, and its speak() result, using println so each pet lands on its own line.

The Code

public void allSpeak()
{
    for (int i = 0; i < petList.size(); i++)
    {
        Pet pet = (Pet) petList.get(i);
        System.out.println(pet.getName() + " " + pet.speak());
    }
}

Why Each Piece Matters

  • (Pet) petList.get(i) — since petList isn't declared with a generic type, get(i) hands back a plain Object; the cast is what makes .getName() and .speak() callable at all.
  • pet.speak(), not "check the type and call the matching subclass logic" — this is the entire point of the hierarchy: because speak() is abstract in Pet and overridden differently in Cat, Dog, and LoudDog, calling pet.speak() through a Pet-typed reference automatically runs whichever version belongs to that object's real class. This is polymorphism, and it's exactly why the original problem specifically warns that solutions which reimplement this dispatch by hand instead of just calling speak() "will not receive full credit" — manually checking types defeats the purpose of making speak() abstract in the first place.
  • getName() before speak() — matches the order stated in the rule ("name followed by ... speak").

Tracing the Example

No numeric worked table is given for this part of the question — the prompt only specifies each class's behavior in words (Cat.speak() returns "meow"; LoudDog.speak() returns whatever Dog's own speak() would return, repeated twice). Using those specified behaviors directly: suppose a Kennel's petList holds a Cat named "Whiskers" and a LoudDog named "Rex", and — since Dog's actual speak() implementation isn't given in the problem — that it would have returned "woof" if Rex were a plain Dog:

Pet in petList getName() speak() Line printed
Cat "Whiskers" "Whiskers" "meow" Whiskers meow
LoudDog "Rex" "Rex" "woof" + "woof" = "woofwoof" Rex woofwoof

Each call to pet.speak() runs the correct override automatically — the loop itself never checks which subclass it's looking at.

Common Mistakes to Avoid

  • Checking instanceof and calling different logic per subclass. This costs credit per the problem's explicit warning, and defeats the entire purpose of overriding speak().
  • Forgetting the (Pet) cast. Since petList is a raw ArrayList, get(i) returns Object, which has neither getName() nor speak() available to call directly.
  • Printing the name and the speak result with two separate print/println calls. The spec calls for exactly one line per Pet, not two.
  • Concatenating pet.getName() + pet.speak() with no separator. Not wrong syntactically, but it runs the two values together unreadably — worth double-checking against "its name followed by the result."

Notes: A Simpler Way to Double a String

For LoudDog.speak(), Java's repeat(int) method on String (added in Java 11) replaces the double super.speak() concatenation:

public String speak()
{
    return super.speak().repeat(2);
}
  • repeat(2) returns the string repeated twice back-to-back, exactly matching "a String containing dog-sound repeated two times."
  • It isn't listed on the AP CSA Java Quick Reference sheet, but that doesn't mean it's disallowed — AP CSA graders accept any correct Java. The only real tradeoff is that its exact behavior isn't there to double-check on the reference sheet if you're unsure how it handles an unusual argument — the super.speak() + super.speak() version in the main solution never raises that question at all.

Key Takeaways

  • A subclass only needs to supply what its parent doesn't already provide — Cat added no new fields at all, and its constructor did nothing but forward to super(...).
  • Calling an abstract method through a reference typed as the supertype (here, Pet) is what makes polymorphism work: the actual object's own override runs automatically, with no type-checking needed.
  • When a collection is declared without a generic type, every value that comes out of get(...) is a plain Object and needs an explicit cast before its real methods become callable.

Related FRQs