Layers of Logic

6.1

Classes, Objects, and `new`

A class defines a type with state and behaviour. Each object is one instance of that type, with its own fields.

Core20 min read4 exercises
01

Previously on

Section 4.1 stored one roster in four parallel arrays. A swap had to move four entries together.

Nothing connected those entries in the type system. If three moved and one stayed, the compiler accepted the broken roster.

Section 4.2 also separated an array variable from the array object created by new. Classes use the same reference model.

02

The problem

Suppose one student needs four values.

String name = "Aditya";
int age = 28;
int rollNumber = 101;
String college = "IIT Guwahati";

A second student needs four more variables and new names for all of them.

String name2 = "Rohit";
int age2 = 24;
int rollNumber2 = 102;
String college2 = "IIT Guwahati";

Java sees eight unrelated variables. It does not know that name2 and rollNumber2 describe the same student.

Methods expose the same weakness.

printDetails(name, age, rollNumber, college);

Swapping the two String arguments still compiles. Adding a phone number changes every call site.

Parallel arrays reduce the variable count, but they keep the alignment bug. The program needs a type that makes one student’s data travel together.

03

The idea

A class defines that type.

class Student {
    String name;
    int age;
    int rollNumber;
    String college;

    void printDetails() {
        System.out.println(rollNumber + ": " + name + ", " + age + ", " + college);
    }

    boolean isAdult() {
        return age >= 18;
    }
}

The fields hold state. The methods define behaviour available on a Student.

Student is now a reference type, like String or int[]. An object is one instance of that type.

ClassObject
Roledefines a typeis one instance of that type
Statedeclares the fieldsholds one value for each instance field
Behaviourdeclares method codereceives instance method calls
Written or made byclass Student { ... }new Student()

Here is a complete program.

class Student {
    String name;
    int age;
    int rollNumber;

    void birthday() {
        age++;
    }

    String label() {
        return rollNumber + ": " + name + " (" + age + ")";
    }
}

public class Demo {
    public static void main(String[] args) {
        Student aditya = new Student();
        aditya.name = "Aditya";
        aditya.age = 28;
        aditya.rollNumber = 101;

        Student rohit = new Student();
        rohit.name = "Rohit";
        rohit.age = 24;
        rohit.rollNumber = 102;

        aditya.birthday();
        System.out.println(aditya.label());
        System.out.println(rohit.label());
    }
}
101: Aditya (29)
102: Rohit (24)

birthday() needs no Student parameter. An instance method runs with a current object, so plain age means that object’s field.

A roster now needs one array.

Student[] roster = new Student[100];

Swapping two elements moves two references to whole Student objects. A name cannot separate from its roll number during that swap.

04

Under the hood

Going deeper

What new Student() does

new creates an object. The full expression performs several ordered steps.

Creating one Student

  1. Ensure the class is readyThe JVM loads, links, and initialises the class when required. Section 6.4 separates those stages.
  2. Allocate storageThe JVM reserves enough managed memory for the object and its instance fields.
  3. Write default field valuesNumeric fields become 0, boolean fields become false, and reference fields become null.
  4. Run initialisation codeField initialisers, initialiser blocks, and constructors run in a defined order. Section 6.2 traces it.
  5. Produce a referenceThe expression evaluates to an opaque reference to the new object.

The source code cannot read a memory address from that reference. It cannot add one to it either.

The garbage collector may move an object while the program runs. References continue to reach the object after such a move.

A useful memory model

Student aditya = new Student();
Student rohit = new Student();

Stack

main

Student aditya@1a2b
Student rohit@283c

Heap

Student@1a2b

String namenull
int age0
int rollNumber0

Student@283c

String namenull
int age0
int rollNumber0
Two local variables reach two separate objects. The arrows mean references, not exposed numeric addresses.

Real JVMs may keep a local value in a register, move it, or remove it during optimisation. The drawing explains identity and reachability, not a required physical layout.

Declaration, null, and creation are different

Student a;                       // unassigned local variable
Student b = null;                // assigned, but reaches no object
Student c = new Student();       // assigned and reaches an object

Reading a does not compile.

System.out.println(a);           // ERROR: variable a might not have been initialized

Reading b is legal. Dereferencing it is not.

System.out.println(b);           // null
System.out.println(b.name);      // throws NullPointerException

The dot operator asks the object reached by the reference to provide a field or receive a method call. null reaches no object.

Objects may refer to other objects

class Address {
    String city;
}

class Student {
    String name;
    Address address;
}

The address field holds either null or a reference to an Address object. It does not contain the Address object’s fields inline.

