Chapter 4. Reference Types
Reference types hold references to objects and provide a means to access those objects stored somewhere in memory. The memory locations are irrelevant to programmers. All reference types are a subclass of type java.lang.Object
.
Table 4-1 lists the five Java reference types.
Reference type | Brief description |
---|---|
Annotation |
Provides a way to associate metadata (data about data) with program elements. |
Array |
Provides a fixed-size data structure that stores data elements of the same type. |
Class |
Designed to provide inheritance, polymorphism, and encapsulation. Usually models something in the real world and consists of a set of values that holds data and a set of methods that operates on the data. |
Enumeration |
A reference for a set of objects that represents a related set of choices. |
Interface |
Provides a public API and is “implemented” by Java classes. |
Comparing Reference Types to Primitive Types
There are two type categories in Java: reference types and primitive types. Table 4-2 shows some of the key differences between them. See Chapter 3 for more details.
Reference types | Primitive types |
---|---|
Unlimited number of reference types, as they are defined by the user. |
Consists of |
Memory location stores a reference to the data. |
Memory location stores actual data held by the primitive type. |
When a reference type is assigned to another reference type, both will point to the same object. |
When a value of a primitive is assigned to another variable of the same type, a copy is made. |
When an object is passed into a method, the called method can change the contents of the object passed to it but not the address of the object. |
When a primitive is passed into a method, only a copy of the primitive is passed. The called method does not have access to the original primitive value and therefore cannot change it. The called method can change the copied value. |
Default Values
Default values are the values assigned to instance variables in Java, when no initialization value has been explicitly set.
Instance and Local Variable Objects
Instance variables (i.e., those declared at the class level) have a default value of null. null
references nothing.
Local variables (i.e., those declared within a method) do not have a default value, not even a value of null
. Always initialize local variables because they are not given a default value. Checking an uninitialized local variable object for a value (including a value of null
) will result in a compile-time error.
Although object references with a value of null
do not refer to any object on the heap, objects set to null
can be referenced in code without receiving compile-time or runtime errors:
LocalDate
birthdate
=
null
;
// This will compile
if
(
birthdate
==
null
)
{
System
.
out
.
println
(
birthdate
);
}
$
null
Invoking a method on a reference variable that is null
or using the dot operator on the object will result in a java.lang.NullPointerException
:
final
int
MAX_LENGTH
=
20
;
String
partyTheme
=
null
;
/*
* java.lang.NullPointerException is thrown
* since partyTheme is null
*/
if
(
partyTheme
.
length
()
>
MAX_LENGTH
)
{}
Arrays
Arrays are always given a default value whether they are declared as instance variables or local variables. Arrays that are declared but not initialized are given a default value of null
.
In the following code, the gameList1
array is initialized, but not the individual values, meaning that the object references will have a value of null
. Objects have to be added to the array:
/*
* The declared arrays named gameList1 and
* gameList2 are initialized to null by default
*/
Game
[]
gameList1
;
Game
gameList2
[];
/*
* The following array has been initialized but
* the object references are still null because
* the array contains no objects
*/
gameList1
=
new
Game
[
10
];
// Add a Game object to the list, so it has one object
gameList1
[
0
]
=
new
Game
();
Multidimensional arrays in Java are actually arrays of arrays. They may be initialized with the new operator or by placing their values within braces. Multidimensional arrays may be uniform or nonuniform in shape:
// Anonymous array
int
twoDimensionalArray
[][]
=
new
int
[
6
][
6
];
twoDimensionalArray
[
0
][
0
]
=
100
;
int
threeDimensionalArray
[][][]
=
new
int
[
2
][
2
][
2
];
threeDimensionalArray
[
0
][
0
][
0
]
=
200
;
int
varDimensionArray
[][]
=
{{
0
,
0
},{
1
,
1
,
1
},
{
2
,
2
,
2
,
2
}};
varDimensionArray
[
0
][
0
]
=
300
;
Anonymous arrays allow for the creation of a new array of values anywhere in the code base:
// Examples using anonymous arrays
int
[]
luckyNumbers
=
new
int
[]
{
7
,
13
,
21
};
int
totalWinnings
=
sum
(
new
int
[]
{
3000
,
4500
,
5000
});
Conversion of Reference Types
An object can be converted to the type of its superclass (widening) or any of its subclasses (narrowing).
The compiler checks conversions at compile time, and the Java Virtual Machine (JVM) checks conversions at runtime.
Narrowing Conversions
-
Narrowing converts a more general type into a more specific type.
-
Narrowing is a conversion of a superclass to a subclass.
-
An explicit cast is required. To cast an object to another object, place the type of object to which you are casting in parentheses immediately before the object you are casting.
-
Narrowing may result in a loss of data/precision.
Objects cannot be converted to an unrelated type—that is, a type other than one of its subclasses or superclasses. Doing so will generate an inconvertible types
error at compile time. The following is an example of a conversion that will result in a compile-time error due to inconvertible types
:
Object
o
=
new
Object
();
String
s
=
(
Integer
)
o
;
// compile-time error
Converting Between Primitives and Reference Types
The automatic conversion of primitive types to reference types, and vice versa, is called autoboxing and unboxing, respectively. For more information, refer back to Chapter 3.
Passing Reference Types into Methods
When an object is passed into a method as a variable:
-
A copy of the reference variable is passed, not the actual object.
-
The caller and the called methods have identical copies of the reference.
-
The caller will also see any changes the called method makes to the object. Passing a copy of the object to the called method will prevent it from making changes to the original object.
-
The called method cannot change the address of the object, but it can change the contents of the object.
The following example illustrates passing reference types and primitive types into methods and the effects on those types when changed by the called method:
void
roomSetup
()
{
// Reference passing
Table
table
=
new
Table
();
table
.
setLength
(
72
);
// Length will be changed
modTableLength
(
table
);
// Primitive passing
// Value of chairs not changed
int
chairs
=
8
;
modChairCount
(
chairs
);
}
void
modTableLength
(
Table
t
)
{
t
.
setLength
(
36
);
}
void
modChairCount
(
int
i
)
{
i
=
10
;
}
Comparing Reference Types
Reference types are comparable in Java. Equality operators and the equals
method can be used to assist with comparisons.
Using the Equality Operators
The !=
and ==
equality operators are used to compare the memory locations of two objects. If the memory addresses of the objects being compared are the same, the objects are considered equal. These equality operators are not used to compare the contents of two objects.
In the following example, guest1
and guest2
have the same memory address, so the statement "They are equal"
is output:
String
guest1
=
new
String
(
"name"
);
String
guest2
=
guest1
;
if
(
guest1
==
guest2
)
System
.
out
.
println
(
"They are equal"
);
In the following example, the memory addresses are not equal, so the statement "They are not equal"
is output:
String
guest1
=
new
String
(
"name"
);
String
guest2
=
new
String
(
"name"
);
if
(
guest1
!=
guest2
)
System
.
out
.
println
(
"They are not equal"
);
Using the equals() Method
To compare the contents of two class objects, the equals()
method from class Object
can be used or overridden. When the equals()
method is overridden, the hashCode()
method should also be overridden. This is done for compatibility with hash-based collections such as HashMap()
and HashSet()
.
Tip
By default, the equals()
method uses only the == operator for comparisons. This method has to be overridden to really be useful.
For example, if you want to compare values contained in two instances of the same class, you should use a programmer-defined equals()
method.
Comparing Strings
There are two ways to check whether strings are equal in Java, but the definition of “equal” for each of them is different:
-
The
equals()
method compares two strings, character by character, to determine equality. This is not the default implementation of theequals()
method provided by theObject
class. This is the overridden implementation provided byString
class. -
The == operator checks to see whether two object references refer to the same instance of an object.
Here is a program that shows how strings are evaluated using the equals()
method and the == operator (for more information on how strings are evaluated, see “String Literals” in Chapter 2):
class
MyComparisons
{
// Add string to pool
String
first
=
"chairs"
;
// Use string from pool
String
second
=
"chairs"
;
// Create a new string
String
third
=
new
String
(
"chairs"
);
void
myMethod
()
{
/*
* Contrary to popular belief, this evaluates
* to true. Try it!
*/
if
(
first
==
second
)
{
System
.
out
.
println
(
"first == second"
);
}
// This evaluates to true
if
(
first
.
equals
(
second
))
{
System
.
out
.
println
(
"first equals second"
);
}
// This evaluates to false
if
(
first
==
third
)
{
System
.
out
.
println
(
"first == third"
);
}
// This evaluates to true
if
(
first
.
equals
(
third
))
{
System
.
out
.
println
(
"first equals third"
);
}
}
// End myMethod()
}
//end class
Copying Reference Types
When reference types are copied, either a copy of the reference to an object is made, or an actual copy of the object is made, creating a new object. The latter is referred to as cloning in Java.
Copying a Reference to an Object
When copying a reference to an object, the result is one object with two references. In the following example, closingSong
is assigned a reference to the object pointed to by lastSong
. Any changes made to lastSong
will be reflected in closingSong
, and vice versa:
Song
lastSong
=
new
Song
();
Song
closingSong
=
lastSong
;
Cloning Objects
Cloning results in another copy of the object, not just a copy of a reference to an object. Cloning is not available to classes by default. Note that cloning is usually very complex, so you should consider a copy constructor instead, for the following reasons:
-
For a class to be cloneable, it must implement the interface
Cloneable
. -
The protected method
clone()
allows for objects to clone themselves. -
For an object to clone an object other than itself, the
clone()
method must be overridden and made public by the object being cloned. -
When cloning, a cast must be used because
clone()
returns typeobject
. -
Cloning can throw a
CloneNotSupportedException
.
Shallow and deep cloning
Shallow and deep cloning are the two types of cloning in Java.
In shallow cloning, primitive values and the references in the object being cloned are copied. Copies of the objects referred to by those references are not made.
In the following example, leadingSong
will be assigned the value of length
because it is a primitive type. Also, leadingSong
will be assigned the references to title
, artist
, and year
because they are references to types:
Class
Song
{
String
title
;
Artist
artist
;
float
length
;
Year
year
;
void
setData
()
{...}
}
Song
firstSong
=
new
Song
();
try
{
// Make an actual copy by cloning
Song
leadingSong
=
(
Song
)
firstSong
.
clone
();
}
catch
(
CloneNotSupportedException
cnse
)
{
cnse
.
printStackTrace
();
}
// end
In deep cloning, the cloned object makes a copy of each of its object’s fields, recursing through all other objects referenced by it. A deep-clone method must be defined by the programmer, as the Java API does not provide one. Alternatives to deep cloning are serialization and copy constructors. (Copy constructors are often preferred over serialization.)
Memory Allocation and Garbage Collection of Reference Types
When a new object is created, memory is allocated. When there are no references to an object, the memory that object used can be reclaimed during the garbage collection process. For more information on this topic, see Chapter 11.
Get Java Pocket Guide, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.