Skip to main content
Logo image

Section 3.6 Methods: Passing and Returning References of an Object

90 minutes
Programs often consist of many classes that interact with each other. Objects of one class can be instance variables of another class. Objects can be passed as arguments to methods and returned from methods. This is a powerful feature of object-oriented programming that allows for complex interactions between objects.

Subsection 3.6.1 Objects as Instance Variables

Classes can have complex instance variables that use other classes. For example, a Person class could have an instance variable for the address which can be an object with its own instance variables of street, city, state, and zipcode. This is a has-a relationship where a Person has an Address inside it.
class Address
{
    // instance variables
    private String street;
    private String city;
    private String state;
    private String zipcode;
}

class Person
{
    // instance variables
    private String name;
    private Address addr;  // instance variable of type Address
}
In Java, usually each class is saved in its own file with the same name as the class, so the Person class would be in Person.java and the Address class would be in Address.java. If the files are in the same folder, they can be compiled together so that they can interact. In Runestone, we cannot have separate files, so we put them in 1 coding area. In Runestone, only the class that has the main method can be public; the other classes can leave out the public keyword.

Subsection 3.6.2 Objects as Arguments

Java uses Call by Value when it passes arguments to methods and constructors. This means that a copy of the value in the argument is saved in the parameter variable. If the parameter variable changes its value inside the method, the original value outside the method is not changed. With primitive types like int and double, the argument value is copied into the parameter variable as expected. However, if you pass in an argument that holds a reference to an object, like a String or Person or Address or Turtle objects, the reference is copied, not the whole object. Java was designed this way to avoid copying large objects from method to method.
When a copy of a reference is passed in and saved in the parameter variable, the parameter and the argument are then aliases, both refering to the same object. Remember when we discussed reference aliases with turtle objects in SectionΒ 2.2. An object can have many names or aliases that refer to it, much like you may call your teacher with their last name, but friends may call them by their first name, and their children may call them Mom or Dad.
Figure 3.6.1. Turtle Reference Equality
When we pass an object to a method, the parameter variable holds a reference to the same object as the argument variable. This is similar to a Google doc where multiple people can edit the same document at the same time (Thanks to Rachel Chagnon at Somerville High School, MA for this analogy). If one person changes the document, everyone else sees the change. In the same way, if a method changes the state of a mutable object that was passed in as a parameter, the change is seen by the caller of the method. This can lead to unexpected results if we want to preserve the original object unchanged.
Some classes like String are immutable, so they cannot be changed by any method; for example, when s.toUpperCase() is called, it returns a new String object with the uppercase letters, but the original String object is not changed. Any new classes that you write that have setters or methods that change the state of the object will be mutable. So, if you pass in an object of a class that you write into a method as an argument, you need to be careful that the method does not change the object unless that is the intended behavior. It is good programming practice to not modify mutable objects that are passed as parameters unless required in the specification.
Try the code below. Add code in the main method that changes the original address to your city. Does it also change in the person object? This can be a problem with references to mutable objects. In the next subsection, we’ll copy the Address object to avoid this problem.

Activity 3.6.1.

Try the following code. Scroll down to see both the Person and the Address class definitions. The Person class has an Address object as an instance variable. Add code in the main method that changes the original address to your city. Does it also change in the person object? This can be a problem with references to mutable objects.

Subsection 3.6.3 Copying Parameter Objects

The Person class above has an Address object as an instance variable. We found that changing the Address object outside the class also changed the address in the Person object. This is because the Address object is mutable, and the reference to the object is passed in as an argument to the constructor.
When you pass object references as parameters to constructors, those references refer to the same objects as the references in the caller. If the objects are immutable, like String objects it doesn’t matter at all. On the other hand, if the objects are mutable, meaning their instance variables can change after they are constructed, then storing the passed-in reference in an instance variable in your object can lead to surprising results: if some other code changes the object it will change for you too. If that’s not what you want, sometimes it makes sense to copy the object passed to the constructor and store the copy in the instance variable instead. How to make the copy will depend on the class of the object, but often you can just construct a new object of the appropriate class using values from the original object as shown below. This way the instance variable addr does not hold a reference to the original object initAddr, and the methods in the Person class cannot modify the state of the original object.
public class Person
{
    private String name;
    private Address addr; // Assumes an Address class is already defined

    // constructor: initialize instance variable and call Address constructor to
    // make a copy
    public Person(String initName, Address initAddr)
    {
        name = initName;
        addr =  new Address(
                        initAddr.getStreet(),
                        initAddr.getCity(),
                        initAddr.getState(),
                        initAddr.getZipcode());
   }
}
Another way to handle this is to provide a copy constructor in the Address class that takes an Address object as a parameter and makes a copy of it. Then, the Person constructor can call the Address copy constructor.
class Address
{
    // instance variables
    private String street;
    private String city;
    private String state;
    private String zipcode;
    // constructor
    public Address(String initStreet, String initCity,
                   String initState, String initZipcode)
    {
        street = initStreet;
        city = initCity;
        state = initState;
        zipcode = initZipcode;
    }
    // copy constructor
    public Address(Address otherAddr)
    {
        street = otherAddr.getStreet();
        city = otherAddr.getCity();
        state = otherAddr.getState();
        zipcode = otherAddr.getZipcode();
    }
}
class Person
{
    // instance variables
    private String name;
    private Address addr;  // instance variable of type Address defined below

