Summer Camp 2026 — Enroll Now APCSA & APCSP Prep Classes Starting June Learn Python, Java & Web Design AP Cybersecurity — New Course Starting from 2026 Expert-Led · Small Batches · Hands-On Projects Contact: oggntech@gmail.com Summer Camp 2026 — Enroll Now APCSA & APCSP Prep Classes Starting June Learn Python, Java & Web Design AP Cybersecurity — New Course Starting from 2026 Expert-Led · Small Batches · Hands-On Projects Contact: oggntech@gmail.com
Study Notes

Unit 3 — Writing Classes

Designing and implementing your own classes: abstraction, constructors, methods, object references, static members, scope, and the this keyword. Each section ends with a short practice set — click "Show answer" to check yourself.

3.1

Abstraction and Program Design

Abstraction means hiding unnecessary detail behind a simple interface. When you call Math.sqrt(x), you don't need to know how square roots are computed internally — you just need to know what to give it and what you get back. Writing your own classes lets you create that same kind of simplicity for problems specific to your program.

  • A well-designed class models one clear concept (a Dog, a BankAccount) and exposes only what other code actually needs.
  • Breaking a large program into classes and methods makes each piece easier to write, test, and reason about on its own.
  • Good program design asks: what data does this concept need to remember (fields), and what can it do (methods)?
Why it matters: abstraction is what lets a team split up work — one person can use a class correctly without ever reading how it's implemented inside.
Practice

1. You're building a library system. What's one concept that deserves its own class?

Show answer
Answers vary, but a strong one is Book — it has clear data (title, author, isCheckedOut) and clear behavior (checkOut(), returnBook()), matching a single real-world concept.

2. Why is it useful that you can call Math.sqrt() without knowing the algorithm it uses internally?

Show answer
It lets you focus on solving your own problem instead of re-deriving square roots — that's abstraction reducing the amount you need to think about at once.
3.2

Impact of Program Design

How you design a class affects more than whether the program works — it affects how easy it is to read, reuse, debug, and extend later.

  • Encapsulation — keeping fields private and only exposing controlled access through methods — protects a class's data from being changed in invalid ways by outside code.
  • Clear, descriptive names for classes, methods, and variables make code self-documenting and reduce bugs caused by misunderstanding what something does.
  • Duplicated logic scattered across a program is harder to fix consistently; a well-placed method or class centralizes it in one place.
Real cost: poor design doesn't usually break a program today — it shows up later as bugs that are hard to trace, or as changes that ripple unpredictably through unrelated code.
Practice

1. A field is declared public and gets changed directly by five different classes. What design problem does this risk?

Show answer
Without encapsulation, any of those five classes could set the field to an invalid value, and there's no single place to enforce rules about what values are allowed — bugs become hard to trace back to their source.

2. Why might copy-pasting the same 10 lines of logic into three methods be a design problem, even if all three work correctly today?

Show answer
If that logic ever needs to change or has a bug, it now has to be fixed in three places instead of one — easy to miss a spot and end up with inconsistent behavior.
3.3

Anatomy of a Class

A class definition typically contains, in order: fields (instance data), one or more constructors, and methods (behavior).

public class Student {
    // fields — instance data, usually private
    private String name;
    private int gradeLevel;

    // constructor
    public Student(String n, int g) {
        name = n;
        gradeLevel = g;
    }

    // methods
    public String getName() {
        return name;
    }

    public void promote() {
        gradeLevel++;
    }
}
PartPurpose
FieldsThe data each object of this class carries
Constructor(s)Set up a new object's initial state
MethodsDefine what the object can do, or what it can report about itself
Practice

1. In the Student class above, which part sets name when a new Student object is first created?

Show answer
The constructor, Student(String n, int g) — it runs once, at creation, and assigns the incoming values to the fields.

2. Why are fields usually declared private rather than public?

Show answer
To enforce encapsulation (3.2) — outside code can only interact with the data through the class's own methods, which can validate or control how it changes.
3.4

Constructors

A constructor is a special method that runs automatically when an object is created with new. It shares its name with the class and has no return type — not even void.

public class Rectangle {
    private double width, height;

    // constructor with parameters
    public Rectangle(double w, double h) {
        width = w;
        height = h;
    }

    // no-argument (default-style) constructor — overloaded
    public Rectangle() {
        width = 1.0;
        height = 1.0;
    }
}

Rectangle r1 = new Rectangle(4, 2);
Rectangle r2 = new Rectangle();   // uses the no-arg version, 1x1
  • A class can have multiple constructors as long as their parameter lists differ — this is overloading, same idea as in 1.9.
  • If you write no constructor at all, Java silently supplies a default no-argument one that does nothing extra. As soon as you write any constructor yourself, that free default disappears.
Practice

1. What's different about a constructor's header compared to a normal method's header?

Show answer
A constructor has no return type at all (not even void), and its name must exactly match the class name.

2. If Rectangle only has the two-argument constructor shown, what happens if you call new Rectangle();?

Show answer
It won't compile — Java only supplies a free no-arg constructor if you write no constructors at all. Once any constructor exists, you must define a no-arg one yourself if you want it.
3.5

Methods: How to Write Them

