Lecture 8: Memory and Primitive Java Variables


Applications vs. Applets

In the textbook reading for this week, Chapter 2, the book introduces Java Applets. These are a slightly different form of a Java program that you can create. Applets are meant to be inserted into webpages and they can be used to great effect. To start with, my examples and the class assignments will be in the form of Java Applications. This is the more standard form and (maybe) is easier to start with. We'll make some Java Applets after the midterm when we try making some graphical programs.

Primitive Data Types vs. References and Objects

In Friday's lecture, we introduced the concepts of variables and data types. Recall that a data type defined a set of values and the type of operations that could be performed on those values. We covered each of the 8 primitive data types last time. Today, it's time to look at one of the most important parts of the Java language: objects and references.

The primitive data types were for basic values like a number or letter character. Objects, however, can be much more complicated and can hold lots of data as well as methods (called "functions" in some other popular programming languages). Here's an important fact: a variable holds either a primitive data type value or a reference to an object. A reference is an address (location) in the computer's memory. So, a primitive data type variable holds an actual value, but a variable of an object data type does not. Instead, a variable that has an object data type holds only a memory address for where that object can be found. This is an important distinction. If you remember that a variable of an object data type holds a reference and not the actual object itself you'll have a much easier time understanding how objects work. This is a difference I will try to emphasis in this lecture and may test you on for a question on the midterm.

A particular type of object is defined by a class. You can think of a class as a blueprint for creating objects of that type. A class defines what variables and methods that any object of its type will have. To demonstrate this, here's a general outline of a class - without going into details about how the code is actually written.

Abstract View of a Class

Suppose I wanted to create objects to keep track of the shooting percentage of a basketball player. I could do this by writing a class called Shooter to define a type of object that can do what I want. Objects that are made from this class will be referred to as objects of the type Shooter, or Shooter objects. Shooter objects can also be referred to as instances of the Shooter class. Since I'll need to know how many shots the player has attempted and made, I'll give my class two variables: shotsAttempted, and shotsMade. We'll ignore variable types and how they're created and initialized for now. In addition to those 2 numbers just mentioned, I'll need to somehow have my object update its stats whenever the player makes or misses a shot. To do this I'll have two methods: madeShot() and missedShot(). Finally, I'll have a method called getShotPercentage() to tell me the player's current shooting percentage. With these things included, my outline for the class looks like this:
class name:
   Shooter
   
variables: 
   int shotsAttempted  - records how many shots attempted
   int shotsMade       - records how many shots made
   
methods: 
   Shooter()           - constructor method
   madeShot()          - add another made shot
   missedShot()        - add another missed shot
   getShotPercentage() - return the current shooting percentage as a 'double' value
The method that I have not mentioned yet is the special constructor method Shooter(). The name of the constructor method is always the same as its class's name. A constructor of a class is a method that is used to create an instance of that class. So, the Shooter() constructor creates an instance of the Shooter class (which would be a Shooter object). Using this class as a "blueprint" I can create as many Shooter objects as I like. To create two variables holding references to Shooter objects called player1 and player2, I would use these lines:

Shooter player1 = new Shooter();
Shooter player2 = new Shooter();

This line of code used the new operator to create a new object. Creating an object using the new operator is called an instantiation. The new operator works by calling the constructor method for the class. New objects are always created using this reserved word new so you should plan on using it often. Note that the objects referenced by player1 and player2 are two different objects, even though the same "blueprint" class was used to create them. Also, remember the differences between references and objects here. For example, the value held by player1 is a reference to a Shooter object. The player1 variable does not hold an actual Shooter object. Because of this, the code "new Shooter()" does not actually give a Shooter object. It creates a new Shooter object but instead gives a reference to that object which is then placed into the variable to the left of the '=' operator.

Next, if I wanted to use player1's madeShot() method, I would use the line of code:

player1.madeShot();

This line of code makes use of the dot operator (.). Some other operators that we've seen were +,-,*,/,%. This dot operator takes the name of the variable we want on its left side. On the right side of the dot operator we place the name of that variable's method that we want to use. Note that there are also left and right parantheses after the method name. Between these parantheses you place whatever data (variables, literals, etc.) the method requires be passed to it. Pieces of data that are passed to a method are called parameters. This method doesn't require any parameters, but we'll see a couple that do later in this lecture.

In the above case, we're using the player1 variable and we're wanting to use the madeShot method from that variable. One way to think of this is that the madeShot method is "acting on" the player1 object. Another way to think about this is that the method madeShot is a service provided by the player1 object. In either case, we are invoking the madeShot method on the player1 object. The dot operator can also be used to access variables or fields within an object. We do this by placing the field's name to the right of the dot operator (instead of a method name).

Note that we were able to deal with Shooter objects even though we didn't have the specific code defining the Shooter class. The outline that I gave was an abstraction - something to hide unnecessary details to reduce complexity. In this case, the details of how to really write classes and how they work is more than we needed to cover right now. Instead, we can trust that the methods will do what they promise and not worry about how they actually accomplish their tasks.

