Stack Heap and Class Design
Applied class exercises for week 3, covering 2026-03-12-object-oriented-programming-ii and reinforcing java-mutability (why reference types behave differently from primitives when passed to methods).
On the HTML site, fill in each blank with your answer (as a quoted string, e.g. "1"), then click Run Code to check it. In the PDF, the Working callout is shown as a static answer key instead (interactive checking needs a browser).
Question 1 — Tracing the call stack and heap
Understanding the runtime call stack and object heap clarifies unexpected behaviour of reference types. Consider:
class Counter {
private int number;
public Counter() { this.reset(); }
public void increment(int amount) { this.number += amount; }
public void increment() { this.number++; }
public void reset() { this.number = 0; }
public int getValue() { return this.number; }
}
class DebugMe {
static void f(Counter counter, int param) {
counter.increment();
param++;
}
public static void main(String[] args) {
Counter first = new Counter();
int second = 0;
f(first, second);
System.out.println(first.getValue());
System.out.println(second);
}
}What does main print (first line, then second line)?
First line →
Second line →
State of the call stack/heap just before f finishes executing:
main |
|
|---|---|
args |
→ {} |
first |
→ Counter{number: 1} |
second |
0 |
f |
|
|---|---|
counter |
→ (same Counter object as first) |
param |
1 |
first and counter are two references to the same Counter object on the heap, so counter.increment() inside f is visible through first after f returns — first.getValue() prints 1. But param is a int, passed by value: param++ only changes f’s local copy, not main’s second — so second still prints 0. This is why mutating an object through a reference parameter is visible to the caller, but reassigning/incrementing a primitive parameter is not.
Question 2 — Designing a Vertex class
The Triangle class below represents a triangle as a 2D array of coordinates (vertexes[0] = x-coordinates, vertexes[1] = y-coordinates) — leading to a fiddly implementation:
public class Triangle {
private double[][] vertexes;
public Triangle(double a, double b, double c) {
double dividend = (square(a) - square(b) - square(c));
double cx = dividend / (-2 * c);
double cy = Math.sqrt(square(b) - square(cx));
this.vertexes = new double[][]{
new double[]{0, c, cx},
new double[]{0, 0, cy}
};
}
public double perimeter() {
double x0 = vertexes[0][0], x1 = vertexes[0][1], x2 = vertexes[0][2];
double y0 = vertexes[1][0], y1 = vertexes[1][1], y2 = vertexes[1][2];
return distance(x0, y0, x1, y1) + distance(x1, y1, x2, y2) + distance(x2, y2, x0, y0);
}
public Triangle scale(double multiplier) {
double[][] scaledUp = new double[2][3];
for (int x = 0; x < 2; x++) {
for (int y = 0; y < 3; y++) {
scaledUp[x][y] = vertexes[x][y] * multiplier;
}
}
return new Triangle(scaledUp);
}
}Design a Vertex class (signatures only, no implementation needed) that could simplify this — a triangle is really a collection of three vertices, each with an x and y coordinate.
class Vertex {
Vertex(); // Construct a new vertex at (0, 0)
Vertex(double, double);
double distance(Vertex); // distance to another vertex
Vertex scale(double); // multiply the coordinates by the given amount
}Rewriting Triangle to use three Vertex fields instead of a raw 2D array:
public class Triangle {
private Vertex a;
private Vertex b;
private Vertex c;
public Triangle(double a, double b, double c) {
double dividend = (square(a) - square(b) - square(c));
double cx = dividend / (-2 * c);
double cy = Math.sqrt(square(b) - square(cx));
this.a = new Vertex();
this.b = new Vertex(c, 0);
this.c = new Vertex(cx, cy);
}
public Triangle(Vertex a, Vertex b, Vertex c) {
this.a = a;
this.b = b;
this.c = c;
}
public double perimeter() {
return a.distance(b) + b.distance(c) + c.distance(a);
}
public Triangle scale(double multiplier) {
return new Triangle(a.scale(multiplier), b.scale(multiplier), c.scale(multiplier));
}
}Introducing Vertex moves the x/y bookkeeping into a single-responsibility class, so Triangle’s own methods read as “distance between vertices” and “scale each vertex” rather than juggling raw array indices.
Question 3 — Recursive call stack (bonus)
class Reverser {
static void reverse(int[] arr) {
int n = arr.length;
if (n == 1) {
return;
}
int[] rest = new int[n - 1];
for (int i = 1; i < n; i++) {
rest[i - 1] = arr[i];
}
arr[n - 1] = arr[0];
reverse(rest);
}
public static void main(String[] args) {
int[] numbers = {42, 24, 11};
reverse(numbers);
}
}Sketch the state of the call stack and heap just prior to the third call to reverse.
main |
|
|---|---|
args |
→ {} |
numbers |
→ {42, 24, 42} |
reverse (1st call) |
|
|---|---|
arr |
→ {42, 24, 42} |
n |
3 |
rest |
→ {24, 24} |
reverse (2nd call) |
|
|---|---|
arr |
→ {24, 24} |
n |
2 |
rest |
→ {11} |
Each recursive call copies everything except the first element into a new, smaller array (rest), and overwrites the last slot of the current array with its own first element — so by the time the base case (n == 1) is reached, the original array has been rebuilt back-to-front in place.