AP CSA Unit 3 Class Creation: Building Objects with Constructors and Methods

Constructors

A constructor is a special block of code inside a class that runs when you create a new object from that class. Its job is to put the object into a valid starting state—usually by assigning values to the object’s instance variables.

If you think of a class as a blueprint for making “things,” the constructor is the part of the blueprint that tells you how to assemble a brand-new thing. Without it, an object might exist but have meaningless or unintended initial values.

What makes a constructor different from a method?

Constructors look like methods, but they have a few rules that make them unique:

  • A constructor’s name must match the class name exactly.
  • A constructor has no return type at all—not even void.
  • Constructors run automatically when you use new.

Here’s a comparison that helps students avoid mixing them up:

FeatureConstructor“Regular” method
NameSame as classAny valid identifier
Return typeNoneRequired (void or a type like int)
When it runsAutomatically on newOnly when called
Main purposeInitialize object statePerform actions / compute results

Creating objects: when constructors run

When you write something like:

Student s = new Student("Ava", 11);

three important things happen conceptually:

  1. Memory is allocated for a new Student object.
  2. The Student constructor you matched by parameters is invoked.
  3. The reference variable s is set to refer to that new object.

The constructor is where you typically assign instance variables so the object is immediately usable.

Instance variables, parameters, and the this keyword

Inside a constructor, you usually assign values into instance variables (fields). A common style is to use parameter names that match field names, but that creates a name conflict called shadowing: the parameter hides the instance variable.

That’s why Java provides this, which means “this current object.” Using this.fieldName makes it unambiguous that you mean the instance variable.

public class Student {
    private String name;
    private int gradeLevel;

    public Student(String name, int gradeLevel) {
        this.name = name;              // instance variable = parameter
        this.gradeLevel = gradeLevel;  // instance variable = parameter
    }
}

If you forget this in the common shadowing situation:

public Student(String name, int gradeLevel) {
    name = name;            // BUG: assigns parameter to itself
    gradeLevel = gradeLevel; // BUG: assigns parameter to itself
}

The instance variables never change, so the object keeps default values (null for String, 0 for int, etc.). This is one of the most common constructor bugs.

Default initialization vs. your own initialization

If you don’t assign an instance variable yourself, Java gives it a default value:

  • Numeric primitives (int, double): 0 or 0.0
  • boolean: false
  • Reference types (like String): null

Those defaults are not “smart” values; they’re just placeholders. Constructors exist so you can set meaningful starting values (for example, a bank account balance starting at 0 but with a real account owner name).

The no-argument constructor and when it exists

A no-argument constructor (often called a “no-arg constructor”) is a constructor with no parameters:

public Student() {
    name = "Unknown";
    gradeLevel = 9;
}

Important AP CSA rule: if you write no constructors at all, Java provides an automatic no-arg constructor for you. But if you write any constructor, Java does not automatically generate a no-arg constructor; you must write it yourself if you want it.

This comes up in both multiple-choice and free-response: code that used to compile stops compiling after a constructor is added, because something still tries to call new ClassName().

Constructor overloading

Overloading means you have multiple constructors with the same name (the class name) but different parameter lists.

Overloading matters because it lets you create objects in different valid ways depending on what information you have.

Example:

public class Student {
    private String name;
    private int gradeLevel;

    public Student(String name, int gradeLevel) {
        this.name = name;
        this.gradeLevel = gradeLevel;
    }

    public Student(String name) {
        this.name = name;
        this.gradeLevel = 9; // default
    }

    public Student() {
        this.name = "Unknown";
        this.gradeLevel = 9;
    }
}

When you do new Student("Ava"), Java chooses the constructor whose parameter list best matches (String).

Calling another constructor with this(...)

Inside a constructor, you can call another constructor in the same class using this(...). This is often used to avoid repeating initialization logic.

Key rule: if you use this(...), it must be the first statement in the constructor.

public class Student {
    private String name;
    private int gradeLevel;

    public Student(String name, int gradeLevel) {
        this.name = name;
        this.gradeLevel = gradeLevel;
    }

    public Student(String name) {
        this(name, 9); // must be first statement
    }

    public Student() {
        this("Unknown", 9);
    }
}

This pattern is common on the AP exam because it tests whether you understand constructor chaining and parameter matching.

Worked example: constructor effects

Consider:

public class Counter {
    private int value;

    public Counter() {
        value = 0;
    }

    public Counter(int start) {
        value = start;
    }

    public int getValue() {
        return value;
    }
}

If you run:

Counter a = new Counter();
Counter b = new Counter(5);
System.out.println(a.getValue());
System.out.println(b.getValue());

Expected output:

0
5

The main idea is that each constructor sets up a different initial state, but after construction both objects behave the same way through their methods.