Finally, remember that even though player1 and player2 objects had the same class "blueprint", they are distinct objects - just like two basketball players they might represent. The players each the same features - eyes, ears, hands, feet, shoe contracts - but they are completely different people. They each have feet, but the size of one player's feet doesn't affect the size of another player's feet. Similarly, since we created two different Shooter objects, the values held by each may be different. To illustrate this point, and note some of the effects of storing references rather than straight values, let's look at a class already written for us: DecimalFormat.

The DecimalFormat Class

The DecimalFormat class is one commonly available, simple class. Here we'll only take a small look at the abilities and methods of this class. We'll be looking at 3 methods from the class:
DecimalFormat (String pattern)
Constuctor to create a new DecimalFormat object that uses the specified pattern.
void applyPattern (String pattern)
Reset the object to use the given pattern.
String format (double number)
Returns a string containing the specified number formatted to the current pattern of this DecimalFormat object.
For our uses, the sole purpose of a DecimalFormat object is to format decimal numeric values to a certain number of decimal places. Note that each of the methods now specifies a parameter that must be given when that method is called. The DecimalFormat and applyPattern methods require a single parameter: a reference to a String object. The format method requires a parameter of type double. The format method also states that it returns a reference to a String object. Lastly, the use of the reserved word void by the applyPattern method is to state that it does not return any values. This is the same reserved word as has been used by each main method in every program you've written. We'll see much more about parameters when we start writing our own classes later in this course.

Our main purpose here is to exhibit the different effects of dealing with references instead of primitive type values. The ReferenceTest.java program does this by trying out several things with a set of three DecimalFormat objects and a single double constant to hold a decimal number to be formatted. This last one is a constant rather than a variable because of the reserved word final beginning the declaration for the variable VALUE. This is a modifier indicating that once the value for this constant is set, it's value will not ever be changed again. The conventions for how the names of constants are commonly written state that all letters in the name should be capitalized and that multiple words should be separated by the underscore (_) character. Otherwise, you can basically treat constants as variables that you can never modify.

The ReferenceTest.java program starts out by intializing the double constant and two of the DecimalFormat objects.

DecimalFormat formatter1 = new DecimalFormat("0.##");
DecimalFormat formatter2 = new DecimalFormat("0.###");
final double VALUE = 0.1234567;

We can visualize the current state of these 3 variables with this diagram.



Each of the sharp-edged rectangular boxes denotes a value held by one of the three variables. As expected, the double variable holds an actual value while the two DecimalFormat variables only hold references to actual DecimalFormat objects. The DecimalFormat objects are denoted by the rounded-edge boxes and are labeled with "#1" or "#2" to denote them as seperate objects. Their current formatting patterns are also listed.

With these three variables declared and intialized, the program next prints out their values to verify how the DecimalFormat objects are formatting. Next, the third and final DecimalFormat variable formatter3 is declared. Since it is not initialized, it is automatically initialized to the special value null. 'null' can be thought of as a placeholder value so that the computer can deal with uninitialized variables (often by crashing your program). The next diagram reflects the null value of formatter3 by placing a slash symbol in it's box. Since the variable VALUE holds the same numeric value throughout the program, it is left out of the remaining diagrams.

DecimalFormat formatter3;



The program next makes an assignment statement to assign a DecimalFormatter object to formatter3. This is done with the following line of code and results in the following diagram:

formatter3 = formatter2;



The formatter3 variable now has a reference to a DecimalFormat object, but it's the same object that formatter2 references. Logically, this is the conclusion that we should have come to since these variables hold references to objects and not the actual objects themselves. Using the bucket analogy, the assignment operator (=) takes the value out of the bucket on the right side and places it into the bucket on the left side. In this case, it takes the value held in formatter2's memory space (a reference to DecimalFormat object #2) and places that value into formatter3's memory space. In this way formatter3 is set to reference the same object as formatter2. The program next does another printout to verify this.

Since we now have two variables referencing the same object, using one variable to change the object will affect both objects. The next line of code and the next diagram reflect this.

formatter3.applyPattern("0.######");



The program next does another printout to show the current state of the variables. With formatter2 and formatter3 referencing the same object, any changes directly to that object affect both variables. This changes when formatter2 is next assigned a new reference to a new object.

formatter2 = new DecimalFormat("0.#");



Note that this did not affect the formatter3 variable because we didn't modify the object it was referencing. By using the new operator formatter2 was given a completely new object to reference. This once again seperates it from the formatter3 variable. The program tests this after doing yet another printout to show the current state of things.

formatter3.applyPattern("0.####");



The formatter3 object once again modified the object that it was referencing. Since it is the only variable now referencing that object, all other variables were unaffected.

Object and Reference Review

Today we've introduced a pretty important aspect of Java: objects and references. Here's a review of some of the key ideas introduced today.