Stack

main

Student s@1a2b

Heap

Student@1a2b

String name"Aditya"
Address address@283c

Address@283c

String city"Guwahati"
The Student and Address are separate objects connected by a reference.

Two students may hold references to the same Address. A change through either path then reaches that shared object.

Methods are not copied into every object

Each object has its own instance fields. It does not hold another copy of the method bytecode.

The JVM keeps class metadata and method code with the loaded class. Each defining class loader gets its own class identity and static state.

That detail matters in application servers and plugin systems. Section 6.4 returns to it when counting static members.

05

What it costs

Objects add a header, alignment, and references around your field data. Their measured size depends on the JVM and its options. Section 6.3 uses JOL to inspect one real layout.

An access such as student.address.city follows two references. Either target may be far from the other data in memory, which can hurt cache use.

Objects also create lifetime work. The garbage collector must track reachable objects and recover storage from unreachable ones.

The extra machinery is not free. In return, the type system keeps related state together and checks which operations belong to it.

06

Check yourself

Answer from memory before opening the explanation. Then compare your rule with the answer and revisit the example if they differ.

  1. What differs between `Student s;`, `Student s = null;`, and `Student s = new Student();`?

    Show the answer

    The first declares a local variable but does not give it a value. Java will reject any read of that local variable until every path assigns it.

    The second gives the variable the reference value null. The variable may be read, but s.name throws because there is no object to receive the field access.

    The third creates and initialises a Student object. It then stores a reference to that object in s. The reference is an opaque JVM value, not an address your program can inspect or do arithmetic with.

  2. A class has one `name` field declaration. You create two objects. How many `name` fields exist?

    Show the answer

    Two instance fields exist, one in each object. The declaration in the class defines the shape that every instance receives.

    Changing one object's field does not change the other object's field. A field marked static follows a different rule and belongs to the class instead.

  3. What does a class define besides a group of fields?

    Show the answer

    A class defines a new reference type. Its fields describe object state, its methods describe available behaviour, and its constructors control how instances begin.

    The compiler can now reject assigning a Rectangle to a Student variable. It can also check field and method names on every use.

07

Exercises

Write each solution in VS Code, predict its result, and then run the checks. Reading an example does not replace implementing it.

4 exercises90 pointsabout 105 minutes

The Layers of Logic VS Code extension runs the checks for exercises marked checked. For a manual exercise, run the program and compare its behaviour with the stated requirements and sample output.
A

From Parallel Arrays to Objects

Real work·30 min·25 points·The Registry

checkedex-6-1-a

Go back to the roster you built in Section 4.1 with four parallel arrays, and rebuild it with a class.

Then look hard at the sorting code. In the array version, every swap had to happen in four places, and nothing stopped you from forgetting one. Count how many places a swap touches now.

That is the whole argument for objects, and you can only see it properly because you wrote the bad version first.

Write the explanation as a comment in your file. Not “objects are better”, but the specific mechanical reason the bug cannot be expressed any more.

What to write

These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.

class RegistryV2 {
    static Unit[] roster()
    static Unit highestReadiness(Unit[] units)
    static Unit[] sortedByReadinessDescending(Unit[] units)
    static Unit findById(Unit[] units, int id)
    static int arraysNeededToSort()
}

Write a nested Unit class holding name, id, readiness and active. The roster is the same five units from Section 4.1. The sort returns a new array and leaves the one it was given alone. arraysNeededToSort is you counting how many arrays a swap has to touch now.

What your program must do

  • Write a Unit class and rebuild the roster with it
  • Sort by readiness and confirm no unit comes apart
  • Return whole units from the finders, not indexes
  • Count how many arrays a swap touches now against Section 4.1
RegistryV2.java
import java.util.*;

public class RegistryV2 {

    // TODO: a Unit class holding name, id, readiness and active
    static class Unit {
    }

    // The same five units as Section 4.1:
    //   Atlas 101 88.5 true, Beacon 102 42.0 true, Cipher 103 95.5 false,
    //   Drift 104 61.0 true, Ember 105 73.5 true
    static Unit[] roster() { return new Unit[0]; }  // TODO

    static Unit highestReadiness(Unit[] units) { return null; }  // TODO
    static Unit findById(Unit[] units, int id) { return null; }  // TODO

    // Sort by readiness, highest first. Return a NEW array.
    static Unit[] sortedByReadinessDescending(Unit[] units) { return units; }  // TODO

    // How many arrays does one swap have to touch now?
    static int arraysNeededToSort() { return 4; }  // TODO

