CompSci.rocks
FRQcsapa

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 StringChecker interface requires exactly one method:
    • boolean isValid(String str)
  • You're asked to write the complete CodeWordChecker class, which:
    • Implements StringChecker
    • Can be constructed two different ways:
      • CodeWordChecker(int min, int max, String forbid) — minimum length, maximum length, and a forbidden substring
      • CodeWordChecker(String forbid) — just a forbidden substring; length bounds default to 6 and 20
    • Considers a code word valid only if both:
      1. Its length is between the minimum and maximum, inclusive
      2. It does not contain the forbidden string anywhere inside it

Writing the CodeWordChecker Class

Step-by-Step Approach

  1. Declare the class header as public class CodeWordChecker implements StringChecker — this promises the compiler that isValid will be provided, which is what makes StringChecker sc1 = new CodeWordChecker(...) legal.
  2. Add three instance fields to remember the minimum length, maximum length, and forbidden string.
  3. Write the three-argument constructor, assigning each parameter directly to its matching field.
  4. 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.
  5. Write isValid(String str) as a single return statement: 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 type StringChecker, 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) == -1indexOf returns the position where forbidden first appears inside str, or -1 if it never appears at all. Checking against -1 is the Quick-Reference-sheet way to ask "does this string contain that substring?" (contains would say it more directly, but isn't listed there — see the Notes section below).
  • One return with &&, no if/else — since isValid already needs to produce a boolean, the combined condition is the answer; wrapping it in an if that returns true or false would 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 type StringChecker — without the implements clause, 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 of this(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 indexOf is 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 inside str?" — no comparison against -1 needed.
  • 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) == -1 is the Quick-Reference-sheet way to test "does not contain" — remember it's case-sensitive, just like every other String comparison.

Related FRQs