Exam Focus
  • Typical question patterns
    • You’re shown a class with instance variables and asked to write a constructor that correctly initializes them from parameters (often requiring this).
    • You’re asked which constructor overload is called for a given new ClassName(...) expression.
    • You’re asked why code no longer compiles after adding a constructor (missing no-arg constructor is a frequent reason).
  • Common mistakes
    • Writing a return type on a constructor (even void)—that turns it into a method and breaks construction.
    • Forgetting this when parameters shadow instance variables, leading to assignments that do nothing.
    • Using this(...) but not placing it as the first statement in the constructor.

Writing Methods

A method is a named block of code inside a class that represents a behavior—something an object can do, or a calculation it can perform. While constructors create and initialize objects, methods are how you use those objects after they exist.

A class without methods is like a device with no buttons: it may store data, but you can’t do much with it in a controlled way.

Anatomy of a method: signature, parameters, and return type

A method definition usually includes:

  • An access modifier (commonly public in AP CSA questions)
  • A return type (void if it returns nothing)
  • A method name
  • A parameter list (possibly empty)
  • A method body (the code)

Example:

public int addPoints(int points) {
    score = score + points;
    return score;
}

Here:

  • public means it can be called from outside the class.
  • int means it must return an integer.
  • addPoints is the name.
  • points is a parameter (an input).
  • The method both updates state and returns a value.

A helpful way to think about parameters: they are like “slots” the caller must fill with actual values (arguments) when calling the method.

Instance variables vs. local variables

Inside methods, you’ll work with two major kinds of variables:

  • Instance variables: declared in the class, outside methods; they represent the object’s stored state.
  • Local variables: declared inside a method (including parameters); they exist only while the method runs.

Example:

public class GameScore {
    private int score; // instance variable

    public void bonusRound() {
        int bonus = 10; // local variable
        score = score + bonus;
    }
}

The local variable bonus disappears after bonusRound finishes. The instance variable score remains stored in the object.

A frequent confusion: students sometimes think a parameter becomes “stored” in the object automatically. It does not. If you want to keep input values, you must assign them into instance variables.

How methods are called (dot notation)

You usually call an instance method on an object using the dot operator:

GameScore gs = new GameScore();
gs.bonusRound();

The object before the dot is the “receiver” of the method call. Inside the method, this refers to that receiver object.

If you have two objects, calling the same method on each affects each one’s own instance variables—not the other’s.

void methods vs. non-void methods

Methods fall into two broad categories:

  • void methods do something but don’t return a result.
  • Non-void methods compute something and return a value.

Examples:

public void reset() {
    score = 0;
}

public int getScore() {
    return score;
}

Common error: trying to print the result of a void method as if it returned a value:

System.out.println(gs.reset()); // does not compile

To use a void method, you call it as a statement by itself:

gs.reset();
System.out.println(gs.getScore());

Return statements: what they do and where they go

A return statement ends the method immediately and (for non-void) provides the value that gets sent back to the caller.

  • In a non-void method, every possible path must return a value.
  • In a void method, you can use return; to exit early, but you cannot return a value.

Example with early return:

public boolean isPassing(int grade) {
    if (grade < 0 || grade > 100) {
        return false;
    }
    return grade >= 60;
}

Designing method behavior: update state vs. compute without changing state

When you write methods, decide whether the method should:

  • Mutate (change) the object’s instance variables, or
  • Compute a value without changing the object.

This is an important design skill: it affects how predictable and safe your class is to use.

Example of mutation:

public void deposit(double amount) {
    balance = balance + amount;
}

Example of non-mutating computation:

public double projectedBalance(double interestRate) {
    return balance * (1 + interestRate);
}

AP questions sometimes test whether you understand that calling a method may or may not change object state.

Method overloading (same name, different parameters)

Just like constructors, methods can be overloaded: same method name, different parameter lists.

public int area(int side) {
    return side * side;
}

public int area(int length, int width) {
    return length * width;
}

Java chooses the correct overload based on the number and types of arguments.

Common mistake: thinking the return type alone can distinguish overloads. It cannot. These two cannot both exist:

public int f() { return 1; }
public double f() { return 1.0; } // not allowed

Worked example: writing a method that maintains an invariant

In object-oriented design, an invariant is a rule that should always be true about an object’s state. For example: “a bank balance should never be negative.”

Here is a class that enforces that invariant:

public class BankAccount {
    private String owner;
    private double balance;

    public BankAccount(String owner, double initialDeposit) {
        this.owner = owner;
        if (initialDeposit < 0) {
            balance = 0;
        } else {
            balance = initialDeposit;
        }
    }

    public boolean withdraw(double amount) {
        if (amount <= 0) {
            return false;
        }
        if (amount > balance) {
            return false;
        }
        balance = balance - amount;
        return true;
    }
}

Why this is “good method writing” for AP CSA:

  • It uses clear parameters and a meaningful return type.
  • It updates an instance variable only after checking conditions.
  • It communicates success/failure with boolean.

If you call:

BankAccount acct = new BankAccount("Sam", 50.0);
System.out.println(acct.withdraw(70.0));
System.out.println(acct.withdraw(20.0));

Expected output:

false
true
Exam Focus
  • Typical question patterns
    • Write a method from a description: given instance variables and a specification, implement the method body (often includes conditionals and arithmetic).
    • Trace code: determine what a method returns and how it changes instance variables after a sequence of calls.
    • Identify compilation errors: mismatch between return type and return statements, or calling a void method in an expression.
  • Common mistakes
    • Declaring a non-void return type but forgetting to return a value on all paths.
    • Using local variables when you intended to update instance variables (or vice versa).
    • Assuming a method call changes state when it only returns a computed value, or assuming it doesn’t change state when it actually mutates fields.

Accessor Methods

An accessor method (often called a “getter”) is a method whose purpose is to provide access to an object’s data without giving direct access to the instance variables themselves.

This idea is tightly connected to encapsulation: instance variables are often private, meaning outside code cannot access them directly. Instead, outside code uses public methods to interact with the object in controlled ways.

If you picture an object like a vending machine, the instance variables are the internal gears and inventory counts (hidden), while accessors are the display window that lets you see relevant information safely.

Why accessors matter: controlling access and protecting invariants

If you make fields public, any code anywhere could assign illegal values:

public double balance; // risky design

Then someone could do:

acct.balance = -1000000;

Accessor methods support a safer pattern:

  • Keep fields private.
  • Expose read-only information through accessors.
  • Use mutator methods (setters or other actions) to change state with checks.

Even when the AP question doesn’t explicitly say “encapsulation,” it often assumes this design: private fields with public accessors.

The shape of an accessor (and how to recognize one)

An accessor typically:

  • Is public
  • Has a non-void return type
  • Takes no parameters (most of the time)
  • Returns an instance variable (or a computed value based on them)
  • Does not change object state

Example:

public class Student {
    private String name;
    private int gradeLevel;

    public Student(String name, int gradeLevel) {
        this.name = name;
        this.gradeLevel = gradeLevel;
    }

    public String getName() {
        return name;
    }

    public int getGradeLevel() {
        return gradeLevel;
    }
}

Notice that the method names communicate intent: getName reads a value.

Accessors can return computed information

Not every accessor returns a field directly. Some return a value derived from the state, still without mutating it.

public boolean isUpperclassman() {
    return gradeLevel >= 11;
}

This is still an accessor in the sense that it exposes information about the object.

Accessors and reference types: the “representation exposure” problem

In AP CSA, you’ll often work with reference types (like String, arrays, or objects). A key subtlety is that returning a reference can sometimes allow outside code to modify your internal data.

  • String is immutable, so returning a String field is generally safe.
  • Arrays are mutable, so returning an array field directly can be risky because outside code could change its contents.

Example of risky accessor if you had an array field:

public int[] getScores() {
    return scores; // outside code can modify scores[0], etc.
}

In many advanced Java courses, you would return a copy instead. In AP CSA, you mainly need to recognize that returning a reference gives access to the same underlying object.

Accessors vs. mutators (getters vs. setters)

It helps to clearly separate two roles:

Type of methodCommon nameTypical effect
Accessorgetterreturns information; usually no state change
Mutatorsetter or action methodchanges instance variables (often with validation)

AP questions frequently mix these together in a class and ask you to implement one of them correctly. A common error is accidentally changing state in a method that’s supposed to be a “getter.”

Worked example: accessor methods used by client code

Consider this Rectangle class:

public class Rectangle {
    private int width;
    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }

    public int area() {
        return width * height;
    }
}

Client code might do:

Rectangle r = new Rectangle(3, 4);
System.out.println(r.getWidth());
System.out.println(r.getHeight());
System.out.println(r.area());

Expected output:

3
4
12

Two important observations:

  1. The client cannot do r.width because width is private.
  2. The accessors provide controlled visibility into the object.

Common accessor pitfalls

Students often lose points on FRQs due to small but critical issues:

  • Returning the wrong variable (for example, returning a parameter or local variable that isn’t the field).
  • Accidentally creating infinite recursion by calling the accessor inside itself:
public int getWidth() {
    return getWidth(); // BUG: calls itself forever
}
  • Printing instead of returning:
public int getWidth() {
    System.out.println(width);
    // BUG: no return value
}

Accessors are about returning values to the caller, not producing output.

Exam Focus
  • Typical question patterns
    • Implement one or more getters for private instance variables following a given class design.
    • Determine what gets printed after calls to accessors and other methods (understanding that getters usually don’t change state).
    • Write a computed accessor (for example, isEligible(), getAverage()) that returns a value derived from fields.
  • Common mistakes
    • Changing an instance variable inside an accessor that should be read-only.
    • Confusing printing with returning (especially in questions that ask “what does this method return?”).
    • Writing an accessor that mistakenly calls itself (recursion) instead of returning the field.