    public static void main(String[] args) {
        // TODO: print the roster, sorted by readiness
    }
}
Hint 1
The class needs the four fields and a constructor. Assign with this.name = name so the parameter and the field can share a name.
Hint 2
The sort is the same bubble sort as Section 4.1 with one difference: swapping one element now moves the whole unit, because the fields travel together.
Hint 3almost the answer
That is the answer to the last question. Four arrays became one, and the bug where you forget to swap one of them stopped being possible to write.
What this is really testing

Whether you can see what a class buys you, by rewriting code you already wrote badly. The Section 4.1 sorting bug should become impossible, and you should be able to say exactly why.

B

Draw What new Does

Warm up·20 min·15 points

checkedex-6-1-b

Draw this program. On paper, by hand, line by line.

One box per variable on the stack. One block per object on the heap. An arrow for every reference.

Count the objects. There are three, and the number of new keywords tells you that before you run anything.

Then predict the printed city. If your drawing is right, the answer is obvious. If your drawing is wrong, running the program will tell you which arrow you got wrong, which is the most useful kind of feedback there is.

Finish with the null line. Uncomment it, read the exception, and point at the exact box in your drawing that caused it.

What to write

These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.

class Drawing {
    static Student make(String name, int age, String city)
    static boolean freshStudentHasAddress()
    static String freshStudentName()
    static int freshStudentAge()
    static boolean assignmentSharesTheObject()
    static boolean fieldCopyStillSharesAddress()
    static int objectsCreatedBy(String what)
}

Student and Address are nested classes. objectsCreatedBy takes the text "new Student()" or "make" and answers how many objects that creates. fieldCopyStillSharesAddress copies name, age and address one at a time, then changes the city through the copy.

What your program must do

  • Draw the stack and heap for two students before writing any code
  • Say what a brand new Student contains, field by field
  • Show that assignment shares the object
  • Show that copying the fields still shares the Address
Drawing.java
public class Drawing {

    static class Address {
        String city;
        Address(String city) { this.city = city; }
    }

    static class Student {
        String name;
        int age;
        Address address;
    }

    static Student make(String name, int age, String city) { return null; }  // TODO

    // What does a brand new Student actually contain? Predict all three.
    static boolean freshStudentHasAddress() { return true; }  // TODO
    static String  freshStudentName()       { return ""; }    // TODO
    static int     freshStudentAge()        { return -1; }    // TODO

    // Assign one Student to another, change the second, and report on the first.
    static boolean assignmentSharesTheObject() { return false; }  // TODO

    // Copy name, age and address ONE AT A TIME into a new Student, then change
    // the city through the copy. Did the original change too?
    static boolean fieldCopyStillSharesAddress() { return false; }  // TODO

    // How many objects does each of these create?
    static int objectsCreatedBy(String what) { return 0; }  // TODO

    public static void main(String[] args) {
        // TODO: draw the stack and the heap for two students on paper first
    }
}
Hint 1
new Student() creates one object. Its address field is a slot holding null, and no Address exists until something calls new Address.
Hint 2
Fields get default values: null for a reference, 0 for an int, false for a boolean. Nothing is left uninitialised.
Hint 3almost the answer
Copying the fields is a shallow copy. name and age came across as values, and address came across as a reference, so both students now point at one Address.
What this is really testing

Whether you can draw the stack and heap for a small program with objects. Every confusing thing later in this phase becomes clear if you can draw this, and stays confusing if you cannot.

C

Move the Methods In

Real work·25 min·20 points

checkedex-6-1-c

Four methods that all take a Rectangle as their first parameter. That is a sign. When every method in a group needs the same thing, that thing should own the methods.

Move them inside the class and delete the parameter. Watch the code get shorter and clearer at the same time.

Then add scaleBy, which changes the rectangle rather than producing a new one. Ask yourself why it can work at all, given that the caller passed the object in. You already know the answer from Section 6.3, and it is worth stating out loud.

Finally, isSquare has a bug that has nothing to do with objects. Find it, name the phase it comes from, and fix it.

What to write

These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.

class Behaviour {
    static Rectangle make(double w, double h)
    static int argumentsNeededNow()
    static int argumentsNeededBefore()
}

Rectangle is a nested class and the four methods move INSIDE it: area(), perimeter(), isSquare(), describe(), plus scale(double). They take no Rectangle argument, because the object is the argument. describe returns a String containing the area and whether it is square.

What your program must do

  • Move all four methods inside the class
  • Add a scale method that changes the object
  • Show that two rectangles keep their own measurements
  • Say what happened to the parameter every one of those methods used to need
Behaviour.java
public class Behaviour {

