dev only

Methods: Passing & Returning References of an Object

Objects as Instance Variables (Has-A)

A class's instance variable can itself be an object of another class — a has-a relationship. A Subsystem might have a MotorConfig (its tuning settings) as an instance variable:

class MotorConfig
{
    private double rampRate;
    private int currentLimit;
    // constructor, getters, setters not shown
}

class Subsystem
{
    private String name;
    private MotorConfig config; // instance variable of type MotorConfig
}

Call by Value, for Object References

Java always uses call by value — but for an object, the value being copied is the reference (the pointer to the object), not the object itself. Passing a MotorConfig into a method copies the reference — the parameter and the argument then both point at the same object.

public Subsystem(String initName, MotorConfig initConfig)
{
    name = initName;
    config = initConfig; // stores the SAME reference the caller passed in
}

If MotorConfig is mutable (has setters), this matters a lot: whoever else holds a reference to that same MotorConfig object can change it, and the Subsystem sees the change too — because it's not a separate copy, it's the same object viewed from two places.

Defensive Copying

If you don't want outside code to be able to reach in and change your object's data after the fact, make a copy instead of storing the reference you were handed:

public Subsystem(String initName, MotorConfig initConfig)
{
    name = initName;
    // Defensive copy — a brand new MotorConfig with the same values,
    // not the same object
    config = new MotorConfig(initConfig.getRampRate(), initConfig.getCurrentLimit());
}

Now changing the original initConfig object afterward has zero effect on Subsystem's own copy.

Returning an Object Reference

The same rule applies in reverse: a method that returns an object returns a reference to the real object, not a copy. If that object is mutable, whoever calls the getter can reach in and change it:

public MotorConfig getConfig()
{
    return config; // returns the SAME object — caller can mutate it!
}
MotorConfig c = subsystem.getConfig();
c.setRampRate(0.5); // this actually changes subsystem's real config!

If that's not what you want, return a defensive copy from the getter too.

Chaining Method Calls

Calling a method directly on another method's return value — subsystem.getConfig().getRampRate() — is called chaining. It only works if you know exactly what type each method in the chain returns, since the next method has to actually exist on that type.

Common Pitfalls

  • Assuming storing a passed-in object automatically makes an independent copy. It doesn't — only the reference is copied, so the stored object is the exact same one the caller still has.
  • Returning a mutable instance variable directly from a getter. This hands the caller a live reference to your object's actual internal state — they can change it without going through any of your class's own methods.
  • Chaining calls without checking each return type. a.getX().getY() only compiles if getX()'s return type actually has a getY() method.

Key Takeaways

  • An object can be another object's instance variable — a "has-a" relationship.
  • Passing or returning an object copies its reference, not the object itself — both sides then point at the same object.
  • If the object is mutable and you don't want outside code able to change it later, make a defensive copy — on the way in (constructor/setter) and/or on the way out (getter).
  • Chaining method calls (a.getX().getY()) requires knowing exactly what type each call in the chain returns.

Derived from csawesome-2026: unit-3-class-creation/3.06-methods-passing-and-returning-references-of-an-object.md (CSA 3.6)