Methods: Passing and Returning References of an Object
Why this matters
Have you ever worked on a group project using Google Docs? Imagine your friend, Priya, shares the link to your history presentation. You open the link and add a slide about the Boston Tea Party. When Priya opens the link again, she sees your new slide immediately. You both are looking at the exact same presentation.
Now, what if instead, Priya had emailed you a PowerPoint file? You download it, add your slide, and save it. Does Priya's original file have your new slide? Nope. You'd have to email your version back.
In Java, passing objects to methods is like sharing the Google Doc link. You're not sending a copy; you're sending a reference—a way to get to the one, original object. Understanding this is key to building programs that work together correctly. We'll explore how to pass these "links," how to return them, and a few important rules to avoid messing up the original "document."
Concept overview
flowchart TD
subgraph main method
A[Dog myDog = new Dog("Fido")];
end
subgraph The Heap (Memory)
O1[Dog Object <br> name: "Fido"];
end
subgraph assignKennelName(Dog doggy)
B[Parameter 'doggy' gets copy of reference];
C[doggy.setName("Buddy")];
end
subgraph The Heap (Memory)
O2[Dog Object <br> name: "Buddy"];
end
A -->|references| O1;
A -- passes reference to --> B;
B -->|also references| O1;
C -- follows reference and modifies object --> O2;
O1 -- state changes to --> O2;
Core explanation
Hello everyone! I'm Saavi, and today we're going to tackle one of the most fundamental ideas in object-oriented programming: how objects travel between different parts of your code. Getting this right is the difference between a program that works perfectly and one with mysterious, frustrating bugs.
The Google Doc Analogy: References vs. Copies
Let's stick with our Google Doc analogy. In Java, primitive types (int, double, boolean) are like emailing a file. When you pass an int to a method, the method gets a completely separate copy of its value.
void changeValue(int x) {
x = 100; // This only changes the method's local copy.
}
// In main method...
int myNumber = 10;
changeValue(myNumber);
System.out.println(myNumber); // This will still print 10!
Objects, however, are like sharing a Google Doc link. The variable you use to hold an object doesn't contain the object itself; it contains a reference—the address where the object lives in memory.
When you pass an object to a method, you're giving the method a copy of the reference. Both the original variable and the method's parameter now point to the exact same object in memory.
Passing References to Methods
Let's see this with a Dog class.
public class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
Now, let's write a kennel method that tries to rename a Dog.
public class Kennel {
public void assignKennelName(Dog doggy) {
// We are using the reference 'doggy' to call a method
// on the original Dog object.
doggy.setName("Buddy");
}
}
// In your main method...
Dog myDog = new Dog("Fido");
System.out.println("My dog's name is: " + myDog.getName()); // Prints "Fido"
Kennel myKennel = new Kennel();
myKennel.assignKennelName(myDog); // We pass the reference to myDog
System.out.println("My dog's new name is: " + myDog.getName()); // Prints "Buddy"
Look at that! The assignKennelName method successfully changed the name of our original Dog object. This is because the doggy parameter inside the method and the myDog variable in main were both pointing to the same Dog object. When we called doggy.setName("Buddy"), we were following the reference to the original object and changing its state. This is what the College Board means when it says a method can "alter the state of the object."
public void tryToReplaceDog(Dog doggy) {
// This creates a brand new Dog object.
Dog newDog = new Dog("Max");
// This makes the 'doggy' parameter point to the NEW dog.
// It does NOT change what 'myDog' in main points to.
doggy = newDog;
}
// In your main method...
Dog myDog = new Dog("Fido");
System.out.println("My dog's name is: " + myDog.getName()); // Prints "Fido"
Kennel myKennel = new Kennel();
myKennel.tryToReplaceDog(myDog);
System.out.println("Did my dog's name change? " + myDog.getName()); // Still prints "Fido"!
Why didn't it change? Because when you do doggy = newDog, you're only changing the local parameter doggy. You made it hold the address of a new Dog object named "Max." You did not change the original myDog variable back in main. It still holds the address of the "Fido" object. You just broke the link.
Returning References from Methods
This works the other way, too. When a method returns an object, it's returning a reference.
Imagine a method that finds the student with the highest GPA from a list.
// In a School class
public Student findValedictorian(Student[] students) {
Student topStudent = students[0];
for (int i = 1; i < students.length; i++) {
if (students[i].getGpa() > topStudent.getGpa()) {
topStudent = students[i]; // 'topStudent' now holds the reference to a different student
}
}
// We are returning a REFERENCE to the student who was already in the array.
// We are NOT creating a new copy of the valedictorian.
return topStudent;
}
When you call this method, the variable you assign the result to gets a reference to the original Student object that exists in the students array.
Student valedictorian = mySchool.findValedictorian(allStudents);
// 'valedictorian' and the student in the 'allStudents' array are one and the same.
A Special Rule: Accessing Private Data
Normally, you can't access private instance variables from outside a class. But there's a special exception. A method can access the private data of a parameter if the parameter is the same class type as the method itself.
This sounds confusing, but it's essential for methods like equals().
Let's add an equals method to our Dog class to see if two Dog objects have the same name.
public class Dog {
private String name;
// ... constructors and other methods ...
// This method compares the current Dog object ('this')
// to another Dog object ('other').
public boolean equals(Dog other) {
// Because 'other' is also a Dog, we are allowed
// to access its private 'name' variable directly!
return this.name.equals(other.name);
}
}
See it in action
Worked examples
Let's walk through a couple of examples to make sure this is crystal clear.
Updating an Order in a System
Problem: You're working on a system for a coffee shop in Seattle. You have an Order class. You need to write a method in your Register class that marks an order as "complete."
// The Order class
public class Order {
private int orderId;
private boolean isComplete;
public Order(int orderId) {
this.orderId = orderId;
this.isComplete = false; // All new orders start as not complete
}
public void setComplete(boolean status) {
this.isComplete = status;
}
public boolean isComplete() {
return this.isComplete;
}
}
// The Register class
public class Register {
// This is the method we need to understand
public void fulfillOrder(Order currentOrder) {
currentOrder.setComplete(true);
}
}
Walkthrough:
- 1SetupLet's say our
mainmethod creates anOrderobject.Order order12 = new Order(12); System.out.println("Order 12 complete? " + order12.isComplete()); // Prints "false"At this point,
order12is a variable holding a reference (an address) to anOrderobject in memory. That object'sisCompletefield isfalse. - 2Calling the MethodNow, we create a
Registerand call our method.Register frontRegister = new Register(); frontRegister.fulfillOrder(order12);Why this step matters: When
fulfillOrderis called, Java takes the reference stored inorder12and copies it into the method's parameter,currentOrder. Now, bothorder12(inmain) andcurrentOrder(infulfillOrder) point to the exact sameOrderobject. - 3Inside the MethodThe line
currentOrder.setComplete(true);executes. Java follows the reference incurrentOrderto theOrderobject and calls itssetCompletemethod. This changes theisCompleteinstance variable inside that single object fromfalsetotrue. - 4The ResultAfter the method finishes, we check the status again in
main.System.out.println("Order 12 complete? " + order12.isComplete()); // Prints "true"The change is visible! We successfully modified the original object's state because we were working with a reference to it.
Comparing Teammates
Problem: You have a Player class for a basketball team. Write a method isSamePosition that checks if another player plays the same position.
public class Player {
private String name;
private String position; // e.g., "Guard", "Forward"
public Player(String name, String position) {
this.name = name;
this.position = position;
}
// The method we're focusing on
public boolean isSamePosition(Player otherPlayer) {
// We can access otherPlayer.position directly!
return this.position.equals(otherPlayer.position);
}
}
Walkthrough:
- 1SetupWe create two
Playerobjects.Player maya = new Player("Maya", "Guard"); Player jordan = new Player("Jordan", "Guard"); Player carlos = new Player("Carlos", "Forward"); - 2Calling the MethodWe call the method on one object, passing another as the argument.
boolean mayaAndJordan = maya.isSamePosition(jordan); // should be true boolean mayaAndCarlos = maya.isSamePosition(carlos); // should be false - 3Inside the Method (First Call)When
maya.isSamePosition(jordan)is called:thisrefers to themayaobject.otherPlayeris a reference to thejordanobject.- The code evaluates
this.position.equals(otherPlayer.position). - Why this works: Even though
positionisprivate, theisSamePositionmethod is part of thePlayerclass. The parameterotherPlayeris also aPlayer. Therefore, the rule allows us to accessotherPlayer.position. - It compares
"Guard".equals("Guard"), which istrue. The method returnstrue.
- 4Inside the Method (Second Call)When
maya.isSamePosition(carlos)is called, it compares"Guard".equals("Forward"), which isfalse. The method returnsfalse.
Try it yourself
Let's put your knowledge to the test.
Problem 1:
You have a Song class and a Playlist class. The Playlist has an instance variable for the currentlyPlaying song.
public class Playlist {
private Song currentlyPlaying;
// ... constructor ...
public void setCurrentlyPlaying(Song newSong) {
// Your code here
}
public Song getCurrentlyPlaying() {
return this.currentlyPlaying;
}
}
In a main method, create a Playlist and two Song objects, songA and songB. The playlist starts with songA playing. Call a method skipSong(Playlist p, Song next) that should change the playlist's currently playing song to songB.
Problem 2:
Add a method to your Song class called hasSameArtist(Song other). It should return true if the other song has the same artist. Assume Song has a private String artist instance variable. Can you write the method body by accessing other.artist directly?
Practice — 8 questions
In simple terms, this topic is about how methods share or return access to existing objects, not brand new copies, like sharing a link to a Google Doc.
void changeValue(int x) {
x = 100; // This only changes the method's local copy.
}
// In main method...
int myNumber = 10;
changeValue(myNumber);
System.out.println(myNumber); // This will still print 10!
- 3.6.A: Develop code to define behaviors of an object through methods written in a class using object references and determine the result of calling these methods.
- 3.6.A.1
- When an argument is an object reference, the parameter is initialized with a copy of that reference; it does not create a new independent copy of the object. If the parameter refers to a mutable object, the method or constructor can use this reference to alter the state of the object. It is good programming practice to not modify mutable objects that are passed as parameters unless required in the specification.
- 3.6.A.2
- When the return expression evaluates to an object reference, the reference is returned, not a reference to a new copy of the object.
- 3.6.A.3
- Methods cannot access the private data and methods of a parameter that holds a reference to an object unless the parameter is the same type as the method's enclosing class.
flowchart TD
subgraph main method
A[Dog myDog = new Dog("Fido")];
end
subgraph The Heap (Memory)
O1[Dog Object <br> name: "Fido"];
end
subgraph assignKennelName(Dog doggy)
B[Parameter 'doggy' gets copy of reference];
C[doggy.setName("Buddy")];
end
subgraph The Heap (Memory)
O2[Dog Object <br> name: "Buddy"];
end
A -->|references| O1;
A -- passes reference to --> B;
B -->|also references| O1;
C -- follows reference and modifies object --> O2;
O1 -- state changes to --> O2;
Read what Saavi narrates
Hello! I'm Saavi, and welcome to Shrutam.
Have you ever worked on a group project using Google Docs? Imagine your friend, Priya, shares the link to your history presentation. You open it and add a slide. When Priya opens the link again, she sees your new slide immediately. You're both looking at the exact same presentation.
In Java, passing objects to methods is just like sharing that Google Doc link. You're not sending a copy of the whole object, you're just sending a reference... like a link or an address that points to the one, original object.
Let's walk through an example. Imagine we have an Order class for a coffee shop, and a Register class. The Register has a method called fulfillOrder, which takes an Order as a parameter.
In our main code, we create a new order, let's call it order twelve. Right now, its status is 'not complete'. Then, we call the fulfillOrder method, passing in order twelve.
Here's the magic. The method receives a reference that points to our original order twelve. Inside the method, we call a setter to change the order's status to 'complete'.
Now, back in our main code, what happens if we check the status of order twelve? It's 'complete'! The change we made inside the method stuck, because we were modifying the original object all along.
This brings up a really common mistake. Many students think that to change an object, you have to re-assign it inside the method, like saying the parameter now equals a whole new object. But that doesn't work. Doing that just changes what the local parameter is pointing to. It doesn't affect the original variable at all. It's like you took the Google Doc link someone sent you, and just made it point to a different document on your own computer. It doesn't change their original link.
So remember, when you're working with objects, you're almost always working with references. Use those references to call methods and change the object's state. You've got this!
Reassigning a parameter (`param = new Object();`) only changes the method's local copy of the reference. It does not affect the original variable that was passed in.
To change the state of the original object, call one of its methods (e.g., a setter like `param.setValue(newValue)`).
Java passes a copy of the *reference*, not a copy of the *object*. Both the original variable and the method parameter point to the same object in memory.
Remember the Google Doc analogy. You're sharing a link, so any changes made through that link affect the original document.
Primitives (`int`, `double`, etc.) are passed by value (a true copy). Objects are passed by reference (a copy of the address). Changes to primitive parameters inside a method are never reflected outside the method.
Always ask yourself, "Is this a primitive or an object?" If it's an object, be aware of potential side effects (changes to the object's state).
The special rule for accessing private data only applies when the parameter's type is the same as the class containing the method.
If you have a `Student` method that takes a `Teacher` parameter, you must use the `Teacher`'s public methods (`teacher.getSubject()`) to access its data. You cannot access `teacher.privateSubject` directly.
If a method's purpose is to modify the state of an object passed to it (a "mutator"), it often has a `void` return type. The changes are saved to the original object via its reference.
Look at the method's purpose. If it's modifying an existing object (like `fulfillOrder`), it's often `void`. If it's finding or creating an object (like `findValedictorian`), it will have an object return type.