    static class Rectangle {
        double width;
        double height;

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

        // TODO: move the four methods in here. They take NO Rectangle argument.
        //   double area()
        //   double perimeter()
        //   boolean isSquare()
        //   String describe()   returns something containing the area and isSquare
        //   void scale(double factor)  multiplies both sides
    }

    static Rectangle make(double w, double h) { return null; }  // TODO

    // How many arguments did area need before, and how many now?
    static int argumentsNeededBefore() { return 0; }  // TODO
    static int argumentsNeededNow()    { return 1; }  // TODO

    public static void main(String[] args) {
        // TODO: build a rectangle, describe it, scale it, describe it again
    }
}
Hint 1
Inside the class, width means this object's width. There is no argument to pass because the object is already the thing being asked.
Hint 2
describe() can call area() and isSquare() directly, with nothing passed between them.
Hint 3almost the answer
scale shows the other half of the idea. A method can change the object it belongs to, which is why data and behaviour together is more than a tidy arrangement.
What this is really testing

Whether you can see that an object holds behaviour as well as data. A class with only fields is a struct. Putting the methods where the data lives is what makes it an object.

D

Measure the Overhead

Hard·30 min·30 points

checkedex-6-1-d

You were told an object costs more than its fields. Measure how much more.

Predict the numbers using the configuration stated in the starter. Then run the heap-usage experiment.

Two million ints in an array, against two million objects each holding one int. Same information. Very different memory.

The ratio explains why boxed values can cost more memory. It belongs to this run and this configuration.

The numbers will vary because collection, allocation buffers, and heap growth affect the difference. Compare the trend, then use JOL when you need a field layout.

What to write

These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.

class Overhead {
    static long usedBytes()
    static long primitiveArrayBytes(int n)
    static long objectArrayBytes(int n)
    static int bytesPerObject()
    static int bytesPerPrimitive()
    static int headerBytes()
    static long expectedObjectBytes(int n)
}

Use this explicit model for the tested arithmetic: 64-bit HotSpot, 12-byte object headers, 4-byte compressed references, and 8-byte object alignment. The heap-difference methods are noisy experiments. They do not measure an individual object's layout.

What your program must do

  • Measure both arrays and record the two numbers
  • Hold a live reference while measuring, and say why
  • Predict the object cost under the stated HotSpot model
  • Explain why a heap difference is not an object-layout measurement
Overhead.java
public class Overhead {

    static class Boxed { int value; Boxed(int v) { value = v; } }

    static Object keep;   // hold a live reference while measuring

    static long usedBytes() { return 0; }  // TODO

    static long primitiveArrayBytes(int n) { return 0; }  // TODO: an int[n]
    static long objectArrayBytes(int n)    { return 0; }  // TODO: a Boxed[n], all filled

    // MODEL FOR THIS EXERCISE ONLY: 64-bit HotSpot, compressed references,
    // 12-byte headers, 8-byte object alignment. Other configurations differ.
    static int headerBytes()       { return 0; }  // TODO: the model's header
    static int bytesPerPrimitive() { return 0; }  // TODO
    static int bytesPerObject()    { return 0; }  // TODO: header + int, padded to a multiple of 8

    static long expectedObjectBytes(int n) { return 0; }  // TODO: objects plus the reference array

    public static void main(String[] args) {
        // TODO: measure both at 2,000,000 and compare
        // TODO: run with -Xms2g -Xmx2g so the heap is not resizing under you
    }
}
Hint 1
Without a live reference the collector can take the array before you read the number, and you measure zero. Run with -Xms2g -Xmx2g so the heap is not growing underneath you either.
Hint 2
Under this exercise's model, the header is 12 bytes. Add four for the int and apply the model's 8-byte alignment. Do not carry those settings to another JVM without checking them.
Hint 3almost the answer
Under the same model, the object version pays for each 16-byte object and each 4-byte array slot. JOL is the right tool for checking an individual layout on the active JVM.
What this is really testing

Whether you can separate a layout prediction for one named HotSpot configuration from a noisy heap-usage experiment. Both are useful evidence, but neither is a universal Java object size.

08

After the credits

The current objects can exist with null names and negative ages. Section 6.2 introduces constructors and one place to enforce valid starting state.

References create sharing. Section 6.3 shows why assignment aliases an object and why Java still passes every argument by value.

Fields are also open to any code that can reach them. Section 7.1 adds access control and keeps state changes behind methods.

Two separate Student objects remain unequal under ==, even when all their fields match. Section 8.3 defines value equality, then Phase X uses it inside hash-based collections.

Threads you opened in this section