It's hard to think of actors William Shatner and Patrick Stewart
as anything other than captains of the starship Enterprise.
On the Enterprise we know exactly what they're likely to say and do.
Even when they're not in Star Trek movies
we keep expecting them to talk about engaging their warp drive,
setting their phasers on stun, firing their photon torpedoes,
and worrying about the Klingons or the Borg.
As actors, they are thoroughly typecast in those roles.
| Type | Constraints | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
Kirk
|
|
||||||||||
Spock
|
|
||||||||||
Picard
|
|
||||||||||
Data
|
|
||||||||||
Similarly, all Java entities are typed.
An entity's type specifies both what it is and what it can do.
So typing in Java is more than just the set of values an entity has,
it's also the set of things that we can do to those values.
Type double
Type double variables can hold a decimal number
in the range from about minus 10308 to plus 10308.
A double variable, however,
can't remember more than 15 digits of a decimal.
So we can't put the number 9,999,999,999,999,999.0
into a double variable,
even though it's in range.
If we tried, the 16th decimal place would be unreliable,
so we could easily end up with 10,000,000,000,000,000.0.
Besides being able to hold a certain set of decimal values,
double variables also have a set of operators
to apply to those values:
addition (+),
subtraction (-),
multiplication (*),
and division (/).
Only those four operators are legal for double variables,
just as only certain numbers are legal double values.
Type int
Type int variables can hold a whole number
but only within the range
from about minus two billion to about plus two billion.
If we try to put a whole number outside that range
into an int variable
the number we end up with will differ from the one we tried to put in.
The type int has the same set of operators
as type double except that it modifies the meaning of division
and it adds one more operator, remainder (%).
First, if a and b have int values
then a / b is also an int
and the division discards any fractional remainder.
| An example of integer division | ||
|---|---|---|
15 / 1
|
equals | 15 |
15 / 2
|
equals | 7 |
15 / 3
|
equals | 5 |
15 / 4
|
equals | 3 |
15 / 5
|
equals | 3 |
15 / 6
|
equals | 2 |
15 / 7
|
equals | 2 |
15 / 8
|
equals | 1 |
15 / 9
|
equals | 1 |
15 / 10
|
equals | 1 |
Second, if a and b have int values
then a % b is also an int
and it is the whole number remainder
of a divided by b.
| An example of integer remainder | ||
|---|---|---|
15 % 1
|
equals | 0 |
15 % 2
|
equals | 1 |
15 % 3
|
equals | 0 |
15 % 4
|
equals | 3 |
15 % 5
|
equals | 0 |
15 % 6
|
equals | 3 |
15 % 7
|
equals | 1 |
15 % 8
|
equals | 7 |
15 % 9
|
equals | 6 |
15 % 10
|
equals | 5 |
Although the division operators for type int
and type double look the same,
they are really two different operators.
The same symbol, "/", means two different things
depending on the type of its operands.
Type boolean
Type boolean variables can hold only one of two values:
true or false.
The type boolean has a completely different set of operators:
and (&&),
or (||),
and not (!).
If a and b have boolean values then:
!a
|
is true
|
whenever a is false
|
||
a && b
|
is true
|
whenever a is true
and b is true
|
||
a || b
|
is true
|
whenever a is true
or b is true
or both a and b are true
| ||
In any other case, these operators produce
false.
(Example:
!true is false;
!false is true;
true && true is true;
true && false is false;
false && false is false;
true || true is true;
true || false is true;
false || false is false.)
Types and Expressions
We can combine each of the above three types using their various operators to produce expressions. An expression is not a variable, although it may be composed of variables, nor is it an operator, although it may contain operators, nor is it a value, although it always has a value. Each expression has the same type as the type of its operands.
For example,
if a, b, and c have int values
then the expression
((a / b) + c) % 5
also has a int value.
Similarly,
if a, b, and c have double values
then the expression
((a / b) * c) + 5
also has a double value.
Finally,
if a, b, and c have boolean values
then the expression
((a || b) && c) || true
also has a boolean value.
Because a boolean expression must always have
a boolean value,
it must always have either the value true or false.
Similarly, an int expression must always have
an int value;
that is, it must be a whole number somewhere in the range
from about minus two billion to plus two billion.
A double expression is similar.
Since both a boolean expression and a boolean
variable will always have a boolean value,
not only can we always assign a boolean expression
to a boolean variable,
but every boolean variable
is also a boolean expression.
Similiarly, every int variable
is also an int expression,
and every double variable
is also a double expression.
Implicit Type Casting
Since every int value
has a corresponding double value,
the Java interpreter lets us assign any int expression
to a double variable.
Thus, if at least one of a, b, and c
have double values
while the rest have int values
then the expression:
((a / b) * c) + 5
has a double value.
The Java interpreter automatically casts int values
to double values wherever necessary
to ensure that expressions mixing int and double
values always have a single type, which is type double.
On the other hand,
since not every double has a corresponding int value,
we can't assign a double value to an int variable.
So even though the following looks reasonable, it is illegal:
double a;
a = 5.0;
int b;
b = 3;
int c;
c = a + b;
The last assignment asks the Java interpreter
to assign a double value
(produced through an automatic cast)
to an int variable,
and it refuses.
Explicit Type Casting
We can force the Java interpreter to demote any double value
to an int value with an explicit cast, like so:
double a;
a = 5.4321;
int b;
b = 3;
int c;
c = (int) (a + b);
The int cast operator, (int),
tells the Java interpreter that we don't care that the value on the
right-hand side is a double,
we want the interpreter to discard any fractional portion
and put the result in the int variable c.
The upshot will be that int variable c
will contain the int value 8.
We can also use a double cast operator,
(double),
to force the interpreter to promote an int value to a
double value, like so:
double a;
int b;
b = 3;
a = (double) b;
But this is pointless since the Java interpreter will always do so
automatically anyway.
Although we can cast back and forth between int values and
double values, we can't cast boolean variables
or reference variables to either of them.
We can, however, cast boolean values
to each other as much as we want
with the boolean cast operator,
(boolean),
but there is no point.
We can also cast reference variables to each other,
but only under certain conditions.
Later we will discover how to cast reference variables.
Types and Values
So far, we've seen variables, operators, expressions, and types. Variables have values, operators manipulate values, expressions have values, and types specify values. But what are values? Values are the things that can go into the variables.
The only values we can have are:
true, false,
any whole number between about minus two billion and plus two billion,
any decimal number that can be expressed within 15 digits of precision
and which lies in the range from about minus 10308 to plus
10308,
and an unlimited number of (secret) names of objects
(in other words, reference values).
That's (almost) all the possible values the Java interpreter understands;
so that's (almost) all the values we could ever have in any Java program,
no matter how complicated it is.
Here's the big picture: Each one of the four types of variables is a box. It has a name. It has a type. It also has something inside it---a value. Values, however, can also exist without being inside a named variable since we can simply write the value in an expression without first putting it in a variable. Operators manipulate values whether they are inside variables or not. Expressions combine operators, values, and variables.
Operators don't have a type, they belong to a type.
Values, too, belong to a type but don't have a name.
Expressions have a type, but they don't have a name, either.
Variables, though, always have a name, type, and value.
Reference Values
So far we've seen almost all the values for all the four predefined types;
boolean,
int,
double,
and reference.
There is, however, one special value,
null---the value that's in a reference variable
before we use it to store any actor's name (that is, a real reference value).
If a reference variable has the value null,
it names no object at all.
Unlike every other value in Java,
the value null has no type at all.
The words null and void are similar,
but not the same.
A method has a void return type if it returns no value.
A reference variable has the value null if it names no object.
We can't assign void to a reference variable,
nor can we declare the return type of a method to be null.
Although reference values are one of the four type of values, there is no such thing as a reference expression. We can't add reference values, divide them, and them, or negate them, or otherwise operate on them in almost any other way. All we can do is use them as object names, assign them from one reference variable to another reference variable, test whether two reference variables containing them both contain the same reference value, and use the dot operator on reference variables containing them to refer to variables and methods of objects.
Operators and Operator Combinations
We can construct boolean expressions
from the entities we have so far with the operators
is less than (<),
is greater than (>),
is less than or equal to (<=),
is greater than or equal to (>=),
is equal to (==),
and is not equal to (!=).
a < b
|
is true
|
whenever a is less than b
|
||
a > b
|
is true
|
whenever a is greater than b
|
||
a <= b
|
is true
|
whenever a is less than or equal to b
|
||
a >= b
|
is true
|
whenever a is greater than or equal to b
|
||
a == b
|
is true
|
whenever a is equal to b
|
||
a != b
|
is true
|
whenever a is not equal to b
|
||
In any other case, these operators produce
false.
All six of these operators apply when a and b
have either int or double values.
We can also use the last two operators, == and !=,
with boolean and reference values as well.
We can combine all the operators with parentheses
to produce complex boolean expressions.
We can say, for example,
boolean condition;
int apples;
int oranges;
apples = 12;
oranges = 5;
condition = ((apples / 7) > 0) && (oranges <= 5)
&& (apples > oranges);
In this case, condition will have the value true
since the value of (apples / 7) is 1, which is greater than 0,
the value of oranges is less than or equal to 5,
and the value of apples is greater than
the value of oranges.
If we change the earlier assignments so that any of those conditions fails,
however,
then condition will have the value false.
Here are some more examples of boolean expressions.
All of them have either the value true
or the value false:
true
5 > 7
(false)
(5 > 7)
(a == true)
(true || false)
((a == false) == true)
((a == b) == (c == d))
estragon.finishedDividing
(((e / f) > (g * h)) && true)
(estragon.finishedDividing == false)
((apples > 0) && !(oranges <= 5)) || (apples > oranges)
That last expression is true only when:
either the value of apples is greater than 0
and the value of oranges is not less than or equal to 5,
or when the value of apples is greater than
the value of oranges.
Since the value of oranges is not less than or equal to 5
only when its value is greater than 5,
the expression is true exactly when
either the value of apples is greater than 0
and the value of oranges is greater than 5,
or the value of apples is greater than
the value of oranges.
So, the last expression has the same boolean value as:
((apples > 0) && (oranges > 5)) || (apples > oranges)
(Almost) Everything Has a Type
We must declare the type of variables, methods, classes, and objects before the stage manager will agree to produce any one of them. That type can never change. Expressions, too, must have a type, but we don't need to declare them since the stage manager can figure out the right type for itself given the type of the operands and operators in it.
In theater terms, every Java program is like a bad spaghetti western where everyone has to wear hats. Once we see some folks in white hats, we know they're going to be good guys---and they will be good guys forever. In Java, you must have a hat, that hat is glued to your head, and the color of your hat---your type---is an inescapable part of you.
| Type | Constraints | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
double
|
|
||||||||||
int
|
|
||||||||||
boolean
|
|
||||||||||
An entity's type tells us what we can do with that entity,
and what we can expect from it.
If it's an int variable, say,
then we can only use it to hold an int value,
so it can only hold one of roughly four billion numbers,
and we can only operate on it to add, subtract, multiply, divide,
and find remainders.
Similarly, a reference variable can only hold reference values
(that is, names of objects)
or the value null,
and it can only respond to operators like "."
and == and !=.
(Recall that we use the dot operator to ask an actor,
through a reference variable, to execute one of its methods,
as in estragon.divideApplesAmongPeople()
or to report one of its variables,
as in estragon.finishedDividing).
Thus, reference variables form a type just like int variables do.
| Type | Constraints | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| reference variable |
|
||||||||||
Methods, too, have a type.
When defining a method we must tell the stage manager
what type of values that actors executing the method will return,
as we saw with divideApplesAmongPeople(),
or we must use the word void
when such actors won't return any values at all,
as we saw with main().
Classes also have a type---all classes are of type Class.
Besides obeying the new operator though,
entities of type Class have special properties
that we won't see more of until much later on.
| Type | Constraints | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
Class
|
|
||||||||||
Finally, objects also have types.
An object's type is the class it belongs to.
In other words, an actor's type is the role it plays.
Other actors, when seeing the actor's role,
will know exactly what the actor is capable of doing---that is,
its type.
For example, here is the new type we've added to the stage manager's
repertoire simply by writing a role defining the type:
| Type | Constraints | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
SimpleRole
|
|
||||||||||
The new type, SimpleRole,
can hold two int variables
plus a boolean variable.
So whereas an int variable can only hold any one of
about four billion different values,
and a boolean variable can only hold any one of
two different values,
a SimpleRole object
(an object of type SimpleRole)
can hold any one of roughly four billion times four billion times two
different values,
or roughly 32 million million million different values.
Since the value that an entity currently holds
out of the set of all possible values it
could be holding is what specifies that entity's state,
a SimpleRole object can be in any one of
roughly 32 million million million different states.
Depending on what methods we have written for it (that is, its behavior)
each one of those states might trigger it to do something new.
So even a simple object might have a huge repertoire of
states and behaviors.
In sum,
classes have a type---it's type Class;
objects have a type---it's the name of the class they play;
methods have a type---it's the type of the values they return;
values have a type;
expressions have a type;
int, double, and boolean
variables have a type---it's the particular set of values they will accept
and the particular set of operators they will allow;
and reference variables have a type,
although exactly what that is is a little tricky to understand just now.
For the time being, assume that a reference variable
must have the same type as the object it's naming.
Everything in Java (except for null) has a type.
Stuff and Operators on Stuff
The Java interpreter gains great power from types. Every time we define a new class, we add a new type to the Java interpreter's repertoire. So as we add classes, we're making the language the interpreter understands richer and richer. The richer its language becomes, the more likely is it that when we come to solve a new problem it is already smart enough to understand major chunks of our solution. Whatever it still doesn't understand we then have to explain to it in laborious detail in terms of things it understands.
All of programming can be summed up in SimpleRole's
divideApplesAmongPeople() method.
All programming consists of defining stuff and then manipulating that stuff.
The stuff goes by various names depending on the language,
but usually it's kept in variables
(little boxes with names, values, and types),
and the things we can do to that stuff
also go by various names depending on the language,
but usually they're operators of one kind or another
that can be applied to the stuff---for example,
operators like addition and subtraction.
The Java interpreter has two main kinds of stuff
and two corresponding main kinds of operators to manipulate that stuff.
It inherited the simpler kinds of stuff
(int, double, and boolean values)
from older languages, going back decades.
The operators on those kinds of stuff are equally simple---usually
arithmetic operators plus a few others.
Then there are the more complicated kinds of stuff (objects).
These can hold multiple types of variables,
with each variable holding a number or boolean,
but it can also hold reference variables that hold object names.
Further, it can operate on that more complicated stuff
in more interesting ways than simply adding or dividing two numbers
since every method we define specifies a new way of operating on stuff.
So, really, there are only two things in programming: stuff and operators on stuff. There are simple kinds of stuff (numbers, for example) plus simple kinds of operators on that stuff (arithmetic operators, for instance), and then there are complex kinds of stuff (objects) and complex operators to manipulate that more complex stuff (methods).
Programming is hard because the types of stuff each language understands all by itself---even ultra-modern ones like Java---are far simpler than the types of stuff a reasonable problem is made of. We want to build bridges and space shuttles and nuclear power stations but all we have is a stack of a billion paperclips, pushpins, postit notes, and a dozen pots of glue.
So programming is a process of building up more and yet more complex stuff piece by piece from simpler stuff by adding more and yet more complex operators to work with that simpler stuff until the stuff and its operators are about as complex as the problem we're trying to solve. Once we get to that stage, we can describe the solution to the problem naturally in terms of elements of the problem itself, rather than in terms of tedious low-level details of the machine we're using to solve the problem.