StringChecker / CodeWordChecker: 2018 FRQ 3
A step-by-step solution to the 2018 AP CSA FRQ 3 (CodeWordChecker), covering implementing an interface, overloaded constructors, and constructor chaining with this(...) in Java.
Writing an entire class from scratch — not just filling in a single method — is the task in this AP Computer Science A free-response question, built around a StringChecker interface and a CodeWordChecker implementation that needs two different constructors.
What This FRQ Tests
- AP CSA units: Unit 5 (Writing Classes) and Unit 9 (Interfaces)
- Core skill: implementing an interface's method contract, and overloading a constructor so one version calls another with
this(...) - Secondary skill: combining a numeric range check and a substring search into a single boolean expression
- Official category: "Classes," which on the 2018 exam was FRQ 3 (the fixed FRQ 1–4 category order used in more recent years — Methods and Control Structures, Classes, Array/ArrayList, 2D Array, always in that sequence — wasn't standardized until the 2019–2020 Course and Exam Description redesign; 2018's actual printed order was FrogSimulation, WordPair, StringChecker, ArrayTester)
The Setup
- The given
StringCheckerinterface requires exactly one method:boolean isValid(String str)
- You're asked to write the complete
CodeWordCheckerclass, which:- Implements
StringChecker - Can be constructed two different ways:
CodeWordChecker(int min, int max, String forbid)— minimum length, maximum length, and a forbidden substringCodeWordChecker(String forbid)— just a forbidden substring; length bounds default to6and20
- Considers a code word valid only if both:
- Its length is between the minimum and maximum, inclusive
- It does not contain the forbidden string anywhere inside it
- Implements
Writing the CodeWordChecker Class
Step-by-Step Approach
- Declare the class header as
public class CodeWordChecker implements StringChecker— this promises the compiler thatisValidwill be provided, which is what makesStringChecker sc1 = new CodeWordChecker(...)legal. - Add three instance fields to remember the minimum length, maximum length, and forbidden string.
- Write the three-argument constructor, assigning each parameter directly to its matching field.
- Write the one-argument constructor by delegating to the three-argument one with
this(6, 20, forbid), instead of repeating the same three assignment lines. - Write
isValid(String str)as a singlereturnstatement: the length check combined with the "doesn't contain the forbidden string" check, joined by&&.
The Code
public class CodeWordChecker implements StringChecker
{
private int minLength;
private int maxLength;
private String forbidden;
public CodeWordChecker(int min, int max, String forbid)
{
minLength = min;
maxLength = max;
forbidden = forbid;
}
public CodeWordChecker(String forbid)
{
this(6, 20, forbid);
}
public boolean isValid(String str)
{
return str.length() >= minLength && str.length() <= maxLength
&& str.indexOf(forbidden) == -1;
}
}
Why Each Piece Matters
-
implements StringChecker— without it,new CodeWordChecker(...)couldn't be assigned to a variable declared as typeStringChecker, which is exactly how both examples in the problem construct their objects. -
this(6, 20, forbid)— calls the other constructor in the same class instead of duplicating its three assignment lines. If the default bounds ever needed to change, there'd be exactly one line to update. -
str.indexOf(forbidden) == -1—indexOfreturns the position whereforbiddenfirst appears insidestr, or-1if it never appears at all. Checking against-1is the Quick-Reference-sheet way to ask "does this string contain that substring?" (containswould say it more directly, but isn't listed there — see the Notes section below). -
One
returnwith&&, noif/else— sinceisValidalready needs to produce aboolean, the combined condition is the answer; wrapping it in anifthat returnstrueorfalsewould just be longer for the same result.
Tracing the Example
StringChecker sc1 = new CodeWordChecker(5, 8, "$"); — valid code words are 5 to 8 characters, and can't contain "$":
| Call | str.length() |
Length OK (5-8)? | Contains "$"? |
Result |
|---|---|---|---|---|
sc1.isValid("happy") |
5 | yes | no | true |
sc1.isValid("happy$") |
6 | yes | yes | false |
sc1.isValid("Code") |
4 | no (too short) | — | false |
sc1.isValid("happyCode") |
9 | no (too long) | — | false |
StringChecker sc2 = new CodeWordChecker("pass"); — this delegates to this(6, 20, "pass"), so valid code words are 6 to 20 characters and can't contain "pass":
| Call | str.length() |
Length OK (6-20)? | Contains "pass"? |
Result |
|---|---|---|---|---|
sc2.isValid("MyPass") |
6 | yes | no — "pass" is lowercase, "MyPass" has a capital P |
true |
sc2.isValid("Mypassport") |
10 | yes | yes — lowercase "pass" starts at index 2 |
false |
sc2.isValid("happy") |
5 | no (too short) | — | false |
sc2.isValid("1,000,000,000,000,000") |
21 | no (too long) | — | false |
All eight results match the problem's tables exactly — including the subtle "MyPass" case, where indexOf is case-sensitive, so the capitalized "Pass" inside "MyPass" does not count as containing the lowercase "pass".
Common Mistakes to Avoid
-
Forgetting
implements StringChecker. Both examples declare their variable as typeStringChecker— without theimplementsclause, that assignment wouldn't compile. -
Using
>and<instead of>=and<=. The bounds are stated as inclusive ("5 to 8 characters"), so a code word of exactly 5 or exactly 8 characters must still count as valid. -
Getting the default order backwards — writing
this(20, 6, forbid)instead ofthis(6, 20, forbid)swaps the minimum and maximum. -
Re-typing the three assignment lines in the one-argument constructor instead of calling
this(...). It still works, but it means two different places would need updating if the defaults ever changed. -
Assuming
indexOfis case-insensitive."MyPass"does not contain"pass"for exactly this reason — capitalization matters.
Notes: A Method Not on the AP CSA Quick Reference Sheet
String's contains method reads a little more naturally than the indexOf comparison, if your class has covered it:
public boolean isValid(String str)
{
return str.length() >= minLength && str.length() <= maxLength
&& !str.contains(forbidden);
}
contains(forbidden)directly asks "does this string appear insidestr?" — no comparison against-1needed.- It isn't listed on the real exam's Java Quick Reference sheet, but that doesn't mean it's off-limits — AP CSA graders accept any correct Java. The only real tradeoff is not being able to look up its exact behavior on the reference sheet if you second-guess yourself mid-exam, the way you could with
indexOf.
Key Takeaways
- Implementing an interface means supplying every method in its contract with a matching signature — that's what lets an object be stored in a variable of the interface's type.
- When one constructor's job is "the same thing, but with certain values defaulted," call the other constructor with
this(...)instead of duplicating field assignments. indexOf(str) == -1is the Quick-Reference-sheet way to test "does not contain" — remember it's case-sensitive, just like every otherStringcomparison.
Need help preparing for the AP exam?
FRQs are one of the toughest parts of the AP CS exam. I offer 1-on-1 tutoring to help you work through practice problems, tighten up your responses, and build the confidence to earn full credit on exam day.
Book a tutoring session →