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.
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, aBankAccount) 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)?
1. You're building a library system. What's one concept that deserves its own class?
Show answer
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
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.
1. A field is declared public and gets changed directly by five different classes. What design problem does this risk?
Show answer
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
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++; } }
| Part | Purpose |
|---|---|
| Fields | The data each object of this class carries |
| Constructor(s) | Set up a new object's initial state |
| Methods | Define what the object can do, or what it can report about itself |
1. In the Student class above, which part sets name when a new Student object is first created?
Show answer
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
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.
1. What's different about a constructor's header compared to a normal method's header?
Show answer
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
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
returnexecutes, 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.
1. Why won't this compile? public int sign(int n) { if (n > 0) return 1; }
Show answer
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; }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; }
1. In the birthday example, why does the original Student object end up changed, even though objects are "passed by value"?
Show answer
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
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/method | Static field/method | |
|---|---|---|
| Belongs to | Each individual object | The class as a whole |
| Storage | A separate copy per object | One shared copy, no matter how many objects exist |
| Called via | object.method() | ClassName.method() |
1. In the example above, if a third Student is created, what does Student.getTotalStudents() return?
Show answer
3 — totalStudents 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
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.
| Kind | Scope |
|---|---|
| Local variable | Only inside the block (method, loop, if) where it's declared |
| Parameter | Only inside the method it belongs to |
| Instance field | Anywhere inside the class, in every method — the whole object's lifetime |
| Modifier | Visible from |
|---|---|
private | Only inside the same class |
public | Any 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 }
this (see 3.9).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
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, writingname = name;in that constructor would just assign the parameter to itself and leave the field unset. thiscan also be used to call another constructor in the same class (this(...)as the first line), avoiding duplicated setup code.- Using
thisis optional when there's no naming conflict —this.nameandnamemean the same thing if no local variable namednameexists in that scope.
1. In the constructor above, what would go wrong if this.name = name; were written as just name = name;?
Show answer
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.