Writing a method means deciding its signature (1.9) and then implementing the logic that fulfills it — every path through the method must return a value matching its declared return type (unless it's void).

public boolean isPassing(int score) {
    if (score >= 60) {
        return true;
    } else {
        return false;
    }
}

// equivalent, more direct — the comparison IS the boolean
public boolean isPassing(int score) {
    return score >= 60;
}
  • A method with a non-void return type must return a value on every possible path — the compiler rejects a method where some branch falls through without returning.
  • Once a return executes, the method ends immediately — code after it in the same block never runs.
  • Prefer writing the logic as directly as possible; wrapping a boolean comparison in an if/else that returns true/false (like the first version above) is usually unnecessary.
Practice

1. Why won't this compile? public int sign(int n) { if (n > 0) return 1; }

Show answer
Not every path returns a value — if n is not greater than 0, the method falls off the end without returning an int, which the compiler rejects.

2. Rewrite isPassing above as directly as possible for a scale where 50 or above passes.

Show answer
public boolean isPassing(int score) { return score >= 50; }
3.6

Methods: Passing and Returning References of an Object

Recall from 1.13 that object variables hold references, not the object's data directly. When an object is passed as a method argument, the reference is copied — so the method gets its own variable pointing at the same object.

public static void birthday(Student s) {
    s.promote();   // modifies the ORIGINAL object — s points to the same Student
}

Student a = new Student("Ana", 9);
birthday(a);
// a's gradeLevel is now updated — the method changed the real object
  • Because the reference is copied, reassigning the parameter itself (s = new Student(...)) inside the method does not affect the caller's variable — only calling methods or changing fields on the object it points to does.
  • A method can also return a reference to an object, handing back either a brand-new object or an existing one.
public static Student olderOf(Student a, Student b) {
    return (a.getGradeLevel() > b.getGradeLevel()) ? a : b;
}
Practice

1. In the birthday example, why does the original Student object end up changed, even though objects are "passed by value"?

Show answer
The value being passed is the reference itself (an address). Both the caller's variable and the method's parameter point to the same object in memory, so calling a method on that object through either variable affects the one shared object.

2. If a method does s = new Student("New", 6); to its parameter, does that change what the caller's variable points to?

Show answer
No. That reassigns the method's own local copy of the reference to a new object; the caller's original variable still points to the original Student.
3.7

Class Variables and Methods

Fields and methods marked static belong to the class itself, shared by all objects, rather than to any one instance (recall 1.10's static Math calls).

public class Student {
    private String name;
    private static int totalStudents = 0;   // one copy, shared by ALL Students

    public Student(String n) {
        name = n;
        totalStudents++;    // every new Student increments the shared count
    }

    public static int getTotalStudents() {
        return totalStudents;
    }
}

Student a = new Student("Ana");
Student b = new Student("Ben");
System.out.println(Student.getTotalStudents());  // 2
Instance field/methodStatic field/method
Belongs toEach individual objectThe class as a whole
StorageA separate copy per objectOne shared copy, no matter how many objects exist
Called viaobject.method()ClassName.method()
Rule of thumb: if a value belongs to "the class as a concept" rather than to any single object (a running total, a shared constant), make it static.
Practice

1. In the example above, if a third Student is created, what does Student.getTotalStudents() return?

Show answer
3totalStudents is static, so every Student constructor call increments the same shared counter.

2. Why wouldn't it make sense for name in the Student class to be static?

Show answer
Each student needs their own distinct name — a static field would mean all Student objects shared the exact same single name, which defeats the purpose of the field.
3.8

Scope and Access

Scope is where in the code a variable name is visible and usable. Access modifiers control which other classes are allowed to see or use a field or method.

KindScope
Local variableOnly inside the block (method, loop, if) where it's declared
ParameterOnly inside the method it belongs to
Instance fieldAnywhere inside the class, in every method — the whole object's lifetime
ModifierVisible from
privateOnly inside the same class
publicAny class, anywhere
public class Counter {
    private int total;   // instance field: visible in every method below

    public void add(int amount) {
        int doubled = amount * 2;  // local variable: only exists inside add()
        total += doubled;
    }
    // doubled is NOT visible here — its scope already ended
}
Shadowing: if a local variable or parameter has the same name as an instance field, the local one "wins" inside that method — the field is temporarily hidden unless you use this (see 3.9).
Practice

1. In the Counter class above, why can't another method in the class reference doubled?

Show answer
doubled is a local variable declared inside add() — its scope is limited to that method's body, so it doesn't exist anywhere else.

2. Why might you make a field private even in a class you're sure no other class will misuse?

Show answer
Encapsulation isn't just about distrust — it keeps a clear, controlled interface (3.2) and means the internal representation can change later without breaking other code that depends on the class.
3.9

this Keyword

this refers to "the current object" — the specific instance whose method is currently running. It's most often used to distinguish an instance field from a parameter or local variable that shares its name.

public class Student {
    private String name;

    public Student(String name) {   // parameter shadows the field
        this.name = name;      // this.name = the field; name = the parameter
    }

    public boolean hasSameName(Student other) {
        return this.name.equals(other.name);
    }
}
  • Without this, writing name = name; in that constructor would just assign the parameter to itself and leave the field unset.
  • this can also be used to call another constructor in the same class (this(...) as the first line), avoiding duplicated setup code.
  • Using this is optional when there's no naming conflict — this.name and name mean the same thing if no local variable named name exists in that scope.
Practice

1. In the constructor above, what would go wrong if this.name = name; were written as just name = name;?

Show answer
The parameter name shadows the field, so name = name; would just assign the parameter to itself — the instance field would remain unset (still its default value).

2. In hasSameName, why is this.name used but just other.name (no "this") for the other student?

Show answer
this refers to the current object, so this.name means "my own name." other is a different Student object entirely, so its field is accessed directly through its own variable, not through this.