In today's lecture we look at what happens when you create an object.
Java's built-in primitive types lets the client represent integer and real numbers, but there is no primitive type that represents fractions.
You could as the client keep track of all of the fraction numerators and denominators, but this quickly becomes cumbersome:
import java.io.PrintStream; public class YuckFractions { public static void main(String[] args) { PrintStream output = System.out; // computes Example 4.3 from the textbook manually int num1 = 5; int den1 = 3; int num2 = 7; int den2 = 6; int num3 = 31; int den3 = 45; int num4 = 3; int den4 = 4; // compute (num1 / den1) x (num2 / den2) int num5 = num1 * num2; int den5 = den1 * den2; // compute (num5 / den5) / (num3 / den3) int num6 = num5 * den3; int den6 = den5 * num3; // compute (num6 / den6) + (num4 / den4) int num7 = num6 * den4 + num4 * den6; int den7 = den6 * den4; // aargh, even printing out the fraction is a pain output.printf("%d/%d%n", num7, den7); } }
« utility » FractionUtility |
numerator: double denominator: double |
add(double, double): void divide(double, double): void multiply(double, double): void |
// set the numerator and denominator FractionUtility.numerator = 5; FractionUtility.denominator = 3; // do some arithmetic FractionUtility.multiply(7, 6); FractionUtility.divide(31, 45); FractionUtility.add(3, 4); output.printf("%d/%d%n", FractionUtility.numerator, FractionUtility.denominator);
The utility looks like it made using fractions much easier, but what if you want more than one fraction? For example, suppose that you want to read in two fractions from the user:
Scanner scanner = new Scanner(System.in); // read the first fraction Fraction.numerator = scanner.nextInt(); Fraction.denominator = scanner.nextInt(); // where do I store the second fraction???
A key feature of a utility is that there can only ever be one copy
of the attributes (and methods) in memory, and those copies belong
to the utility. This is what the keyword static
means.
type.lib.Fraction
The type.lib
package
provides a Fraction
class that encapsulates the idea of a fraction.
It provides methods for fraction arithmetic, getting and setting the
values of the numerator and denominator, and many other actions.
import java.io.PrintStream; import type.lib.Fraction; public class YippeeFractions { public static void main(String[] args) { PrintStream output = System.out; Fraction f = new Fraction(5, 3); f.multiply(new Fraction(7, 6)); f.divide(new Fraction(31, 45)); f.add(new Fraction(3, 4)); output.println(f); } }
Unlike a utility class, the Fraction
class
allows the client to create separate and distinct "copies"
called objects, or instances.
In the real world, an object is a physical thing (the person next to you, your shoe, your computer, etc.). Objects have state (properties) and behavior (actions they can perform).
"Lola" the Cat
State: name, color, weight, asleep,
awake, crazy, ...
Behavior: sleep, sleep, sleep, eat, purr, ...
In Java, an object also has state and behavior. An object stores its state in attributes and performs its behavior using methods. Often, the methods will cause the state of the object to change (by modifying its attributes). You can think of attributes as being variables that are owned by the object.
In the real world, there are many individual house cats; however, cats in general share enough common features that when someone says "my cat ..." everyone knows what you are talking about.
In Java, a class is a blueprint for constructing objects. A class defines what attributes an object will have (its state) and it defines what behavior an object can perform (its methods). Notice that a class groups related attributes and methods in one place.
A class defines a type; for example, if we had
a Cat
class, it would be possible to
create variables of type Cat
.
Using a class, the client can create many different
instances of the class; for example, if we had
a Cat
class, we could create many objects, each
one representing a different cat.
Fraction f;
Mean?The statement Fraction f;
is similar to
the the statement int x;
. It causes a block
of memory to be reserved that is large enough to hold
what is called a reference to a
Fraction
object and labels the block with
the identifier f
.
0 | ||
¦ | ||
main | ||
f ⇒ | 100 | |
¦ |
We say that f
is a reference variable
that holds a value that refers to a Fraction
object (or a Fraction
instance). In this example, the
actual Fraction
object has not yet been created, so
no value is set for the memory block starting at 100
.
Fraction
ClassRecall that a class serves as a blueprint for creating objects;
the Fraction
class serves as a blueprint for creating
Fraction
objects.
The first time that the running program uses the Fraction
class, the compiled class is loaded somewhere into memory. In our
memory diagram model, the class is shown as a block of memory that
contains the attributes, methods, and constructors of the class.
0 | ||
¦ | ||
main | ||
f ⇒ | 100 | |
¦ | ||
¦ | ||
500 | Fraction class | |
numerator ⇒ | 504 | |
denominator ⇒ | 520 | |
¦ |
Not all of the features need to shown in the memory diagram; in the example below we only show the attributes that represent the numerator and denominator:
new Fraction(5, 3)
MeanThe expression new Fraction(5, 3)
creates a new Fraction
object with
a numerator of 5 and a denominator of 3. In our memory
diagram model, you can think of the expression as leading
to the following sequence of events:
Fraction
objectFraction
class into the allocated
block of memorynumerator
attribute
to 5
and the denominator
attribute to 3
.0 | ||
¦ | ||
main | ||
f ⇒ | 100 | |
¦ | ||
500 | Fraction class | |
numerator ⇒ | 504 | |
denominator ⇒ | 520 | |
¦ | ||
600 | Fraction object | |
numerator ⇒ | 604 | 5 |
denominator ⇒ | 620 | 3 |
¦ |
Notice that the class Fraction
is not used
to store the values of the numerator
or denominator
; the class (in this case) is
only used as a template to create the Fraction
object. The object is responsible for storing the values
of the numerator
and denominator
.
f = new Fraction(5, 3)
MeanThe statement f = new Fraction(5, 3)
causes a value to be stored in the memory reserved for the
variable f
. But what value is stored there?
Recall that f
is a reference variable; thus it
must hold some value that refers to an object in memory. We shall
use for the value the memory address
of the block of memory holding the newly created Fraction
object.
0 | ||
¦ | ||
main | ||
f ⇒ | 100 | 600 |
¦ | ||
500 | Fraction class | |
numerator ⇒ | 504 | |
denominator ⇒ | 520 | |
¦ | ||
600 | Fraction object | |
numerator ⇒ | 604 | 5 |
denominator ⇒ | 620 | 3 |
¦ |
Notice that the Fraction
object looks like a copy
of the Fraction
class; the object also has:
Whenever you declare a variable whose type is some sort of
object (i.e. not a primitive like int
or
double
) you are declaring a reference variable.
A reference variable holds a reference. A reference is some value that ultimately refers to an object in memory. In the preceding examples, we assumed that the reference was the memory address of an object.
The statement:
Fraction f = new Fraction(5, 3);
means f
is a variable whose value refers to a
Fraction
object.
the value of f
IS NOT a Fraction
object.
the value of f
IS NOT a Fraction
object.
the value of f
IS NOT a Fraction
object.
one more time...
the value of f
IS NOT a Fraction
object.
You can have multiple reference variables that all refer to the same object.
import java.io.PrintStream; import type.lib.Fraction; public class YippeeFractions2 { public static void main(String[] args) { PrintStream output = System.out; Fraction f = new Fraction(5, 3); Fraction g = f; f.multiply(new Fraction(7, 6)); f.divide(new Fraction(31, 45)); f.add(new Fraction(3, 4)); output.println(f); output.println(g); Fraction h = g; output.println(h); } }
In the above example, f
, g
, and
h
all refer to the same Fraction
object which results in the fraction 443/124
being printed 3 times.
Fraction g = f;
MeanThe statement Fraction g = f;
does
not create a new Fraction
object. Instead,
a block of memory is reserved to hold a reference to
a Fraction
object and the block is labelled
with the name g
. Then the value of
f
is copied into the memory block named
g
.
0 | ||
¦ | ||
main | ||
f ⇒ | 100 | 600 |
g ⇒ | 104 | 600 |
¦ | ||
500 | Fraction class | |
numerator ⇒ | 504 | |
denominator ⇒ | 520 | |
¦ | ||
600 | Fraction object | |
numerator ⇒ | 604 | 5 |
denominator ⇒ | 620 | 3 |
¦ |
A class that allows a client to create objects will usually provide one or more constructors that allows the client to create an object with the desired state.
A constructor looks similar to a method but there are several important differences. First, the name of a constructor is the same as the name of the class. The statement
Fraction f = new Fraction(5, 3);
uses the constructor from the Fraction
class to
create a Fraction
object with a numerator of 5
and a denominator of 3.
The constructor is responsible for initializing the state of the object based on the arguments provided by the client.
Second, a constructor never returns a value (not even void). If you look in the API you will see that the constructor declarations look like:
public Fraction()
public Fraction(long numerator, long denominator)
The operator new
is responsible for returning
a reference to the newly created object.
Third, as a client,
you always use new
with a constructor, and you never
include the class name or an object reference in front of the constructor.
// good Fraction f = new Fraction(5, 3); // good f.multiply(new Fraction(7, 6));
Some classes define a constructor with no parameters; such a constructor is called a default constructor. The purpose of a default constructor is to allow a client to create an object without having to specify its initial state; instead, an object will be created with a default state (as defined by the implementer of the class).
The statement
Fraction f = new Fraction();
creates a Fraction
object with a numerator of
0 and a denominator of 1 (look in the API), and assigns the value
of the memory address of the object to the reference f
.