character study


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
Values (roughly) -10308 to +10308
with 15 digits of precision
Operators + - * /
< <= > >= != ==
int
Values (roughly) -2 billion to +2 billion
Operators + - * / %
< <= > >= != ==
boolean
Values true false
Operators && || !
!= ==

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
Values an actor's (true) name or null
Operators .
!= ==

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
Values a class name
Operators new

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
Values int, int, boolean
Operators divideApplesAmongPeople()

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.


last | | contents | | next