Note: The course notes on objects, arrays, and references will be useful.
All objects (instances of classes) and arrays in Java are accessed through references. A reference is the same thing as a pointer in C. A reference is a value which “points to” an object or array. You can think of a reference value as being the address (location in memory) of the object or array.
Unlike pointers in C/C++, Java references are completely opaque, meaning that they cannot be converted into a numeric address. For example, in C:
This code snippet will print something like
The variable myPoint contains the address 0x601010
There is no way in Java to turn a reference into a numeric address.
Because all objects and arrays in Java are accessed through references, assigning the value of one class-type or array-type variable to another variable copies the reference, not the contents.
What happens when this code is executed?
It is obvious that the first set of System.out.println statements will print the output
p: x=4, y=5 q: x=13, y=14
It is also reasonably obvious that the second set of System.out.println statements will print the output
p: x=13, y=14 q: x=13, y=14
This is because after the assignment p = q;, both p and q contain the same value.
The interesting question is: what happens when the x field of q is modified? Will the assignment affect the x field of p?
The answer, of course, is yes: following the assignment p = q;, both p and q point to the same Point object. In other words, the reference contained in q - what q points to -is copied, not the contents of the object q points to.
This is best illustrated with a “boxes and arrows” diagram. Here’s what things look like at point (1) in the code example:
Both p and q point to different objects.
The assignment p = q;, makes p point to the same thing q points to, as illustrated by the situation at point (2) above:
So, after the assignment q.x = 42; is executed, the change is also reflected in the value of p.x at point (3):
The key idea in object-oriented programming is that a user defined data type (a class) has not only member variables but also member functions. “Method” is another term for member function.
In the code above, when we printed the values of the x and y coordinates of a Point object, we did so using code like the following:
There are two problems with this code:
- It violates encapsulation - we shouldn’t be directly accessing the fields of the Point class
- Looking at the code does not really tell the reader what the code is doing - it’s hard to understand.
We can remedy the second problem by creating an instance method called print which prints the values of the x and y coordinates of a Point object. Let’s revise the Point class:
Now, if we want to print the coordinate values of p, we do so as:
This code is much easier to understand. Let’s look closely at the second line:
This is the “Zen” of object-oriented programming:
find an object, call a method on it.
In the line above,
- p is a Point object
- print is a method belonging to the Point class, and supported by all instances (objects) of the Point class
The Point class’s print method works because within an instance method belonging to a class, a field (e.g., x and y for the Point class) refers to the field residing within the object on which the method is called. We refer to this object as the instance or the receiver.
We can fix the first problem with the original Point class - violating encapsulation - by making the fields private. This means that only methods in the Point class are allowed to directly access those fields.
Encapsulation is important because it means that if we ever need to change the fields in the Point class -adding a field, removing a field, changing the type of a field, etc. - the only code that we will need to change will be the methods in the Point class. Reducing the impact of code changes becomes very important when we write larger programs. Properly encapsulated code is also easier to understand because all operations on a user-defined data type are performed by calling methods.
Here’s the revised class:
Now we have a problem - our code snippet which created and used Point objects no longer works, because it cannot directly access the x and y fields of Point objects.
The print method takes care of printing the x and y values of a Point object, so we can change our original System.out.println statements into calls to the Point class’s print method.
However, we still need a way to set the initial values of the x and y fields of a Point object once it’s been created. One way to do this is using setter methods. Here’s the final version of the class:
Here’s the final version of our original code snippet:
This version is considerably simpler than the original.
Currently, our Point class needs setX and setY methods in order to be able to initialize the x and y fields of newly-created Point objects. We cannot access these fields directly because we made them private in order to enforce encapsulation.
Java provides a special kind of method whose purpose is to initialize the fields of a newly-created object: the constructor. Here’s a revised version of the Point class containing a constructor method:
Using the revised Point class, we can create new Point objects as follows:
The important things to understand about constructors are
- the name of a constructor method is the same as the name of the class
- constructor methods have no return type
- argument values passed when the new operator is called -are passed as parameters to the constructor method
- within a constructor method, this (the reference to the instance) is the newly-created object whose fields are being initialized by the constructor
In the example above, the argument values are 4 and 5, so those are the values passed as the x and y parameters to the constructor method.
Note that you may have as many constructors in a class as you would like. For example, we could add an additional constructor to the Point class that initializes both x and y to 0:
To use this constructor, we don’t pass any argument values when using the new operator:
Here are some of the important things you should now know:
- a class is a used-defined data type, much like a
structtype in C
- an object is an instance of a class
- objects are accessed by reference; a variable whose type is a class stores a reference to an object
- two variables may contain references to the same object
- if p and q are variables whose type is a class, then the assignment p = q makes p point to the same object that q points to