|
Editor's note: The following article is an excerpt from the upcoming book Practical Java published by Addison-Wesley. You can pre-order this book through Amazon.com. Read our interview with author Peter Haggar.
Java provides two different types: reference types and primitive, or built-in, types. In addition, wrapper classes are provided for each primitive type. If you need a variable for an integer, do you use the primitive int or an object of the Integer class? If you need to declare a boolean type, do you use the primitive boolean or an object of the Boolean class? The following table shows the primitive types along with their object wrapper classes.
| Primitive type |
Wrapperclass |
| boolean |
Boolean |
| char |
Character |
| byte |
Byte |
| short |
Short |
| int |
Integer |
| long |
Long |
| float |
Float |
| double |
Double |
References and primitives behave altogether differently and possess different semantics. For example, assume that there are two local variables in a method. One local variable is a primitive of type int, and the other is an object reference to an Integer object:
| int i = 5; |
//primitive type |
| Integer j = new |
|
| Integer(10); |
//object reference |
These two variables, which are stored in a local variable table on the Java stack, are represented completely differently. (For the remainder of this discussion, the generic term stack is used in place of local variable table.) The primitive type int
is stored on the stack as a 32-bit word. (The exact size of an int, or other primitive type, is dependent on the JVM implementation. Most JVM implementations use 32 bits to represent an int.) The stack entry for the Integer object is not the object itself but an object reference.
All objects in Java are accessed by object references. Object references are pointers
to an area in the heap where the storage for the object exists. When you declare
a primitive type, you declare the storage for the type itself. The previous two lines
of code are represented like this:

Reference and primitive types have different characteristics and usages. These include size and speed issues, in what type of data structure the type is stored, and the default values assigned when references and primitives are used as instance data of a class. The default value for an object reference instance variable is null , whereas the default value for a primitive type instance variable varies depending on its type.
For many programs, the code will contain both primitive types and their object wrappers. Using both of these types and knowing how they correctly interact and coexist is problematic when testing for equality. Programmers must understand how these types work and interact together to avoid buggy code.
For example, you cannot call a method on a primitive type, but you can, of course,
call a method on an object:
| int j = 5; |
|
| j.hashCode() ; |
//ERROR |
| //... |
|
| Integer i = new Integer(5); |
|
| i.hashCode() ; |
//OK |
Using a primitive type eliminates the need to call new and create an object. This saves time and space. Mixing primitive types and objects can also create unexpected results with regard to assignment. What looks like innocent code might not do what you expect. For example:
import java.awt.Point;
class Assign
{
public static void main(String args[])
{
int a = 1;
int b = 2;
Point x = new Point(0,0);
Point y = new Point(1,1); //1
System.out.println("a is " + a);
System.out.println("b is " + b);
System.out.println("x is " + x);
System.out.println("y is " + y);
System.out.println("Performing assignment and " +
"setLocation()...");
a = b;
a++;
x = y; //2
x.setLocation(5,5); //3
System.out.println("a is " + a);
System.out.println("b is " + b);
System.out.println("x is " + x);
System.out.println("y is " + y);
}
}
This code generates the following output:
a is 1
b is 2
x is java.awt.Point[x=0,y=0]
y is java.awt.Point[x=1,y=1]
Performing assignment and setLocation()...
& a is 3
b is 2
x is java.awt.Point[x=5,y=5]
y is java.awt.Point[x=5,y=5]
The results of modifying the integers a and b are not surprising. The integer variable a is assigned the value of b and then a is increased by 1. The output reflects what we expect to happen. What might be surprising, however, is the output of the x and y objects after the assignment and the call to setLocation. How can x and y have the same values when we specifically called setLocation on x after the assignment of x = y? After all, we assigned y to x and then changed x just as we did with the integers a and b.
The confusion is due to the usage of primitive types and objects. Assignment is
not doing anything different with these types. It might, however, appear that it is.
Assignment makes the value on the left side of the equal (=) sign equal to
the value on the right side. This is obvious for primitive types such as the
int a and
b above. For non-primitive types, such as the
Point objects, assignment
modifies the object reference and not the object. Therefore, after the statement
x = y;
x is equal to
y
. In other words, because
x and
y
are object references, they now
refer to the same object. Therefore any changes to
x
also change
y
. Here is what
the situation looks like after the code at //1 is executed:
After the assignment at //2, the situation is as follows:
When setLocation is called at //3, the method is executed on the object that
x
references. Because x references the same Point object as
y
, we now have:

Because x
and
y
refer to the same object, all methods that are executed on
x
work with the same object as methods that are executed on
y.
It is important to distinguish between reference and primitive types and to understand
the semantics of references. Failure to do so results in code that does not
behave as intended.
|  |