Vocabulary Words
Primitive (Simple) Types
A Quick Note on "floats" and "doubles"
As we mentioned earlier, Java uses "floating point" numbers to represent numbers that include a fractional component. As compared to "fixed point" numbers, the number of digits to the right of the decimal "floats" as the absolute magnitude of the number changes.If the magnitude is very great, the precision of the fractional component is reduced. If the magnitude is very small, the precision of the fractional compentent is higher. This makes sense from a technical standpoint, becuase the same amount of memory is used, regardless of the magnitude. So, if more memory is used for the magnitude, less is available for the fraction. And, it makes sense from a user's perspective, because we care more about the fine details of the fraction on small numbers than we do on large ones. The fractions are more important when looking through a microscope than a telescope, I'd guess.
An old-school "floating point" number used a fixed number of digits to represent the number. So, there was a one-for-one trade-off between sigits used for the integer component and the fractional component. This isn't exactly the way it works with most modern computers. Instead, the decimal place doesn't necessarily move -- but the precision changes. In other words, the greater the magnitude, the more of an error might be present in the fractional component. This is sometimes called "rounding error", even though there isn't really any rounding involved. You'll learn all about the details once you get to 15-213.
For now, I'd like to note just one thing. This error means that, when doing computation, we need to think about the order in which we do it. In principle, when we do math, as long as we follow the precendence rules, the order doesn't matter. But, because of the roudning error, when it comes to floating point arithmetic, it sometimes does.
If we, for example, take a fraction, then multiply by a very large number, and then divide by another very large number and then add a fraction, we've made a mess. When we multiply the first fraction by the large number, we've encountered a "rounding error" by going from a "small number" to a larger one. When we divide by the larger number, we find ourselves back in the "small number range" and can support a lot of precision -- but we're just guessing. The real precision was "rounded away" when we multiplied by the large number. So, the result of the calculation is less precise than it need be. We are much better off doing the division fo the two large numbers first, to get the smaller result, then doing the multiplaction and addition.
Calculator Model
Today, we are going to construct a simple calculator. It works just like the "4 button" calculators many of you have seen (anyone seen one lately?) It can add, subtract, multiply, and divide.In order to perform computation, the user enters one operand, then the operation, then the next operand. The calculator remembers the first operand and the operation until the second operand is entered, at which time it performs the computation. The result of the computation replaces the first operand in the calculator's memory.
In this way, the result of one computation can be carried forward to the next -- notice it is in the same place as was originally used for the first operand. For this reason, the place in memory where it is stored is often times called the "accumulator" -- it accumulates the results over time. In other words, it holds the present result.
The calculator displays the number most recently entered by the user, until the user asks to add, subtract, multiply, or divide. When this happens, the accumulator value is displayed. Please pay careful attention to this. It is a bit ticklish. If the user presses, "5 + 3", "3" is displayed. If the user continues "5 + 3 +", 8 is displayed.
So, we see that the calculator is, in effect, remembering three things: the accumulator value and the value that is most recently displayed. It also temporarily holds onto an input value -- but it doesn't need to remember it long, as it is promptly rolled into the accumulator by via the stored operation and also displayed.
Calculator Interface/Class Skeleton
Recall that, in English, an "interface" means, "The place where the outer surface of two things come together." In software, an "interface" describes how a piece of a program can be used by other pieces. In the context of object-oriented software, an "interface" is the set of "public" methods of an object that can be used by other objects.Java has a special construct known as the interface that can be used to define, in a formal way, an interface that can be adopted when writing a class specification. But, we'll get to that later.
For now, we are going to begin our effort to write a calculator class specification by writing what is commonly known as a "class skeleton". It will be a class specification containing only empty method specifications. In so doing, it will define the interface of a calculator -- those methods that can be invoked by other objects.
class Calculator { public void add() { } public void subtract() { } public void multiply() { } public void divide() { } public void equals() { } public void enterNumber(double input) { } public void display() { } }What to notice? Well, a few things. First, notice that every class specification has a name. In this case, the name is "Calculator". By convention, we always capitalize the first letter of a class's name. If the class's name uses multipel words, such as "StudentAccount", we capitalize exactly the first letter of each word.
Notice the "reserved word" "class". A "reserved word" is a word that has a special meaning to Java. The reserved word, "class" indicates that we are wrtiting a class specification. It is always followed by the name of the class that we are specifying, in this case, "Calculator".
Also notice the {}-braces. When you see them, think "begin" and "end". Notice the set associated with the class specification. They enclose all of the methods specifications in the example above. They denote the beginning and the ending of the class specification. Everything within the {}-braces is said to be the "body".
Notice also the seven method specifications: add, subtract, multiple, divide, enterNumber, equals, and display. Each has its own set of {}-braces. In the next step, we will add the code within the braces to complete the methods. What goes in between those braces is said to be the body of the method specification.
Consider the method specification for enterNumber:
public void enterNumber(double input) { }We can see that the method name is "enterNumber". In other words, the "identifier" for this method is "enterNumber". It is "public", which means that it can be used from other classes of objects (we'll talk about private methods soon enough).
Within the ()-parenthesis, we see what is known as the "formal argument list". The formal argument list tells us what needs to be given to the object allong with the request to "enterNumber" -- in this case, the number to enter. Notice that it is a "double", as opposed to, for example, an "int". We'll soon see examples of methods specifications that use more than one argument.
Notice also the Java reserved word "void". It is basically a placeholder. It indicates that when this method is done, it doesn't give anything back to the caller. Remember, for this model of the calculator, the calculator will eventually display the result. As a constract, consider the example we did in class, where was asked, "add 5 and 4" and expected a reply so that the requestor could play with the value from there. Next class, we'll see methods that have more interesting return types. In these cases, the placeholder "void" will be one of replaced by "int", "float", "char", &c.
Calculator Example
Below is the calculator example we fleshed out in class:
class Calculator { private double accumulator; private double display; private char operation; public Calculator() { accumulator = 0.0; display = 0.0; operation = '='; } public void add() { display = accumulator; operation = '+'; } public void subtract() { display = accumulator; operation = '-'; } public void multiply() { display = accumulator; operation = '*'; } public void divide() { display = accumulator; operation = '/'; } public void equals() { display = accumulator; operation = '='; display(); } public void enterNumber(double input) { if ('=' == operation) accumulator = input; if ('+' == operation) accumulator = accumulator + input; if ('-' == operation) accumulator = accumulator - input; if ('*' == operation) accumulator = accumulator * input; if ('/' == operation) accumulator = accumulator / input; operation = '='; display = input; } public void display() { System.out.println (display); } }
The are a bunch of interesting things to notice here. The first are the declaration of the "instance variables", those variables that exist independently in each and every instance of the Calculator:
private double accumulator; private double display; private char operation;
Notice that they are qualified as being "private". This means that they can
only be accessed by methods described within the Calculator class --
not from methods of other classes. This type of access protection ensures
that we control their access through the methods that we write. Most
instance variables will be "private". You also see the general form
of a declaration: "
The next thing you might notice is the "constructor". It looks a lot
like a method specification -- but you'll notice a few differences.
First, it has exactly the same name as the class -- they are both
called, "Calculator". The constructor always has the same exact name,
including capitalization, as the class, itself.
Second, you might notice that there is no return type specification,
not even a "void". Remember, the constructor is not "called", so it
cannot "return". It is simply a description of how Java should
go about initializing an instance -- it is not a generally callable
method. This is also the reson that we left it out of the "class skeleton"
that we created above.
The constructor's basic job is to give each of the instance variables an
initial value and also to do any other necessary initialization. The
constructor is shown below:
The add() method, shown below, is a good example of a simple
method. You can see the simple assignments performed within the method
body. The "assignment operator", a single =-sign, works just as it did
in your math classes -- it assigns the value from the right-hand side to
the variable on theleft-hand side.
The enterNumber() method, shown below, is a bit more sophisticated. Notice
that the value "input" is passed in by the caller. Since input is declared
within the formal argument list, we know that it's "scope" is local -- it
can only be used within this method. We also know that it is initialized
with a value passed in by the caller.
We also see, for the first time, the "if statement". The if statement allows
for conditional execution. If the predicate is true, the body is executed.
If not, the body is skipped. The predicate of the if statement is a
"boolean expression". In other words, it is an expression that evaluates
to true or false. If it evaluates to true, the body is executed. If it
evaluates to false, the body is not executed.
If the "operation" variable holds the "+" sign, then the assignment
"accumulator = accumulator + input" is made. Otherwise it is skipped.
The {}-braces are optional if only one statement is controlled by the
if -- but required if there are more than one. We'll see examples of
this later. But, below is an example:
Consider the specific example below:
if ('+' == operation) {
accumulator = accumulator + input;
System.out.println ("Added the numbers.");
}
The only other thing to notice is the use of '-single-quotes. Single
quotes are used to enclose "char" types, single characters. We'll
learn soon that "-double-quotes are used to enclose strings of characters,
such as "Hello World". Since it is possible to have a "String" that only
happens to contain a single character, we need to be careful to use
the type fo quote that matches the declared type -- we can't base it
on the lenght of the particular case.
The full method is shown below:
Structure and Style
Notice that the code is indented within each set of {}-braces. Each level
of nexting gets anotehr level of indenting. To keep the code readable,
indents should be fairly small -- just 2-3 characters. Otherwise, as
the code becomes more complex and we find ourselves with nested if statements
within loops within method specifications within classes, we'll waste too
much real estatre on indents.
Vertical space, meaning blank lines, should be used to group related
code together to make it more readable. Identifiers should always be
meaningful and descriptive -- they should not be cryptic or so
abbreviated as to be meaningless. As a matter of good style, when an
identifier consists of multiple words, we combine them by running them
together and capitalizing exactly the first letter of each word. We do
not use _-underscores. The only exception is that we do not capitalize
the first letter of an identifer -- except for the names of classes
(and interfaces), where we do capitalize the first letter.
From Classes to Objects
We're not going to build our calculator out of wood, plastic or metal.
Instead, it is going to be a model constructed within the program.
So, we need to ask Java to create the new calculator. We do this
using Java's new operator, as follows:
The "new" operator allocates the storage for the Calculator and then
uses the "constructor" specified within the Calculator class to initialize
its instance variables and perform any other initialization. Once the
new operator is done, a new Calculator, all ready to go, exists within
the program.
In this case, the constructor doesn't happen to take any arguments, but
if it did, they would be passed in within the ()-parentheses.
References and Reference Variables
The new-operator gives us a reference to the newly created object. We can
capture that reference into a variable by assigning it. Consider the code
below:
The variable "c" is known as a reference variable because it
stores a reference to the calculator. And, this is very important:
It stores a reference to a Calculator, not the Calculator object, itself.
Please note that other intro sections confuse the details here. They
make claims such as "c is an object" or "C is a Calculator". These
claims are false. "c" denotes a variable capable of holding a reference
to a calculator -- not a calculator. A reference is a simple, primitive
value. The reference, itself, is not complex and cannot exhibit any
behaviors -- it is simply read and assigned, just like an "int", "float",
or "char".
Next class, we'll take a look at an example that makes it particularly
clear that these variables are references to object, not objects
themselves. It'll also make it clear that they are primitive values,
not objects.
We manipulate an object via a reference to it via the "scope oeprator",
which is a period. For example, "c.add();" asks the calculator to add.
"c.enterNumber(6);" asks the calculator to enter the number 6 as input.
The main() method is the starting point. It will create the initial objects
and get the program going. The main method always has exactly the same
form. And, it is very important that it always takes the same form shown
below. Java looks for a method exactly like this as the starting point.
If the arguments don't match or the name doesn't match, it won't be
recognized as the starting point. So, memorize this!
Notice the qualifer "static" -- you now know what this means.
This means that this method isn't part of an object. Instead, it is
just "floating". For the moment, I'd like to punt on "String[] args".
In short, "String[]" represents a "String array", which is basically
a list of Strings. We'll hit the details soon.
In class, we created the following main() method -- it does nothing
more than create a new calculator and ask it to go through a few
of its motions.
Person vs. Machine: Closing the Gap
When people write programs, they are doing it with a mind toward
solving some real problem. They want to be concerned with the problem and
its solution, not the details of the machine that is a tool for solving it.
For this reason, programmers usually write programs in English-like
programming languages: C, C++, and our favorite, Java, for example.
Although these languages are structured in a way that is useful to a
computer, they are designed to be understandable and convenient for people.
Ultimately, a program written by a computer in one of these languages,
a so-called High-Level Language is translated into another
form that is better tailored for the machine. This new form is often
known as an assembly language program or, in Java, byte code.
To help understand the difference, I like to think about a car with a
driver and passenger. The passenger might give the driver directions:
The passenger provided a set of high-level instruction to the driver.
These instructions were provided in a way that the driver understood
and used commands that were descriptive in the context of the problem:
Navigating the city en route to the grocery store.
But the car, the machine, can't understand these instructions. It requires
instructions in a different language. The instructions might begin like
this:
Etc. Etc. Etc.
The driver has to translate the high-level instructions provided by
the passenger into a low-level (physical) language understood by the
car. This language is coposed of much smaller steps. And, it might vary
slightly from car to car. For example, the appropriate actions taken
by the driver will be different for a car with a "standard" transmission
than one with an "automatic" transmission. So, although the passenger's
language is problem-oriented and car-independent, the driver's translation
is car-oriented and car-specific.
The programming process happens in much the same way. Human programmers
produce high-level directions in languages like Java. In this respect,
we are acting like the passenger. These documents are then given to
a program called a compiler that acts like the driver and translates
them into a language appropriate for the machine.
The resulting program, which is machine specific, can run on only one
type of computer, such as iMacs running OS X. This is why, when you
go to the store, you have to buy different versions of the same program
for different types of computers. The same high-level programs were
compiled for different types of computer.
Java and the Virtual Machine
This solution obviously can't work -- at least without something else
entering the picture. Why not? Different computers understand different
low-level instructions.
So, here's what they did. They invented a new type of computer, the
Java Machine. And, they wrote a compiler that would convert
Java high-level programs into low-level programs that would only run
on this Java Machine.
But, they didn't build this machine in hardware. Instead they wrote
a program to model it in software. Running this program creates the
so-called Java Virtual Machine (JVM). The JVM was then
compiled for many different types of physical computers. This software
was then made available for many systems: It is part of the jdk that
you might ahve downloaded to run java from home.
So, here's the Java model. The JVM is written and compiled for many
different systems and is distributed widely. Java programmers compile
software for the JVM. When one wants to run a Java program, one starts
the JVM program and lets it run the compiled Java program.
Command-line Tools
We can start java programs by providing the class name, without the
extension, to the JVM. This is done, again no great surprise, with
a program called "java" as follows:
The above example prints, as intended, the output of our
Calculator's quick-and-dirty test program onto a console.
Making it Happen
Literal Values and Strings
So, consider the following:
The code above initializes each of s1 and s2 to reference the literal
String "Hello World". The way this works is that, before the program
runs, Java creates a "Hello World" String object. At runtime, the literal
"Hello World" embedded within the program basically acts as a reference to
this pre-existing object. So, both s1 and s2 reference exactly the same
object.
The == Operator
So, let's take a look at a classic example and see what happens:
To the surprise of some folks, "The two references identify the same object."
is printed. The want to believe that "s1" and "s2" are Strings and that
the ==-operator is comparing them. But, we debunked that myth last class.
They are not String objects -- they are reference variables. And, in
this case, they both point to the single "Hello World" object.
Now consider the example below. It forces the creation of a new String
object by calling the "new" operator. You can see that a reference to
the "Hello World" object is being passed into the constructor. It does
exactly what you'd expect -- it creates another object that also models
"Hello World". In effect, what we now have, are two "Red, 2008, Honda
Accords". We have two different, but equivalent objects.
This time the code prints, "The two references identify different objects.".
And, this is, of course, correct -- we created a second object. Remember,
we are comparing the refernces, not the objects. Each reference identifies
a different object -- it doesn't matter that the two objects happen to
be equivalent.
The equals() method
As it turns out, in Java, when we write a class specification, we don't
start with a blank slate. Instead, we inherit features from a
generic class specification called Object. What this means is that,
without us having to write them, we get some methods that are part of
the Object class's specification.
This includes one method as below:
Notice "this" in the code above. In any method, "this" is a reference to
the object that is running the method. So, for example, if we consider
"x.equals(y)", within the equals() method, "this" is equal to "x" and
"y" is passed in as "o".
So, we see that the equals() method, by default, does exactly the same
thing as the ==-operator. And, this isn't surprising. What else can
it do in a "one size fits all" way? Comparing objects is difficult
stuff. And, it is done differntly for each type of object.
For example, in comapring strings, they are equivalent if each corresponding
letter matches. But, in comparing people, they might be considered equal
if they have the same number of years of experience and the same degree, or
the same height, weight, and game-day stats.
So, by default, the equals() method isn't particularly useful -- it is
the same as the more convenient operator. But, the good news is that, in
writing our own class specifications, we can override it. If we
write a method with exactly the same name and argument list, it will
replace the inherited version -- allowing us to compare the objects,
themselves, however we'd like. We'll do some of this next class.
But, for now, here's what I'd like you to remember: Use the equals()
operator if you want to compare Strings, since they are objects -- only
use the ==-operator if you want to compare the references, themselves.
If we rewrite our example form above, it looks as below:
Hidden Instance Variables
Do you see the problem? We are trying to initialize the instance variable
"playerName" with the argument "playerName" -- and what we've got makes
no sense. Both identifiers are in scope. They both are usable.
Does the identifier "playerName" represent the instance variable?
Or the argument?
Well, the argument hides the instance variable. "playerName"
refers to the argument. So, how do we get to the instance variable by
the same name?
You've got the tool in your toolbox. Can you think of it? We just discussed
it. Remember the "this" reference? Check out how it can solve the problem
by giving us a way to get to the instance variable:
public Calculator() {
accumulator = 0.0;
display = 0.0;
operation = '=';
}
public void add() {
display = accumulator;
operation = '+';
}
Consider the specific example below:
if ('+' == operation)
accumulator = accumulator + input;
public void enterNumber(double input) {
if ('=' == operation)
accumulator = input;
if ('+' == operation)
accumulator = accumulator + input;
if ('-' == operation)
accumulator = accumulator - input;
if ('*' == operation)
accumulator = accumulator * input;
if ('/' == operation)
accumulator = accumulator / input;
operation = '=';
display = input;
}
In Java, {}-braces denote the beginning and ending of blocks. a }-brace
is never followed immediately by a semicolon. But, each "statement" within
a block is ended with a semicolon. It is technically legal to place
several statements on the same line -- but this is generally considered bad
form as it makes the code hard to read.
Last class, we created the Calculator class specification. But, a
specification is, like a schematic or a blue print, just a descriptive
document. We haven't yet created an actual calculator. So, let's talk about
how to do that.
new Calculator();
Okay, so, we've created a new Calculator object, but how do we use it?
In order to use an object, we need a reference to it. A reference
is something we use to "refer to an object". In other words, it is something
that we use to identify which object to use. In general, references are
stored within reference variables and are accessed through those variables.
The "main" method
Calculator c;
c = new Calculator();
Okay. So, we know how to create class specs. And, we know how to turn those
specs into objects. But, how does a program actually get started? This
happens via a special method known as "main". When we start up Java, we give
it the program and it runs the main() method. This method is static.
In other words, it isn't the behaivor of an object, it is just floating
out there by itself. Java actually runs it, rather than an object
exhibiting it.
public static void main(String[] args) {
/*
* Your code here
*/
}
public static void main(String[] args) {
Calculator c = new Calculator();
c.enterNumber(4);
c.display();
c.add();
c.display();
c.enterNumber(5);
c.display();
c.add();
c.display();
c.enterNumber(6);
c.display();
c.equals();
c.display();
}
Next, I'd like to talk about how to actually get our program to
run. But, before I do that, I want to provide some perspective about how
our code interacts with the computer and the environment. This will
help us to better understand the toos we use to make our program run
and how they work.
Back out of the driveway and go right. Continue for 4 blocks. At the
stop sign, make a right. Travel about 1/2 mile. You'll see a grocery
store. The parking lot is on the right-hand side of the road, just past
that grocery store. Park there.
Press break pedal: Not less than 25 lbs of pressure. Push key into
keyswitch, twist forward to on position; listen for click. Twist
forward again with not less than 10 lbs of pressure. Listen
for motor. Release keyswitch pressure. Slide gear selector down
two notches into reverse. Reduce brake pressure to 5 lbs. Roll to
bottom of driveway. Rapdily increase break pressure to not less than 25
lbs.
The designers of Java wanted to take a different approach. They wanted
a programming system which was "compile once-run anywhere". In other
words, they wanted one set of machine-oriented instructions to work on
all computers.
We can compile this program using the Java compiler. It shouldn't be
surprising that this program is named, javac. Here's how it
works:
javac Calculator.java
If we look in the file system, we now see the byte-code -- the low-level
program for the JVM. The byte code has the extension ".class".
ls Calculator.class
java Calculator
The process used to create the "Calculator program" involved three steps:
creating the Java program, compiling it, and starting it. If it had been
a more complex program we might also have had to manage multiple pieces
or debug mistakes.
I'd like to observe that all literal values are known before a
program ever runs -- they are embedded within the code, itself. As a
result, the compiler simply creates them when compiling -- they are born
with the program, they are not created at runtime.
String s1 = "Hello World";
String s2 = "Hello World";
Some sections tell their students that, "You can't use == to compare
Objects." And, this is true -- but as they present it, it makes no sense.
You can use the ==-operator to compare string references. The ==-operator
works exactly as it does for every other primitive type: It looks into
the box and it sees if the two values, in this case references, match.
String s1 = "Hello World";
String s2 = "Hello World";
if (s1 == s2)
System.out.println ("The two references identify the same object.");
else
System.out.println ("The two references identify different objects.");
String s1 = "Hello World";
String s2 = new String("Hello World");
if (s1 == s2)
System.out.println ("The two references identify the same object.");
else
System.out.println ("The two references identify different objects.");
Well, what do we do if we want to know if the objects, themselves, are
equivalent? To solve this problem, we can't use the ==-operator. We know that
the ==-operator does the same thing for references as it does any other type
of primitive variable -- it looks at the value and makes a comparison. It
looks at the references, themselves, not the objects that they identify.
public boolean equals (Object o) {
return (this == o);
}
String s1 = "Hello World";
String s2 = new String("Hello World");
if (s1.equals(s2))
System.out.println ("The two Strings are equivalent.");
else
System.out.println ("The two Strings are equivalent.);
Consider the following example:
class Scorecard {
private String playerName;
public Scorecard (String playerName) {
playerName = playerName;
}
...
}
class Scorecard {
private String playerName;
public Scorecard (String playerName) {
this.playerName = playerName;
}
...
}