Popcorn Hack #1: Stack vs Heap

Run the following code in your own notebook and observe:

public class MemoryDemo {
    public static void main(String[] args) {
        // Stack variables
        int a = 10;
        int b = a;  // Copy of value
        b = 20;     // Changing b doesn't affect a
        
        System.out.println("Primitives (Stack):");
        System.out.println("a = " + a);  // Still 10
        System.out.println("b = " + b);  // Now it's 20
        
        // Heap variables
        int[] array1 = {1, 2, 3};
        int[] array2 = array1;  // Copy of reference (address)
        array2[0] = 99;         // Changing array2 DOES affect array1
        
        System.out.println("\nArrays (Heap):");
        System.out.println("array1[0] = " + array1[0]);  // Now it's 99!
        System.out.println("array2[0] = " + array2[0]);  // Also 99
    }
}

MemoryDemo.main(null);
Primitives (Stack):
a = 10
b = 20

Arrays (Heap):
array1[0] = 99
array2[0] = 99

Tasks:

Answer in a few sentences in your notebook

  1. Why does changing b not affect a, but changing array2 affects array1?

Changing b doesn’t affect a because a and b are primitive variables (type int), which store their actual values directly on the stack. When you assign b = a, Java copies the value 10 into b, so the two variables are independent. However, array1 and array2 are reference variables that both point to the same array object in the heap. When you change an element through array2, you’re modifying that shared array, so array1 sees the same change.

  1. Describe what’s on the stack vs. the heap for this code.

On the stack, you have the local variables: a, b, array1, and array2. The primitives (a, b) store actual numbers, while the reference variables (array1, array2) store memory addresses (pointers) to the same array object. On the heap, there’s one array object {99, 2, 3} that both array1 and array2 reference.

Popcorn Hack #2: Understanding Pass-by-Reference

Examine the code below and predict the output before running it:

public class PersonDemo {
    static class Person {
        String name;
        int age;
        
        Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    public static void haveBirthday(Person p) {
        p.age = p.age + 1;  // Modifying object content
        System.out.println("Inside method: " + p.name + " is now " + p.age);
    }
    
    public static void reassignPerson(Person p) {
        p = new Person("New Person", 99);  // Reassigning reference
        System.out.println("Inside reassign: " + p.name + " is " + p.age);
    }
    
    public static void main(String[] args) {
        Person john = new Person("John", 20);
        
        System.out.println("Before birthday: " + john.name + " is " + john.age);
        haveBirthday(john);
        System.out.println("After birthday: " + john.name + " is " + john.age);
        
        System.out.println("\nBefore reassign: " + john.name + " is " + john.age);
        reassignPerson(john);
        System.out.println("After reassign: " + john.name + " is " + john.age);
    }
}

PersonDemo.main(null);
Before birthday: John is 20
Inside method: John is now 21
After birthday: John is 21

Before reassign: John is 21
Inside reassign: New Person is 99
After reassign: John is 21

Questions:

  1. After haveBirthday(john) is called, what is John’s age? Why?

After haveBirthday(john) is called, John’s age becomes 21. This happens because the method receives a copy of the reference to the same Person object in memory. When the method changes p.age, it’s modifying the actual object stored on the heap, so the change is visible outside the method.

  1. After reassignPerson(john) is called, what is John’s name and age? Why?

After reassignPerson(john) is called, John’s name and age remain John, 21. Inside the method, p is reassigned to a new Person object, but that reassignment only changes the local copy of the reference — it doesn’t affect the original john variable in main.

  1. Explain the difference between modifying an object’s contents vs. reassigning a reference.

Modifying an object’s contents changes the data stored in the same heap object that all references point to. Reassigning a reference, on the other hand, just makes the local variable point to a different object, without changing what the original reference (like john) points to.

// Homework Hack #1: Object Creation Practice

public class ObjectCreation {
    public static void main(String[] args) {
        // 1. Create two Car objects using 'new'
        // Example: Car car1 = new Car("Tesla", 2024);
        Car car1 = new Car("Toyota", 2020);
        Car car2 = new Car("Ford", 2018);

        // 2. Print each car's info
        // Example: System.out.println(car1);
        System.out.println(car1);
        System.out.println(car2);
    }
}

class Car {
    // 1. Declare variables: brand, year
    String brand;
    int year;

    // 2. Create a constructor to set those variables
    Car(String brand, int year) {
        this.brand = brand;
        this.year = year;
    }

    // 3. Add a method or toString() to display car info
    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", year=" + year +
                '}';
    }
}

ObjectCreation.main(null);

Car{brand='Toyota', year=2020}
Car{brand='Ford', year=2018}

Homework Hack #2 — Heap vs Stack Storage Demo

Goal - Understand where data is stored in memory — stack vs heap.
Instructions:

  • Create a Book class with one variable: String title.
  • In main(), create:
  • A primitive variable (e.g. int pages = 300;)
  • A Book object (e.g. Book b1 = new Book(“Java Basics”);)
  • Copy both into new variables (int pagesCopy = pages;, Book b2 = b1;)
    Change the original values and print everything — watch what changes.
// Homework Hack #2: Heap vs Stack Storage Demo

public class HeapVsStack {
    public static void main(String[] args) {
        // 1. Create a primitive variable (int pages)
        int pages = 100;
        
        // 2. Create another primitive variable that copies it
        int copiedPages = pages;
        
        // 3. Create a Book object (Book b1 = new Book("Java Basics");)
        Book b1 = new Book("Java Basics");
        
        // 4. Create another Book reference (Book b2 = b1;)
        Book b2 = b1;
        
        // 5. Change the original primitive and the Book title
        pages = 200;  // Changing primitive
        
        // 6. Print both sets of values to compare behavior
        System.out.println("Primitive pages: " + pages);          // 200
        System.out.println("Copied primitive pages: " + copiedPages); // 100
    }
}

class Book {
    // 1. Declare variable: String title
    String title;
    
    // 2. Create a constructor to set the title
    Book(String title) {
        this.title = title;
    }
    
    // 3. Create a toString() to show the title
    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                '}';
    }
}

HeapVsStack.main(null);
Primitive pages: 200
Copied primitive pages: 100