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:
Petis the abstract root;CatandDogboth extendPetdirectly;LoudDogextendsDog. - The given (not written) abstract class
Pet:private String myNamepublic Pet(String name)— setsmyNamepublic String getName()— returnsmyNamepublic 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)andpublic String speak()both exist, with their implementations not shown — treat them as already correct and complete.
- You're asked to write three things:
- A complete
Catclass, whosespeak()returns"meow" - A complete
LoudDogclass, whosespeak()returns whateverDog's ownspeak()returns, repeated twice - The
KennelmethodallSpeak(), which loops overprivate ArrayList petList(holdingPetreferences) and prints each pet's name followed by itsspeak()result
- A complete
Part (a): Building the Cat Class
Step-by-Step Approach
- Declare the class header as
public class Cat extends Pet. - Add no new fields —
Catdoesn't need anything beyond whatPetalready tracks (the name). - Write a constructor that takes a name and forwards it straight to
super(name), since there's no additional state ofCat's own to initialize. - Override the abstract
speak()method to return the literalString"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)—myNameisprivatetoPet, soCatcannot set it directly; the only way to initialize it is throughPet's own constructor.- Overriding
speak()is required, not optional —Petdeclaresspeak()asabstract, which means any non-abstract subclass (likeCat) 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.Pethas no no-argument constructor, so leaving this out is a compile error. - Declaring
Catasabstractinstead of implementingspeak(). The problem specifically requires callingspeak()on a realCatobject, 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
- Declare the class header as
public class LoudDog extends Dog— notextends Petdirectly, matching "aLoudDogis-aDog" from the class hierarchy. - Write a constructor that takes a name and forwards it to
super(name), which reachesDog's own constructor. - Override
speak(): call the parent's version withsuper.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, notextends Pet— this is what makesLoudDogautomatically inheritDog's actualspeak()behavior (whatever it happens to be) to build on top of, withoutLoudDogever needing to know what that behavior is.super.speak() + super.speak(), not some numeric "multiply" —Stringhas 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)reachesDog's constructor, which (per its own definition) reachesPet's —LoudDognever touchesmyNamedirectly, at any point in the chain.
Part (c): Writing allSpeak()
The Rule, Broken Down
- For every
Petstored inpetList, print exactly one line. - 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
- Loop over every index of
petList. - Get the element and cast it to
Pet—petListis declared as a plain, non-genericArrayList, soget(i)returnsObject. - Print that pet's
getName(), a space, and itsspeak()result, usingprintlnso 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)— sincepetListisn't declared with a generic type,get(i)hands back a plainObject; 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: becausespeak()isabstractinPetand overridden differently inCat,Dog, andLoudDog, callingpet.speak()through aPet-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 callingspeak()"will not receive full credit" — manually checking types defeats the purpose of makingspeak()abstract in the first place.getName()beforespeak()— 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
instanceofand calling different logic per subclass. This costs credit per the problem's explicit warning, and defeats the entire purpose of overridingspeak(). - Forgetting the
(Pet)cast. SincepetListis a rawArrayList,get(i)returnsObject, which has neithergetName()norspeak()available to call directly. - Printing the name and the speak result with two separate
print/printlncalls. The spec calls for exactly one line perPet, 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 "aStringcontaining 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 —
Catadded no new fields at all, and its constructor did nothing but forward tosuper(...). - 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 plainObjectand needs an explicit cast before its real methods become callable.