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.