    // constructor: initialize instance variable and call Address constructor to
    // make a copy
    public Person(String initName, Address initAddr)
    {
        name = initName;
        // call the copy constructor of Address to make a defensive copy
        addr =  new Address(initAddr);
    }
}
Try the variation of the code below where the constructor copies the Address object so that it is separate from the original object. Add code in the main method that changes the original address to your city. It will not change the address in the Person object because it was copied.

Activity 3.6.2.

In the following Person class, the constructor method copies the Address object so that it is separate from the original object. Complete the setAddress method in Person so that it also makes a copy of otherAddr object like the constructor. Then, add code in the main method that changes the original address to your city. It should not change the copy! Add code that uses the setAddress method. It should not change the original!

Subsection 3.6.4 Parameter of the Same Class Type

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. In the following code, the Person class can access the Adress instance variable of another Person object because they are of the same class type, but it cannot access the city instance variable of an Address object directly because it is private and not of the same class.
public class Person
{
    private String name;
    private Address addr;  // instance variable of type Address defined below

    public void copyAddress(Person otherPerson)
    {
        // otherPerson.addr will work because the parameter is of the same class Person
        addr = new Address(otherPerson.addr);
    }
     public void copyCity(Address otherAddr)
     {
        // This will not work because the city instance variable is private
        //   and the parameter is not of the same class type
        // addr.city = otherAddr.city;
        // But you can use the get method to access the city
        addr.setCity(otherAddr.getCity());
    }
}
Try it in the code below.

Activity 3.6.3.

Run the code to see that the Person class can directly access the instance variables of objects of the same class, but cannot directly access the instance variable of an Address object because it is not of the same class. Change the code in the copyAddressFromAddress() method to use get/set methods instead.

Subsection 3.6.5 Returning Objects

Methods can also return objects. Remember that methods can only return the value of a variable. If the return value is of a primitive type like int or double, only a copy of the value is returned; the original variable cannot be changed. But when the return expression evaluates to an object reference, the reference is returned, not a reference to a new copy of the object. This means that there can be multiple references to the same object. If the object is mutable, then any changes made to the object through one reference will be seen through any other references to the object.
For example, the Person class can have a getAddress method that returns the Address instance variable which is an object. Since Address has set methods which make it mutable, the caller can change the Address object through the reference returned by the method.
public class Person
{
    // instance variables
    private String name;
    private Address addr;  // instance variable of type Address

    public Address getAddress()
    {
        return addr;
    }

    public static void main(String[] args)
    {
        Person p1 = new Person("Skyler",
                          new Address("123 Main St",
                              "Anytown","Anystate","12345"));
        System.out.println(p1);
        Address a = p1.getAddress();
        a.setCity("Othertown");
        System.out.println(p1);
    }
}
Notice that we created the new Address object above inside the call to the Person constructor, instead of saving it into a variable first: Person p1 = new Person("Skyler", new Address("123 Main St", "Anytown","Anystate","12345")). This is called an anonymous object where we create a new object without associating it with a variable. This is useful when you only need the object for a short time. This new Address object will be copied into the instance variable addr in the Person constructor.
Try it in the code below.

Activity 3.6.4.

Run the code to see that the Person class can return the Address object which is mutable. Add code in the main method that changes the zipcode of the Address object returned by the getAddress method. It will change the zipcode in the Person object too.

Subsection 3.6.6 Chaining Method Calls

Programmers will sometimes call methods in a chain, one after another on the same line, for example p.getAddress().getCity(). The method calls are separated by a dot and the return value of each method is used as the object for the next method call. It is important to know the types that each method returns so that you can call the methods of that object. For example, if the first method returns a String, the next method must be one that is defined for a String object. In the code below, the getAddress method returns an Address object, and then the getCity method is called on that Address object.
Person p1 = new Person("Skyler",
                new Address("123 Main St",
                "Anytown", "Anystate","12345"));
System.out.println( p1.getAddress().getCity() );

Activity 3.6.5.

Activity 3.6.6.

Activity 3.6.7.

Activity 3.6.8.

Print out the first two letters of the state in the address of the person below by chaining together the method calls in one line.

Subsection 3.6.7 Coding Challenge : Friends and Birthdays

In this challenge, you will create a class Friend which keeps track of your friends’ names and birthdays using another class called Date.
  1. Create a class called Date with the following instance variables as ints: month, day, and year. Add a constructor, getters, and setters for the instance variables.
  2. Create a class called Friend with the following instance variables: name and birthdate which is of type Date.
  3. Add a constructor that takes 2 parameters and initializes the instance variables, making sure to copy the birthdate parameter object.
  4. Add a method called isBirthday that returns true if the birthdate matches the given date parameter.
  5. Test your methods in the main method by creating a Friend object with a friend’s name and birthday. Then, create a Date object with today’s date and call the isBirthday method to see if it is the friend’s birthday.

Project 3.6.9.

Complete the Date and Friend classes below. The Friend class should have a method isBirthday that returns true if the birthdate matches the given date parameter. Test your methods in the main method.

Subsection 3.6.8 Summary

  • (AP 3.4.A.3) When a mutable object is a constructor parameter, the instance variable should be initialized with a copy of the referenced object. In this way, the instance variable does not hold a reference to the original object, and methods are prevented from modifying the state of the original object.
  • (AP 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.
  • (AP 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.
  • (AP 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.
You have attempted of activities on this page.