Professional Documents
Culture Documents
Prasun Dewan: COMP 114
Prasun Dewan: COMP 114
Prasun Dewan1
12. Primitive Values vs, Objects
By now, we are used to distinguishing between primitive values and objects. We know, for
instance, that subtyping is provided for object types but not primitive types. In this chapter, we
will focus a bit more in-depth on the differences between these two kinds of values. We will look
at special types, called wrapper classes, that provide bridges between primitive values and
objects. We will also study the difference between the way these two kinds of values are stored in
memory, and the implication this difference has for the assignment statement. This will lead us to
the concept of garbage collection, an important feature of Java.
Wrapper Classes
The class, Object, we saw earlier defines the behavior of all Java objects because the type of
each object is directly or indirectly a subtype of Object. It does not, however, define the
behavior of primitive values, which are not objects. In fact, its not possible to define in Java a
type, Primitive, that describes all primitive values, or a type, Value, that describes all Java
values, because subtyping is not available for primitive types.
Treating primitive values and objects as fundamentally different has two related disadvantages.
First, primitive values cannot be assigned passed as arguments to methods expecting objects. For
instance, given a vector, v, the following is illegal:
v. addElement(4)
because the type of the argument of the add element method is Object. It is not possible to
create another kind of add element method that adds any primitive value or any value because
Java does not have a type to describe the argument of such a method.
Second, primitive types are second-class types in that the benefits of inheritance are not
applicable to them. For instance, we cannot create a new primitive type, say, natural,that
describes natural numbers (positive integers) and inherits the implementation of arithmetic
operations from int.
Primitive types are also present in other object-oriented programming languages such as C++, but
some more pure object-oriented languages such as Smalltalk only have object types. Primitive
types can be more efficiently implemented than object types, which was probably the reason for
including them in Java. However, this benefit comes at the expense of ease of programming and
elegance.
Fortunately, for each primitive type, Java defines a corresponding class, called a wrapper for that
type, and provides mechanisms for automatically converting among instances of a primitive type
and the corresponding wrapper class. A wrapper class:
provides a constructor to create a wrapper object from the corresponding primitive value,
stores the primitive value in an instance variable,
1
For example, it provides the wrapper class, Integer, for the primitive type, int. The
constructor:
public Integer(int value);
can be used to wrap a new object around the int value passed to the constructor, and the getter
instance method:
public int intValue()
can be used to retrieve the value.
To illustrate how a wrapper class may be used, consider the problem of storing the primitive
value, 4 in the vector, v. We can wrap the value into an integer object, and store this object in the
vector:
v.addElement (new Integer (4))
To extract the primitive value from the vector, we must first access the corresponding object, and
then use its getter method to unwrap the primitive value:
int i = v.elementAt(3).intValue()
Here we are assuming that the wrapper object was stored at the third position of the vector.
Besides wrapping and unwrapping primitive values, a wrapper class may also provide useful
class methods for manipulating these values. For example, we have seen that we have used a class
method to convert a string to the corresponding int value:
int i = Integer.parseInt(4);
and another Integer class method to convert an int value to the corresponding string:
String s = Integer.toString(4);
We must look at the documentation of each wrapper class to find what methods it provides.
The wrapper classes for the other primitive types, double, char, boolean, float, long,
and short are Double, Character, Boolean, Float, Long and Short. The constructors
and getter methods for wrapping and unwrapping values of these types are:
public Double(double value);
public double doubleValue();
public Character(char value);
public char charValue();
public Boolean(boolean value);
public boolean booleanValue();
public Float(float value);
public float floatValue();
public Long(long value);
public long longValue();
public Short(short value);
public short shortValue();
5.5
5.5
16
5.5
double d
int i
5.5
double e
48
52
80
Just in this chapter, we will break the Java convention of starting the names of variables with
lowercase letters so that we can easily distinguish between primitive and object variables.
consisting of a single double instance variable is created. The sizes of the two objects are
different because of the difference in the sizes of their instance variable. However, in both cases,
the object block consists of a single variable.
Now consider the following class:
public class APoint implements Point {
int x,y;
public APoint (int initX, int initY) {
x = initX; y = initY;
}
public void setX(int newVal) {
x = newVal;
}
}
The figure below shows how the following assignment of an instance of this class is processed:
Point P = new APoint( 50, 100) ;
An instance of this class has a memory block consisting of two consecutive int blocks, as shown
in the figure.
5.5
Double@8
16
Integer@16
48
Double D
60
16
Integer I
72
Number N
80
50
APoint@80
100
96
80
APoint P
Now consider the following subclass of APoint, called ABoundedPoint, that declares two APoint
instance variables defining a rectangular area defining user-specified bounds of the point:
public class ABoundedPoint extends APoint {
APoint upperLeftCorner, lowerRightCorner;
public ABoundedPoint (int initX, int initY, Point initUpperLeftCorner, Point
initLowerRightCorner) {
super(initX, initY);
upperLeftCorner = initUpperLeftCorner;
lowerRightCorner = initLowerRightCorner;
}
}
Recall that an instance has not only the instance variables defined in its class but also those
defined the superclasses of its class. Therefore, an instance of ABoundedPoint, has a memory
block consisting of memory blocks of four variables, two int variables, each of size 1 word,
inherited from APoint, and two object variables, each also of size 1 word, defined in
ABoundedPoint.
50
APoint@8
50
16
100
APoint@16
100
48
75
ABoundedPoint@48
75
8
16
Java accesses memory at the address associated with I, finds another address there, and then uses
this address to find the integer value. Thus, we do not go directly from a variable address to its
value, but instead, indirectly using the value address or pointer. In some languages, the
programmer is responsible for doing the indirection or dereferencing. For instance, in Pascal,
given an integer pointer variable I, we need to type:
I^
to refer to the value to which it refers. Thus, the equivalent statement in Pascal would be:
writeln(I^)
Java, however, automatically does the dereferencing for us. In fact, we cannot directly access the
address stored in it. Thus, we are not even aware that the variable is a pointer variable.
Sometimes, the term pointer is used for a variable that must be explicitly dereferenced and
reference for a variable that is automatically dereferenced. For this reason, some people say that
Java has no pointer variables. However, we will use these two terms interchangeably.
The special value, null, we saw before, can be assigned to a pointer variable:
Object O = null;
In fact, if we do not initialize a pointer variable, this is the value stored in its memory block. It
denotes the absence of a legal object assigned to the variable. This value is not itself a legal
object, and does not have a class. If we try to access a member of this value:
null.toString();
we get a NullPointerException, which some of you may have already seen. However, we
can use it to determine the value of a pointer variable:
if (O == null)
else
.
Pointer Assignment
Assignment can be tricky with pointers. Consider:
Point p1 = new APoint (50, 50);
Point p2 = p1;
p1.setX(100);
System.out.println(p2.getX());
When p1 is assigned to p2, the pointer stored in p1 is copied into p2, not the object itself. Both
variables share the same object, and thus, the code will print 100 and not 50, as you might
expect.
100
APoint@8
50
16
p1
48
p2
P1
P2
APoint@8
Garbage Collection
What if we now assign to p1, another object:
p1 = new APoint(200, 200);
System.out.println(p2.getX());
The memory contents will now be:
100
APoint@8
50
16
64
p1
48
p2
64
200
APoint@64
200
We will still get the same output, since p2 continues to point to the previous object.
What if we now execute the code:
p2 = p1;
System.out.println(p2.getX());
Now, of course, we will print 200; but what happens to the previous object? No variable refers to
the object, so it is garbage collected. With each object, Java keeps a count, called a reference
count, that tracks how many object variables store pointers to it. When this count goes to zero, it
collects the object as garbage, since no other variable will ever be able to point to it again.
16
64
p1
48
64
p2
64
200
APoint@64
200
Automatic garbage collection is a really nice feature of Java since in most traditional languages
such as C the programmer is responsible for deleting objects. In such languages, the danger is that
we may accidentally delete something that is being used, thereby creating dangling pointers to it,
or forget to delete something that is not being used, thereby creating a memory leak that keeps
wasting memory.
equals() vs. ==
Now consider the following statements:
System.out.println(p1 == p2);
p1 = new APoint (200, 200);
System.out.println(p1 == p2);
The == operator dereferences the two pointers, and compares the resulting objects. When the first
statement is executed, both p1 and p2 refer to the same object. Therefore, we can expect the first
print statement to print true. But what about the second print statement? Both variables refer to
the same logical point in the coordinate space, the point with the coordinates (200,200). However,
they refer to different physical objects, as shown in Figure 7.
72
200
APoint@8
200
16
64
p1
48
72
p2
64
200
APoint@64
200
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
s1 == s2;
System.out.println(s1==s2);
System.out.println(s1.equals(s2));
All print statements except the first one will print true. The equals() method for String
For each new type we define, we must provide our own implementation of equals(). For
example, in the class APoint, we might define equals() as:
public boolean equals(Point otherPoint) {
return x == otherPoint.getX() && y == otherPoint.getY();
}
Now, the statement:
System.out.println(p1.equals(p2))
will indeed print true, assuming the memory configuration of Figure 7.
We can use the getters to retrieve the values of the properties of the original object, and use
them in the copier:
p2 = new APoint (p1.get(), p1.getY())\
This approach requires the copier to do the copying, which may not seem much work in this
case, but would be more if the object to be copied had several instance variables.
We can let the object to be copied do the work and provide a method for copying itself. The
copier simply calls this method:
p2 = p1.copy()
The method would do the work that the copier was doing in the previous case:
public Point copy() { return new APoint(x, y); }
Let us consider a more complicated class such as ABoundedPoint. We can define a similar copy
method:
75
75
APoint@16
50
50
ABoundedPoint@96
APoint@24
100
75
75
100
Pointer Variable
Primitive Variable
75
75
APoint@16
50
50
ABoundedPoint@96
APoint@24
100
Pointer Variable
75
100
75
APoint@32
50
50
APoint@36
100
100
Primitive Variable
Summary
Two object variables can point to the same object by storing the same address.
When no variables points to an object, the object is garbage collected.
The operation == checks if two objects are the same physical object, while the operation
equals() should check if two objects represent the same logical entity.
Exercises
1. Write a class that implements a history of integers, providing the following interface:
public interface IntHistory {
public void addElement(int newVal);
public int size();
public Integer elementAt(int index);
public int intElementAt(int index);
}
The class should store the history as a vector consisting of instances of the wrapper class,
Integer. The difference between elementAt() and intElementAt() is that the former returns
the history element at the specified index as an Integer object while the latter returns it as
an int value. The following figure shows interaction with an instance of the class using
ObjectEditor:
(b) Rewrite copy() so that it makes a true copy of the rectangle, allowing the original
rectangle and the copy to be changed independently.
(c) The kind of copy() the method above does is called shallow copy. The kind of copy the
new method (of part(b) of this question) is required to do, is called deep copy. Can you
suggest the reasons for using these terms for the two kinds of copies?