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.
Similarly, all Java entities are typed.
An entity's type specifies both what it is and what it can do.
So typing is more than just the set of values we can put into an entity,
or get out of it,
it's also the set of things that we can do to those values.
Type double
The type double
has a certain set of decimal values,
but it also has 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 decimal numbers in a certain range and to a certain precision
are legal double
values.
Type int
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
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.
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 value we've neglected,
and that is the value that's in a reference variable
before we use it to store any actor's name (that is, a real reference value).
That special value is null
.
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 to reference variables,
test whether two of them are the same or not,
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))
fred.finishedProcessing
(((e / f) > (g * h)) && true)
(fred.finishedProcessing == 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
the following expression is true
:
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 that out 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 fred.divideApplesAmongPeople()
or to recall one of its variables,
as in fred.finishedprocessing
).
Thus, reference variables are types just like int
variables.
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 later on.
Type | Constraints | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
Class
|
|
Finally, objects also have types.
An object's type is the class that it belongs to.
In other words, an actor's type is the script that it follows.
Other actors, when seeing the actor's script,
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 script defining the type:
Type | Constraints | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
FredsScript
|
|
The new type, FredsScript
,
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 FredsScript
object
(an object of type FredsScript
)
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 FredsScript
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 obey;
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 FredsScript
'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 some 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 very naturally in terms of elements of the problem itself, rather than in terms of low-level details of the machine we're using to solve the problem.