Chapter 10 Objects
As we learned in the previous chapter, an object is a collection of data that provides a set of methods.
For example, a String
is a collection of characters that provides methods like charAt
and substring
.
Java is an “object-oriented” language, which means that it uses objects to represent data and provide methods related to them. This way of organizing programs is a powerful design concept, and we will introduce it a little at a time throughout the remainder of the book.
In this chapter, we introduce two new types of objects: Point
and Rectangle
.
We show how to write methods that take objects as parameters and produce objects as return values.
We also take a look at the source code for the Java library.
10.1 Point objects
The java.awt
package provides a class named Point
intended to represent the coordinates of a location in a Cartesian plane.
In mathematical notation, points are often written in parentheses with a comma separating the coordinates.
For example, (0,0) indicates the origin, and (x,y) indicates the point x units to the right and y units up from the origin.
In order to use the Point
class, you have to import it:
import java.awt.Point; |
Then, to create a new point, you have to use the new
operator:
Point blank; blank = new Point(3, 4); |
The first line declares that blank
has type Point
.
The second line creates the new Point
with the given arguments as coordinates.
The result of the new
operator is a reference to the new object.
So blank
contains a reference to the new Point
object.
Figure 10.1 shows the result.
As usual, the name of the variable blank
appears outside the box, and its value appears inside the box.
In this case, the value is a reference, which is represented with an arrow.
The arrow points to the new object, which contains two variables, x
and y
.
10.2 Attributes
Variables that belong to an object are usually called attributes, but you might also see them called “fields”. To access an attribute of an object, Java uses dot notation. For example:
int x = blank.x; |
The expression blank.x
means “go to the object blank
refers to, and get the value of the attribute x
.”
In this case, we assign that value to a local variable named x
.
There is no conflict between the local variable named x
and the attribute named x
.
The purpose of dot notation is to identify which variable you are referring to unambiguously.
You can use dot notation as part of an expression. For example:
System.out.println(blank.x + ", " + blank.y); int sum = blank.x * blank.x + blank.y * blank.y; |
The first line displays 3, 4
; the second line calculates the value 25
.
10.3 Objects as parameters
You can pass objects as parameters in the usual way. For example:
public static void printPoint(Point p) { System.out.println("(" + p.x + ", " + p.y + ")"); } |
This method takes a point as an argument and displays its attributes in parentheses.
If you invoke printPoint(blank)
, it displays (3, 4)
.
But we don’t really need a method like printPoint
, because if you invoke System.out.println(blank)
you get:
java.awt.Point[x=3,y=4] |
Point
objects provide a method called toString
that returns a string representation of a point.
When you call println
with objects, it automatically calls toString
and displays the result.
In this case, it shows the name of the type (java.awt.Point
) and the names and values of the attributes.
As another example, we can rewrite the distance
method from Section 6.2 so that it takes two Point
s as parameters instead of four double
s.
public static double distance(Point p1, Point p2) { int dx = p2.x - p1.x; int dy = p2.y - p1.y; return Math.sqrt(dx * dx + dy * dy); } |
Passing objects as parameters makes the source code more readable and less error-prone, because related values are bundled together.
10.4 Objects as return types
The java.awt
package also provides a class called Rectangle
.
To use it, you have to import it:
import java.awt.Rectangle; |
Rectangle
objects are similar to points, but they have four attributes: x
, y
, width
, and height
.
The following example creates a Rectangle
object and makes the variable box
refer to it:
Rectangle box = new Rectangle(0, 0, 100, 200); |
Figure 10.2 shows the effect of this assignment.
If you run System.out.println(box)
, you get:
java.awt.Rectangle[x=0,y=0,width=100,height=200] |
Again, println
uses the toString
method provided by Rectangle
, which knows how to display Rectangle
objects.
You can write methods that return objects.
For example, findCenter
takes a Rectangle
as an argument and returns a Point
with the coordinates of the center of the rectangle:
public static Point findCenter(Rectangle box) { int x = box.x + box.width / 2; int y = box.y + box.height / 2; return new Point(x, y); } |
The return type of this method is Point
.
The last line creates a new Point
object and returns a reference to it.
10.5 Mutable objects
You can change the contents of an object by making an assignment to one of its attributes.
For example, to “move” a rectangle without changing its size, you can modify the x
and y
values:
Rectangle box = new Rectangle(0, 0, 100, 200); box.x = box.x + 50; box.y = box.y + 100; |
The result is shown in Figure 10.3.
We can encapsulate this code in a method and generalize it to move the rectangle by any amount:
public static void moveRect(Rectangle box, int dx, int dy) { box.x = box.x + dx; box.y = box.y + dy; } |
The variables dx
and dy
indicate how far to move the rectangle in each direction.
Invoking this method has the effect of modifying the Rectangle
that is passed as an argument.
Rectangle box = new Rectangle(0, 0, 100, 200); moveRect(box, 50, 100); System.out.println(box); |
Modifying objects by passing them as arguments to methods can be useful. But it can also make debugging more difficult, because it is not always clear which method invocations modify their arguments.
Java provides a number of methods that operate on Point
s and Rectangle
s.
For example, translate
has the same effect as moveRect
, but instead of passing the rectangle as an argument, you use dot notation:
box.translate(50, 100); |
This line invokes the translate
method for the object that box
refers to.
As a result, the box
object is updated directly.
This example is a good illustration of object-oriented programming.
Rather than write methods like moveRect
that modify one or more parameters, we apply methods to objects themselves using dot notation.
10.6 Aliasing
Remember that when you assign an object to a variable, you are assigning a reference to an object. It is possible to have multiple variables that refer to the same object. The state diagram in Figure 10.4 shows the result.
Rectangle box1 = new Rectangle(0, 0, 100, 200); Rectangle box2 = box1; |
Notice how box1
and box2
are aliases for the same object, so any changes that affect one variable also affect the other.
This example adds 50 to all four sides of the rectangle, so it moves the corner up and to the left by 50, and it increases the height and width by 100:
System.out.println(box2.width); box1.grow(50, 50); System.out.println(box2.width); |
The first line displays 100, which is the width of the Rectangle
referred to by box2
.
The second line invokes the grow
method on box1
, which stretches the Rectangle
horizontally and vertically.
The effect is shown in Figure 10.5.
When we make a change using box1
, we see the change using box2
.
Thus, the value displayed by the third line is 200, the width of the expanded rectangle.
10.7 The null keyword
When you create an object variable, remember that you are storing a reference to an object.
In Java, the keyword null
is a special value that means “no object”.
You can declare and initialize object variables this way:
Point blank = null; |
The value null
is represented in state diagrams by a small box with no arrow, as in Figure 10.6.
If you try to use a null
value, either by accessing an attribute or invoking a method, Java throws a NullPointerException
.
Point blank = null; int x = blank.x; // NullPointerException blank.translate(50, 50); // NullPointerException |
On the other hand, it is legal to pass a null reference as an argument or receive one as a return value.
For example, null
is often used to represent a special condition or indicate an error.
10.8 Garbage collection
In Section 10.6, we saw what happens when more than one variable refers to the same object. What happens when no variables refer to an object?
Point blank = new Point(3, 4); blank = null; |
The first line creates a new Point
object and makes blank
refer to it.
The second line changes blank
so that instead of referring to the object, it refers to nothing.
In the state diagram, we remove the arrow between them, as in Figure 10.7.
If there are no references to an object, there is no way to access its attributes or invoke a method on it. From the programmer’s view, it ceases to exist. However it’s still present in the computer’s memory, taking up space.
As your program runs, the system automatically looks for stranded objects and reclaims them; then the space can be reused for new objects. This process is called garbage collection.
You don’t have to do anything to make garbage collection happen, and in general don’t have to be aware of it. But in high-performance applications, you may notice a slight delay every now and then when Java reclaims space from discarded objects.
10.9 Class diagrams
To summarize what we’ve learned so far, Point
and Rectangle
objects each have their own attributes and methods.
Attributes are an object’s data, and methods are an object’s code.
An object’s class defines which attributes and methods it will have.
In practice, it’s more convenient to look at high-level pictures than to examine the source code. Unified Modeling Language (UML) defines a standard way to summarize the design of a class.
As shown in Figure 10.8, a class diagram is divided into two sections.
The top half lists the attributes, and the bottom half lists the methods.
UML uses a language-independent format, so rather than showing int x
, the diagram uses x: int.
In contrast to state diagrams, which visualize objects (and variables) at run-time, a class diagram visualizes the source code at compile-time.
Both Point
and Rectangle
have additional methods; we are only showing the ones introduced in this chapter.
See the documentation for these classes to learn more about what they can do.
10.10 Java library source
Throughout the book, you have used classes from the Java library including System
, String
, Scanner
, Math
, Random
, and others.
You may not have realized that these classes are written in Java.
In fact, you can take a look at the source code to see how they work.
The Java library contains thousands of files, many of which are thousands of lines of code. That’s more than one person could read and understand fully, so please don’t be intimidated!
Because it’s so large, the library source code is stored in a file named src.zip
.
Take a few minutes to locate this file on your machine:
-
On Linux, it’s likely under:
/usr/lib/jvm/openjdk-8/
(You might need to install the openjdk-8-source package.) - On OS X, it’s likely under:
/Library/Java/JavaVirtualMachines/jdk.../Contents/Home/
- On Windows, it’s likely under:
C:\Program Files\Java\jdk...\
When you open (or unzip) the file, you will see folders that correspond to Java packages.
For example, open the java folder and then open the awt folder.
You should now see Point.java and Rectangle.java, along with the other classes in the java.awt
package.
Open Point.java in your editor and skim through the file. It uses language features we haven’t yet discussed, so you probably won’t understand everything. But you can get a sense of what professional Java software looks like by browsing through the library.
Notice how much of Point.java is documentation.
Each method is thoroughly commented, including @param
, @return
, and other Javadoc tags.
Javadoc reads these comments and generates documentation in HTML.
You can see the results by reading the documentation for the Point
class, which you can find by doing a web search for “Java Point”.
Now take a look at Rectangle
’s grow
and translate
methods.
There is more to them than you may have realized, but that doesn’t limit your ability to use these methods in a program.
To summarize the whole chapter, objects encapsulate data and provide methods to access and modify the data directly. Object-oriented programming makes it possible to hide messy details so that you can more easily use and understand code that other people wrote.
10.11 Vocabulary
- attribute:
- One of the named data items that make up an object.
- dot notation:
-
Use of the dot operator (
.
) to access an object’s attributes or methods. - object-oriented:
- A way of organizing code and data into objects, rather than independent methods.
- garbage collection:
- The process of finding objects that have no references and reclaiming their storage space.
- UML:
- Unified Modeling Language, a standard way to draw diagrams for software engineering.
- class diagram:
- An illustration of the attributes and methods for a class.
10.12 Exercises
The code for this chapter is in the ch10 directory of ThinkJavaCode. See page ?? for instructions on how to download the repository. Before you start the exercises, we recommend that you compile and run the examples.
- For the following program, draw a stack diagram showing the local variables and parameters of
main
andriddle
just beforeriddle
returns. Use arrows to show which objects each variable references. - What is the output of the program?
- Is the
blank
object mutable or immutable? How can you tell?
public static int riddle(int x, Point p) { x = x + 7; return x + p.x + p.y; } |
public static void main(String[] args) { int x = 5; Point blank = new Point(1, 2); System.out.println(riddle(x, blank)); System.out.println(x); System.out.println(blank.x); System.out.println(blank.y); } |
- Draw a stack diagram showing the state of the program just before
distance
returns. Include all variables and parameters, and show the objects those variables refer to. - What is the output of this program? (Can you tell without running it?)
public static double distance(Point p1, Point p2) { int dx = p2.x - p1.x; int dy = p2.y - p1.y; return Math.sqrt(dx * dx + dy * dy); } public static Point findCenter(Rectangle box) { int x = box.x + box.width / 2; int y = box.y + box.height / 2; return new Point(x, y); } |
public static void main(String[] args) { Point blank = new Point(5, 8); Rectangle rect = new Rectangle(0, 2, 4, 4); Point center = findCenter(rect); double dist = distance(center, blank); System.out.println(dist); } |
- Draw a diagram that shows the state of the program just before the end of
main
. Include all local variables and the objects they refer to. - What is the output of the program?
- At the end of
main
, arep1
andp2
aliased? Why or why not?
public static void printPoint(Point p) { System.out.println("(" + p.x + ", " + p.y + ")"); } public static Point findCenter(Rectangle box) { int x = box.x + box.width / 2; int y = box.y + box.height / 2; return new Point(x, y); } |
public static void main(String[] args) { Rectangle box1 = new Rectangle(2, 4, 7, 9); Point p1 = findCenter(box1); printPoint(p1); box1.grow(1, 1); Point p2 = findCenter(box1); printPoint(p2); } |
You might be sick of the factorial method by now, but we’re going to do one more version.
- Create a new program called Big.java and write (or reuse) an iterative version of
factorial
. - Display a table of the integers from 0 to 30 along with their factorials. At some point around 15, you will probably see that the answers are not right anymore. Why not?
BigInteger
is a Java class that can represent arbitrarily big integers. There is no upper bound except the limitations of memory size and processing speed. Take a minute to read the documentation, which you can find by doing a web search for “Java BigInteger”.- To use BigIntegers, you have to import
java.math.BigInteger
at the beginning of your program. - There are several ways to create a BigInteger, but the simplest uses
valueOf
. The following code converts an integer to a BigInteger:int x = 17; BigInteger big = BigInteger.valueOf(x); - Since BigIntegers are not primitive types, the usual math operators don’t work.
Instead, we have to use methods like
add
. To add two BigIntegers, invokeadd
on one and pass the other as an argument.BigInteger small = BigInteger.valueOf(17); BigInteger big = BigInteger.valueOf(1700000000); BigInteger total = small.add(big); Try out some of the other methods, like
multiply
andpow
. - Convert
factorial
so that it performs its calculation using BigIntegers and returns a BigInteger as a result. You can leave the parameter alone; it will still be an integer. - Try displaying the table again with your modified factorial method. Is it correct up to 30? How high can you make it go?
- Are BigInteger objects mutable or immutable? How can you tell?
public static int pow(int x, int n) { if (n == 0) return 1; // find x to the n/2 recursively int t = pow(x, n / 2); // if n is even, the result is t squared // if n is odd, the result is t squared times x if (n % 2 == 0) { return t * t; } else { return t * t * x; } } |
The problem with this method is that it only works if the result is small enough to be represented by an int
.
Rewrite it so that the result is a BigInteger
.
The parameters should still be integers, though.
You should use the BigInteger
methods add
and multiply
.
But don’t use BigInteger.pow
; that would spoil the fun.