ZeePedia

Controlling Program Flow:Using Java operators, Execution control, true and false

<< Everything is an Object:You manipulate objects with references, Your first Java program
Initialization & Cleanup:Method overloading, Member initialization >>
img
The size of the boolean type is not explicitly defined; it is only specified
to be able to take the literal values true or false.
The primitive data types also have "wrapper" classes for them. That
means that if you want to make a nonprimitive object on the heap to
represent that primitive type, you use the associated wrapper. For
example:
char c = 'x';
Character C = new Character(c);
Or you could also use:
Character C = new Character('x');
The reasons for doing this will be shown in a later chapter.
High-precision numbers
Java includes two classes for performing high-precision arithmetic:
BigInteger and BigDecimal. Although these approximately fit into the
same category as the "wrapper" classes, neither one has a primitive
analogue.
Both classes have methods that provide analogues for the operations that
you perform on primitive types. That is, you can do anything with a
BigInteger or BigDecimal that you can with an int or float, it's just
that you must use method calls instead of operators. Also, since there's
more involved, the operations will be slower. You're exchanging speed for
accuracy.
BigInteger supports arbitrary-precision integers. This means that you
can accurately represent integral values of any size without losing any
information during operations.
BigDecimal is for arbitrary-precision fixed-point numbers; you can use
these for accurate monetary calculations, for example.
Consult your online documentation for details about the constructors and
methods you can call for these two classes.
106
Thinking in Java
img
Arrays in Java
Virtually all programming languages support arrays. Using arrays in C
and C++ is perilous because those arrays are only blocks of memory. If a
program accesses the array outside of its memory block or uses the
memory before initialization (common programming errors) there will be
unpredictable results.
One of the primary goals of Java is safety, so many of the problems that
plague programmers in C and C++ are not repeated in Java. A Java array
is guaranteed to be initialized and cannot be accessed outside of its range.
The range checking comes at the price of having a small amount of
memory overhead on each array as well as verifying the index at run-time,
but the assumption is that the safety and increased productivity is worth
the expense.
When you create an array of objects, you are really creating an array of
references, and each of those references is automatically initialized to a
special value with its own keyword: null. When Java sees null, it
recognizes that the reference in question isn't pointing to an object. You
must assign an object to each reference before you use it, and if you try to
use a reference that's still null, the problem will be reported at run-time.
Thus, typical array errors are prevented in Java.
You can also create an array of primitives. Again, the compiler guarantees
initialization because it zeroes the memory for that array.
Arrays will be covered in detail in later chapters.
You never need to
destroy an object
In most programming languages, the concept of the lifetime of a variable
occupies a significant portion of the programming effort. How long does
the variable last? If you are supposed to destroy it, when should you?
Confusion over variable lifetimes can lead to a lot of bugs, and this section
shows how Java greatly simplifies the issue by doing all the cleanup work
for you.
Chapter 2: Everything is an Object
107
img
Scoping
Most procedural languages have the concept of scope. This determines
both the visibility and lifetime of the names defined within that scope. In
C, C++, and Java, scope is determined by the placement of curly braces
{}. So for example:
{
int x = 12;
/* only x available */
{
int q = 96;
/* both x & q available */
}
/* only x available */
/* q "out of scope" */
}
A variable defined within a scope is available only to the end of that scope.
Indentation makes Java code easier to read. Since Java is a free-form
language, the extra spaces, tabs, and carriage returns do not affect the
resulting program.
Note that you cannot do the following, even though it is legal in C and
C++:
{
int x = 12;
{
int x = 96; /* illegal */
}
}
The compiler will announce that the variable x has already been defined.
Thus the C and C++ ability to "hide" a variable in a larger scope is not
allowed because the Java designers thought that it led to confusing
programs.
108
Thinking in Java
img
Scope of objects
Java objects do not have the same lifetimes as primitives. When you
create a Java object using new, it hangs around past the end of the scope.
Thus if you use:
{
String s = new String("a string");
} /* end of scope */
the reference s vanishes at the end of the scope. However, the String
object that s was pointing to is still occupying memory. In this bit of code,
there is no way to access the object because the only reference to it is out
of scope. In later chapters you'll see how the reference to the object can be
passed around and duplicated during the course of a program.
It turns out that because objects created with new stay around for as long
as you want them, a whole slew of C++ programming problems simply
vanish in Java. The hardest problems seem to occur in C++ because you
don't get any help from the language in making sure that the objects are
available when they're needed. And more important, in C++ you must
make sure that you destroy the objects when you're done with them.
That brings up an interesting question. If Java leaves the objects lying
around, what keeps them from filling up memory and halting your
program? This is exactly the kind of problem that would occur in C++.
This is where a bit of magic happens. Java has a garbage collector, which
looks at all the objects that were created with new and figures out which
ones are not being referenced anymore. Then it releases the memory for
those objects, so the memory can be used for new objects. This means that
you never need to worry about reclaiming memory yourself. You simply
create objects, and when you no longer need them they will go away by
themselves. This eliminates a certain class of programming problem: the
so-called "memory leak," in which a programmer forgets to release
memory.
Chapter 2: Everything is an Object
109
img
Creating new
data types: class
If everything is an object, what determines how a particular class of object
looks and behaves? Put another way, what establishes the type of an
object? You might expect there to be a keyword called "type," and that
certainly would have made sense. Historically, however, most object-
oriented languages have used the keyword class to mean "I'm about to
tell you what a new type of object looks like." The class keyword (which is
so common that it will not be emboldened throughout this book) is
followed by the name of the new type. For example:
class ATypeName { /* class body goes here */ }
This introduces a new type, so you can now create an object of this type
using new:
ATypeName a = new ATypeName();
In ATypeName, the class body consists only of a comment (the stars and
slashes and what is inside, which will be discussed later in this chapter),
so there is not too much that you can do with it. In fact, you cannot tell it
to do much of anything (that is, you cannot send it any interesting
messages) until you define some methods for it.
Fields and methods
When you define a class (and all you do in Java is define classes, make
objects of those classes, and send messages to those objects), you can put
two types of elements in your class: data members (sometimes called
fields), and member functions (typically called methods). A data member
is an object of any type that you can communicate with via its reference. It
can also be one of the primitive types (which isn't a reference). If it is a
reference to an object, you must initialize that reference to connect it to an
actual object (using new, as seen earlier) in a special function called a
constructor (described fully in Chapter 4). If it is a primitive type you can
initialize it directly at the point of definition in the class. (As you'll see
later, references can also be initialized at the point of definition.)
110
Thinking in Java
img
Each object keeps its own storage for its data members; the data members
are not shared among objects. Here is an example of a class with some
data members:
class DataOnly {
int i;
float f;
boolean b;
}
This class doesn't do anything, but you can create an object:
DataOnly d = new DataOnly();
You can assign values to the data members, but you must first know how
to refer to a member of an object. This is accomplished by stating the
name of the object reference, followed by a period (dot), followed by the
name of the member inside the object:
objectReference.member
For example:
d.i = 47;
d.f = 1.1f;
d.b = false;
It is also possible that your object might contain other objects that contain
data you'd like to modify. For this, you just keep "connecting the dots."
For example:
myPlane.leftTank.capacity = 100;
The DataOnly class cannot do much of anything except hold data,
because it has no member functions (methods). To understand how those
work, you must first understand arguments and return values, which will
be described shortly.
Default values for primitive members
When a primitive data type is a member of a class, it is guaranteed to get a
default value if you do not initialize it:
Chapter 2: Everything is an Object
111
img
Primitive type
Default
boolean
false
char
`\u0000' (null)
byte
(byte)0
short
(short)0
int
0
long
0L
float
0.0f
double
0.0d
Note carefully that the default values are what Java guarantees when the
variable is used as a member of a class. This ensures that member
variables of primitive types will always be initialized (something C++
doesn't do), reducing a source of bugs. However, this initial value may not
be correct or even legal for the program you are writing. It's best to always
explicitly initialize your variables.
This guarantee doesn't apply to "local" variables--those that are not fields
of a class. Thus, if within a function definition you have:
int x;
Then x will get some arbitrary value (as in C and C++); it will not
automatically be initialized to zero. You are responsible for assigning an
appropriate value before you use x. If you forget, Java definitely improves
on C++: you get a compile-time error telling you the variable might not
have been initialized. (Many C++ compilers will warn you about
uninitialized variables, but in Java these are errors.)
Methods, arguments,
and return values
Up until now, the term function has been used to describe a named
subroutine. The term that is more commonly used in Java is method, as in
"a way to do something." If you want, you can continue thinking in terms
112
Thinking in Java
img
of functions. It's really only a syntactic difference, but from now on
"method" will be used in this book rather than "function."
Methods in Java determine the messages an object can receive. In this
section you will learn how simple it is to define a method.
The fundamental parts of a method are the name, the arguments, the
return type, and the body. Here is the basic form:
returnType methodName( /* argument list */ ) {
/* Method body */
}
The return type is the type of the value that pops out of the method after
you call it. The argument list gives the types and names for the
information you want to pass into the method. The method name and
argument list together uniquely identify the method.
Methods in Java can be created only as part of a class. A method can be
called only for an object,2 and that object must be able to perform that
method call. If you try to call the wrong method for an object, you'll get an
error message at compile-time. You call a method for an object by naming
the object followed by a period (dot), followed by the name of the method
and its argument list, like this: objectName.methodName(arg1,
arg2, arg3). For example, suppose you have a method f( ) that takes no
arguments and returns a value of type int. Then, if you have an object
called a for which f( ) can be called, you can say this:
int x = a.f();
The type of the return value must be compatible with the type of x.
This act of calling a method is commonly referred to as sending a
message to an object. In the above example, the message is f( ) and the
object is a. Object-oriented programming is often summarized as simply
"sending messages to objects."
2 static methods, which you'll learn about soon, can be called for the class, without an
object.
Chapter 2: Everything is an Object
113
img
The argument list
The method argument list specifies what information you pass into the
method. As you might guess, this information--like everything else in
Java--takes the form of objects. So, what you must specify in the
argument list are the types of the objects to pass in and the name to use
for each one. As in any situation in Java where you seem to be handing
objects around, you are actually passing references3. The type of the
reference must be correct, however. If the argument is supposed to be a
String, what you pass in must be a string.
Consider a method that takes a String as its argument. Here is the
definition, which must be placed within a class definition for it to be
compiled:
int storage(String s) {
return s.length() * 2;
}
This method tells you how many bytes are required to hold the
information in a particular String. (Each char in a String is 16 bits, or
two bytes, long, to support Unicode characters.) The argument is of type
String and is called s. Once s is passed into the method, you can treat it
just like any other object. (You can send messages to it.) Here, the
length( ) method is called, which is one of the methods for Strings; it
returns the number of characters in a string.
You can also see the use of the return keyword, which does two things.
First, it means "leave the method, I'm done." Second, if the method
produces a value, that value is placed right after the return statement. In
this case, the return value is produced by evaluating the expression
s.length( ) * 2.
You can return any type you want, but if you don't want to return
anything at all, you do so by indicating that the method returns void.
Here are some examples:
3 With the usual exception of the aforementioned "special" data types boolean, char,
byte, short, int, long, float, and double. In general, though, you pass objects, which
really means you pass references to objects.
114
Thinking in Java
img
boolean flag() { return true; }
float naturalLogBase() { return 2.718f; }
void nothing() { return; }
void nothing2() {}
When the return type is void, then the return keyword is used only to
exit the method, and is therefore unnecessary when you reach the end of
the method. You can return from a method at any point, but if you've
given a non-void return type then the compiler will force you (with error
messages) to return the appropriate type of value regardless of where you
return.
At this point, it can look like a program is just a bunch of objects with
methods that take other objects as arguments and send messages to those
other objects. That is indeed much of what goes on, but in the following
chapter you'll learn how to do the detailed low-level work by making
decisions within a method. For this chapter, sending messages will
suffice.
Building a Java program
There are several other issues you must understand before seeing your
first Java program.
Name visibility
A problem in any programming language is the control of names. If you
use a name in one module of the program, and another programmer uses
the same name in another module, how do you distinguish one name
from another and prevent the two names from "clashing?" In C this is a
particular problem because a program is often an unmanageable sea of
names. C++ classes (on which Java classes are based) nest functions
within classes so they cannot clash with function names nested within
other classes. However, C++ still allowed global data and global functions,
so clashing was still possible. To solve this problem, C++ introduced
namespaces using additional keywords.
Java was able to avoid all of this by taking a fresh approach. To produce
an unambiguous name for a library, the specifier used is not unlike an
Internet domain name. In fact, the Java creators want you to use your
Chapter 2: Everything is an Object
115
img
Internet domain name in reverse since those are guaranteed to be unique.
Since my domain name is BruceEckel.com, my utility library of foibles
would be named com.bruceeckel.utility.foibles. After your reversed
domain name, the dots are intended to represent subdirectories.
In Java 1.0 and Java 1.1 the domain extensions com, edu, org, net, etc.,
were capitalized by convention, so the library would appear:
COM.bruceeckel.utility.foibles. Partway through the development of
Java 2, however, it was discovered that this caused problems, and so now
the entire package name is lowercase.
This mechanism means that all of your files automatically live in their
own namespaces, and each class within a file must have a unique
identifier. So you do not need to learn special language features to solve
this problem--the language takes care of it for you.
Using other components
Whenever you want to use a predefined class in your program, the
compiler must know how to locate it. Of course, the class might already
exist in the same source code file that it's being called from. In that case,
you simply use the class--even if the class doesn't get defined until later in
the file. Java eliminates the "forward referencing" problem so you don't
need to think about it.
What about a class that exists in some other file? You might think that the
compiler should be smart enough to simply go and find it, but there is a
problem. Imagine that you want to use a class of a particular name, but
more than one definition for that class exists (presumably these are
different definitions). Or worse, imagine that you're writing a program,
and as you're building it you add a new class to your library that conflicts
with the name of an existing class.
To solve this problem, you must eliminate all potential ambiguities. This
is accomplished by telling the Java compiler exactly what classes you want
using the import keyword. import tells the compiler to bring in a
package, which is a library of classes. (In other languages, a library could
consist of functions and data as well as classes, but remember that all
code in Java must be written inside a class.)
116
Thinking in Java
img
Most of the time you'll be using components from the standard Java
libraries that come with your compiler. With these, you don't need to
worry about long, reversed domain names; you just say, for example:
import java.util.ArrayList;
to tell the compiler that you want to use Java's ArrayList class. However,
util contains a number of classes and you might want to use several of
them without declaring them all explicitly. This is easily accomplished by
using `*' to indicate a wild card:
import java.util.*;
It is more common to import a collection of classes in this manner than to
import classes individually.
The static keyword
Ordinarily, when you create a class you are describing how objects of that
class look and how they will behave. You don't actually get anything until
you create an object of that class with new, and at that point data storage
is created and methods become available.
But there are two situations in which this approach is not sufficient. One
is if you want to have only one piece of storage for a particular piece of
data, regardless of how many objects are created, or even if no objects are
created. The other is if you need a method that isn't associated with any
particular object of this class. That is, you need a method that you can call
even if no objects are created. You can achieve both of these effects with
the static keyword. When you say something is static, it means that data
or method is not tied to any particular object instance of that class. So
even if you've never created an object of that class you can call a static
method or access a piece of static data. With ordinary, non-static data
and methods you must create an object and use that object to access the
data or method, since non-static data and methods must know the
particular object they are working with. Of course, since static methods
don't need any objects to be created before they are used, they cannot
directly access non-static members or methods by simply calling those
other members without referring to a named object (since non-static
members and methods must be tied to a particular object).
Chapter 2: Everything is an Object
117
img
Some object-oriented languages use the terms class data and class
methods, meaning that the data and methods exist only for the class as a
whole, and not for any particular objects of the class. Sometimes the Java
literature uses these terms too.
To make a data member or method static, you simply place the keyword
before the definition. For example, the following produces a static data
member and initializes it:
class StaticTest {
static int i = 47;
}
Now even if you make two StaticTest objects, there will still be only one
piece of storage for StaticTest.i. Both objects will share the same i.
Consider:
StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();
At this point, both st1.i and st2.i have the same value of 47 since they
refer to the same piece of memory.
There are two ways to refer to a static variable. As indicated above, you
can name it via an object, by saying, for example, st2.i. You can also refer
to it directly through its class name, something you cannot do with a non-
static member. (This is the preferred way to refer to a static variable
since it emphasizes that variable's static nature.)
StaticTest.i++;
The ++ operator increments the variable. At this point, both st1.i and
st2.i will have the value 48.
Similar logic applies to static methods. You can refer to a static method
either through an object as you can with any method, or with the special
additional syntax ClassName.method( ). You define a static method in
a similar way:
class StaticFun {
static void incr() { StaticTest.i++; }
}
118
Thinking in Java
img
You can see that the StaticFun method incr( ) increments the static
data i. You can call incr( ) in the typical way, through an object:
StaticFun sf = new StaticFun();
sf.incr();
Or, because incr( ) is a static method, you can call it directly through its
class:
StaticFun.incr();
While static, when applied to a data member, definitely changes the way
the data is created (one for each class vs. the non-static one for each
object), when applied to a method it's not so dramatic. An important use
of static for methods is to allow you to call that method without creating
an object. This is essential, as we will see, in defining the main( ) method
that is the entry point for running an application.
Like any method, a static method can create or use named objects of its
type, so a static method is often used as a "shepherd" for a flock of
instances of its own type.
Your first Java program
Finally, here's the program.4 It starts by printing a string, and then the
date, using the Date class from the Java standard library. Note that an
additional style of comment is introduced here: the `//', which is a
comment until the end of the line:
// HelloDate.java
4 Some programming environments will flash programs up on the screen and close them
before you've had a chance to see the results. You can put in the following bit of code at the
end of main( ) to pause the output:
try {
System.in.read();
} catch(Exception e) {}
This will pause the output until you press "Enter" (or any other key). This code involves
concepts that will not be introduced until much later in the book, so you won't understand
it until then, but it will do the trick.
Chapter 2: Everything is an Object
119
img
import java.util.*;
public class HelloDate {
public static void main(String[] args) {
System.out.println("Hello, it's: ");
System.out.println(new Date());
}
}
At the beginning of each program file, you must place the import
statement to bring in any extra classes you'll need for the code in that file.
Note that I say "extra;" that's because there's a certain library of classes
that are automatically brought into every Java file: java.lang. Start up
your Web browser and look at the documentation from Sun. (If you
haven't downloaded it from java.sun.com or otherwise installed the Java
documentation, do so now). If you look at the list of the packages, you'll
see all the different class libraries that come with Java. Select java.lang.
This will bring up a list of all the classes that are part of that library. Since
java.lang is implicitly included in every Java code file, these classes are
automatically available. There's no Date class listed in java.lang, which
means you must import another library to use that. If you don't know the
library where a particular class is, or if you want to see all of the classes,
you can select "Tree" in the Java documentation. Now you can find every
single class that comes with Java. Then you can use the browser's "find"
function to find Date. When you do you'll see it listed as java.util.Date,
which lets you know that it's in the util library and that you must import
java.util.* in order to use Date.
If you go back to the beginning, select java.lang and then System, you'll
see that the System class has several fields, and if you select out you'll
discover that it's a static PrintStream object. Since it's static you don't
need to create anything. The out object is always there and you can just
use it. What you can do with this out object is determined by the type it
is: a PrintStream. Conveniently, PrintStream is shown in the
description as a hyperlink, so if you click on that you'll see a list of all the
methods you can call for PrintStream. There are quite a few and these
will be covered later in this book. For now all we're interested in is
println( ), which in effect means "print what I'm giving you out to the
console and end with a new line." Thus, in any Java program you write
120
Thinking in Java
img
you can say System.out.println("things") whenever you want to print
something to the console.
The name of the class is the same as the name of the file. When you're
creating a stand-alone program such as this one, one of the classes in the
file must have the same name as the file. (The compiler complains if you
don't do this.) That class must contain a method called main( ) with the
signature shown:
public static void main(String[] args) {
The public keyword means that the method is available to the outside
world (described in detail in Chapter 5). The argument to main( ) is an
array of String objects. The args won't be used in this program, but the
Java compiler insists that they be there because they hold the arguments
invoked on the command line.
The line that prints the date is quite interesting:
System.out.println(new Date());
Consider the argument: a Date object is being created just to send its
value to println( ). As soon as this statement is finished, that Date is
unnecessary, and the garbage collector can come along and get it anytime.
We don't need to worry about cleaning it up.
Compiling and running
To compile and run this program, and all the other programs in this book,
you must first have a Java programming environment. There are a
number of third-party development environments, but in this book we
will assume that you are using the JDK from Sun, which is free. If you are
using another development system, you will need to look in the
documentation for that system to determine how to compile and run
programs.
Get on the Internet and go to java.sun.com. There you will find
information and links that will lead you through the process of
downloading and installing the JDK for your particular platform.
Once the JDK is installed, and you've set up your computer's path
information so that it will find javac and java, download and unpack the
Chapter 2: Everything is an Object
121
img
source code for this book (you can find it on the CD ROM that's bound in
with this book, or at ). This will create a
subdirectory for each chapter in this book. Move to subdirectory c02 and
type:
javac HelloDate.java
This command should produce no response. If you get any kind of an
error message it means you haven't installed the JDK properly and you
need to investigate those problems.
On the other hand, if you just get your command prompt back, you can
type:
java HelloDate
and you'll get the message and the date as output.
This is the process you can use to compile and run each of the programs in
this book. However, you will see that the source code for this book also
has a file called makefile in each chapter, and this contains "make"
commands for automatically building the files for that chapter. See this
book's Web page at for details on how to use the
makefiles.
Comments and embedded
documentation
There are two types of comments in Java. The first is the traditional C-
style comment that was inherited by C++. These comments begin with a
/* and continue, possibly across many lines, until a */. Note that many
programmers will begin each line of a continued comment with a *, so
you'll often see:
/* This is a comment
*  that continues
*  across lines
*/
122
Thinking in Java
img
Remember, however, that everything inside the /* and */ is ignored, so
there's no difference in saying:
/* This is a comment that
continues across lines */
The second form of comment comes from C++. It is the single-line
comment, which starts at a // and continues until the end of the line. This
type of comment is convenient and commonly used because it's easy. You
don't need to hunt on the keyboard to find / and then * (instead, you just
press the same key twice), and you don't need to close the comment. So
you will often see:
// this is a one-line comment
Comment documentation
One of the thoughtful parts of the Java language is that the designers
didn't consider writing code to be the only important activity--they also
thought about documenting it. Possibly the biggest problem with
documenting code has been maintaining that documentation. If the
documentation and the code are separate, it becomes a hassle to change
the documentation every time you change the code. The solution seems
simple: link the code to the documentation. The easiest way to do this is
to put everything in the same file. To complete the picture, however, you
need a special comment syntax to mark special documentation, and a tool
to extract those comments and put them in a useful form. This is what
Java has done.
The tool to extract the comments is called javadoc. It uses some of the
technology from the Java compiler to look for special comment tags you
put in your programs. It not only extracts the information marked by
these tags, but it also pulls out the class name or method name that
adjoins the comment. This way you can get away with the minimal
amount of work to generate decent program documentation.
The output of javadoc is an HTML file that you can view with your Web
browser. This tool allows you to create and maintain a single source file
and automatically generate useful documentation. Because of javadoc we
Chapter 2: Everything is an Object
123
img
have a standard for creating documentation, and it's easy enough that we
can expect or even demand documentation with all Java libraries.
Syntax
All of the javadoc commands occur only within /** comments. The
comments end with */ as usual. There are two primary ways to use
javadoc: embed HTML, or use "doc tags." Doc tags are commands that
start with a `@' and are placed at the beginning of a comment line. (A
leading `*', however, is ignored.)
There are three "types" of comment documentation, which correspond to
the element the comment precedes: class, variable, or method. That is, a
class comment appears right before the definition of a class; a variable
comment appears right in front of the definition of a variable, and a
method comment appears right in front of the definition of a method. As a
simple example:
/** A class comment */
public class docTest {
/** A variable comment */
public int i;
/** A method comment */
public void f() {}
}
Note that javadoc will process comment documentation for only public
and protected members. Comments for private and "friendly"
members (see Chapter 5) are ignored and you'll see no output. (However,
you can use the -private flag to include private members as well.) This
makes sense, since only public and protected members are available
outside the file, which is the client programmer's perspective. However,
all class comments are included in the output.
The output for the above code is an HTML file that has the same standard
format as all the rest of the Java documentation, so users will be
comfortable with the format and can easily navigate your classes. It's
worth entering the above code, sending it through javadoc and viewing
the resulting HTML file to see the results.
124
Thinking in Java
img
Embedded HTML
Javadoc passes HTML commands through to the generated HTML
document. This allows you full use of HTML; however, the primary
motive is to let you format code, such as:
/**
* <pre>
* System.out.println(new Date());
* </pre>
*/
You can also use HTML just as you would in any other Web document to
format the regular text in your descriptions:
/**
* You can <em>even</em> insert a list:
* <ol>
* <li> Item one
* <li> Item two
* <li> Item three
* </ol>
*/
Note that within the documentation comment, asterisks at the beginning
of a line are thrown away by javadoc, along with leading spaces. Javadoc
reformats everything so that it conforms to the standard documentation
appearance. Don't use headings such as <h1> or <hr> as embedded
HTML because javadoc inserts its own headings and yours will interfere
with them.
All types of comment documentation--class, variable, and method--can
support embedded HTML.
@see: referring to other classes
All three types of comment documentation (class, variable, and method)
can contain @see tags, which allow you to refer to the documentation in
other classes. Javadoc will generate HTML with the @see tags
hyperlinked to the other documentation. The forms are:
@see classname
Chapter 2: Everything is an Object
125
img
@see fully-qualified-classname
@see fully-qualified-classname#method-name
Each one adds a hyperlinked "See Also" entry to the generated
documentation. Javadoc will not check the hyperlinks you give it to make
sure they are valid.
Class documentation tags
Along with embedded HTML and @see references, class documentation
can include tags for version information and the author's name. Class
documentation can also be used for interfaces (see Chapter 8).
@version
This is of the form:
@version version-information
in which version-information is any significant information you see fit
to include. When the -version flag is placed on the javadoc command
line, the version information will be called out specially in the generated
HTML documentation.
@author
This is of the form:
@author author-information
in which author-information is, presumably, your name, but it could
also include your email address or any other appropriate information.
When the -author flag is placed on the javadoc command line, the author
information will be called out specially in the generated HTML
documentation.
You can have multiple author tags for a list of authors, but they must be
placed consecutively. All the author information will be lumped together
into a single paragraph in the generated HTML.
126
Thinking in Java
img
@since
This tag allows you to indicate the version of this code that began using a
particular feature. You'll see it appearing in the HTML Java
documentation to indicate what version of the JDK is used.
Variable documentation tags
Variable documentation can include only embedded HTML and @see
references.
Method documentation tags
As well as embedded documentation and @see references, methods allow
documentation tags for parameters, return values, and exceptions.
@param
This is of the form:
@param parameter-name description
in which parameter-name is the identifier in the parameter list, and
description is text that can continue on subsequent lines. The
description is considered finished when a new documentation tag is
encountered. You can have any number of these, presumably one for each
parameter.
@return
This is of the form:
@return description
in which description gives you the meaning of the return value. It can
continue on subsequent lines.
@throws
Exceptions will be demonstrated in Chapter 10, but briefly they are
objects that can be "thrown" out of a method if that method fails.
Although only one exception object can emerge when you call a method, a
particular method might produce any number of different types of
Chapter 2: Everything is an Object
127
img
exceptions, all of which need descriptions. So the form for the exception
tag is:
@throws fully-qualified-class-name description
in which fully-qualified-class-name gives an unambiguous name of an
exception class that's defined somewhere, and description (which can
continue on subsequent lines) tells you why this particular type of
exception can emerge from the method call.
@deprecated
This is used to tag features that were superseded by an improved feature.
The deprecated tag is a suggestion that you no longer use this particular
feature, since sometime in the future it is likely to be removed. A method
that is marked @deprecated causes the compiler to issue a warning if it
is used.
Documentation example
Here is the first Java program again, this time with documentation
comments added:
//: c02:HelloDate.java
import java.util.*;
/** The first Thinking in Java example program.
* Displays a string and today's date.
* @author Bruce Eckel
* @author
* @version 2.0
*/
public class HelloDate {
/** Sole entry point to class & application
* @param args array of string arguments
* @return No return value
* @exception exceptions No exceptions thrown
*/
public static void main(String[] args) {
System.out.println("Hello, it's: ");
System.out.println(new Date());
}
128
Thinking in Java
img
} ///:~
The first line of the file uses my own technique of putting a `:' as a special
marker for the comment line containing the source file name. That line
contains the path information to the file (in this case, c02 indicates
Chapter 2) followed by the file name5. The last line also finishes with a
comment, and this one indicates the end of the source code listing, which
allows it to be automatically extracted from the text of this book and
checked with a compiler.
Coding style
The unofficial standard in Java is to capitalize the first letter of a class
name. If the class name consists of several words, they are run together
(that is, you don't use underscores to separate the names), and the first
letter of each embedded word is capitalized, such as:
class AllTheColorsOfTheRainbow { // ...
For almost everything else: methods, fields (member variables), and
object reference names, the accepted style is just as it is for classes except
that the first letter of the identifier is lowercase. For example:
class AllTheColorsOfTheRainbow {
int anIntegerRepresentingColors;
void changeTheHueOfTheColor(int newHue) {
// ...
}
// ...
}
Of course, you should remember that the user must also type all these
long names, and so be merciful.
The Java code you will see in the Sun libraries also follows the placement
of open-and-close curly braces that you see used in this book.
5 A tool that I created using Python (see www.Python.org) uses this information to extract
the code files, put them in appropriate subdirectories, and create makefiles.
Chapter 2: Everything is an Object
129
img
Summary
In this chapter you have seen enough of Java programming to understand
how to write a simple program, and you have gotten an overview of the
language and some of its basic ideas. However, the examples so far have
all been of the form "do this, then do that, then do something else." What
if you want the program to make choices, such as "if the result of doing
this is red, do that; if not, then do something else"? The support in Java
for this fundamental programming activity will be covered in the next
chapter.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from .
1.
Following the HelloDate.java example in this chapter, create a
"hello, world" program that simply prints out that statement. You
need only a single method in your class (the "main" one that gets
executed when the program starts). Remember to make it static
and to include the argument list, even though you don't use the
argument list. Compile the program with javac and run it using
java. If you are using a different development environment than
the JDK, learn how to compile and run programs in that
environment.
2.
Find the code fragments involving ATypeName and turn them
into a program that compiles and runs.
3.
Turn the DataOnly code fragments into a program that compiles
and runs.
4.
Modify Exercise 3 so that the values of the data in DataOnly are
assigned to and printed in main( ).
5.
Write a program that includes and calls the storage( ) method
defined as a code fragment in this chapter.
6.
Turn the StaticFun code fragments into a working program.
130
Thinking in Java
img
7.
Write a program that prints three arguments taken from the
command line. To do this, you'll need to index into the command-
line array of Strings.
8.
Turn the AllTheColorsOfTheRainbow example into a program
that compiles and runs.
9.
Find the code for the second version of HelloDate.java, which is
the simple comment documentation example. Execute javadoc
on the file and view the results with your Web browser.
10.
Turn docTest into a file that compiles and then run it through
javadoc. Verify the resulting documentation with your Web
browser.
11.
Add an HTML list of items to the documentation in Exercise 10.
12.
Take the program in Exercise 1 and add comment documentation
to it. Extract this comment documentation into an HTML file
using javadoc and view it with your Web browser.
Chapter 2: Everything is an Object
131
img
3: Controlling
Program Flow
Like a sentient creature, a program must manipulate its
world and make choices during execution.
In Java you manipulate objects and data using operators, and you make
choices with execution control statements. Java was inherited from C++,
so most of these statements and operators will be familiar to C and C++
programmers. Java has also added some improvements and
simplifications.
If you find yourself floundering a bit in this chapter, make sure you go
through the multimedia CD ROM bound into this book: Thinking in C:
Foundations for Java and C++. It contains audio lectures, slides,
exercises, and solutions specifically designed to bring you up to speed
with the C syntax necessary to learn Java.
Using Java operators
An operator takes one or more arguments and produces a new value. The
arguments are in a different form than ordinary method calls, but the
effect is the same. You should be reasonably comfortable with the general
concept of operators from your previous programming experience.
Addition (+), subtraction and unary minus (-), multiplication (*), division
(/), and assignment (=) all work much the same in any programming
language.
All operators produce a value from their operands. In addition, an
operator can change the value of an operand. This is called a side effect.
The most common use for operators that modify their operands is to
generate the side effect, but you should keep in mind that the value
produced is available for your use just as in operators without side effects.
133
img
Almost all operators work only with primitives. The exceptions are `=',
`==' and `!=', which work with all objects (and are a point of confusion for
objects). In addition, the String class supports `+' and `+='.
Precedence
Operator precedence defines how an expression evaluates when several
operators are present. Java has specific rules that determine the order of
evaluation. The easiest one to remember is that multiplication and
division happen before addition and subtraction. Programmers often
forget the other precedence rules, so you should use parentheses to make
the order of evaluation explicit. For example:
A = X + Y - 2/2 + Z;
has a very different meaning from the same statement with a particular
grouping of parentheses:
A = X + (Y - 2)/(2 + Z);
Assignment
Assignment is performed with the operator =. It means "take the value of
the right-hand side (often called the rvalue) and copy it into the left-hand
side (often called the lvalue). An rvalue is any constant, variable or
expression that can produce a value, but an lvalue must be a distinct,
named variable. (That is, there must be a physical space to store a value.)
For instance, you can assign a constant value to a variable (A = 4;), but
you cannot assign anything to constant value--it cannot be an lvalue. (You
can't say 4 = A;.)
Assignment of primitives is quite straightforward. Since the primitive
holds the actual value and not a reference to an object, when you assign
primitives you copy the contents from one place to another. For example,
if you say A = B for primitives, then the contents of B are copied into A. If
you then go on to modify A, B is naturally unaffected by this modification.
As a programmer, this is what you've come to expect for most situations.
When you assign objects, however, things change. Whenever you
manipulate an object, what you're manipulating is the reference, so when
you assign "from one object to another" you're actually copying a
134
Thinking in Java
img
reference from one place to another. This means that if you say C = D for
objects, you end up with both C and D pointing to the object that,
originally, only D pointed to. The following example will demonstrate
this.
Here's the example:
//: c03:Assignment.java
// Assignment with objects is a bit tricky.
class Number {
int i;
}
public class Assignment {
public static void main(String[]
args) {
Number n1 = new Number();
Number n2 = new Number();
n1.i = 9;
n2.i = 47;
System.out.println("1: n1.i: "
+ n1.i +
", n2.i: " + n2.i);
n1 = n2;
System.out.println("2: n1.i: "
+ n1.i +
", n2.i: " + n2.i);
n1.i = 27;
System.out.println("3: n1.i: "
+ n1.i +
", n2.i: " + n2.i);
}
} ///:~
The Number class is simple, and two instances of it (n1 and n2) are
created within main( ). The i value within each Number is given a
different value, and then n2 is assigned to n1, and n1 is changed. In many
programming languages you would expect n1 and n2 to be independent
at all times, but because you've assigned a reference here's the output
you'll see:
1: n1.i: 9, n2.i: 47
2: n1.i: 47, n2.i: 47
3: n1.i: 27, n2.i: 27
Chapter 3: Controlling Program Flow
135
img
Changing the n1 object appears to change the n2 object as well! This is
because both n1 and n2 contain the same reference, which is pointing to
the same object. (The original reference that was in n1 that pointed to the
object holding a value of 9 was overwritten during the assignment and
effectively lost; its object will be cleaned up by the garbage collector.)
This phenomenon is often called aliasing and it's a fundamental way that
Java works with objects. But what if you don't want aliasing to occur in
this case? You could forego the assignment and say:
n1.i = n2.i;
This retains the two separate objects instead of tossing one and tying n1
and n2 to the same object, but you'll soon realize that manipulating the
fields within objects is messy and goes against good object-oriented
design principles. This is a nontrivial topic, so it is left for Appendix A,
which is devoted to aliasing. In the meantime, you should keep in mind
that assignment for objects can add surprises.
Aliasing during method calls
Aliasing will also occur when you pass an object into a method:
//: c03:PassObject.java
// Passing objects to methods may not be what
// you're used to.
class Letter {
char c;
}
public class PassObject {
static void f(Letter y) {
y.c = 'z';
}
public static void main(String[] args) {
Letter x = new Letter();
x.c = 'a';
System.out.println("1: x.c: " + x.c);
f(x);
System.out.println("2: x.c: " + x.c);
}
136
Thinking in Java
img
} ///:~
In many programming languages, the method f( ) would appear to be
making a copy of its argument Letter y inside the scope of the method.
But once again a reference is being passed so the line
y.c = 'z';
is actually changing the object outside of f( ). The output shows this:
1: x.c: a
2: x.c: z
Aliasing and its solution is a complex issue and, although you must wait
until Appendix A for all the answers, you should be aware of it at this
point so you can watch for pitfalls.
Mathematical operators
The basic mathematical operators are the same as the ones available in
most programming languages: addition (+), subtraction (-), division (/),
multiplication (*) and modulus (%, which produces the remainder from
integer division). Integer division truncates, rather than rounds, the
result.
Java also uses a shorthand notation to perform an operation and an
assignment at the same time. This is denoted by an operator followed by
an equal sign, and is consistent with all the operators in the language
(whenever it makes sense). For example, to add 4 to the variable x and
assign the result to x, use: x += 4.
This example shows the use of the mathematical operators:
//: c03:MathOps.java
// Demonstrates the mathematical operators.
import java.util.*;
public class MathOps {
// Create a shorthand to save typing:
static void prt(String s) {
System.out.println(s);
}
// shorthand to print a string and an int:
Chapter 3: Controlling Program Flow
137
img
static void pInt(String s, int i) {
prt(s + " = " + i);
}
// shorthand to print a string and a float:
static void pFlt(String s, float f) {
prt(s + " = " + f);
}
public static void main(String[] args) {
// Create a random number generator,
// seeds with current time by default:
Random rand = new Random();
int i, j, k;
// '%' limits maximum value to 99:
j = rand.nextInt() % 100;
k = rand.nextInt() % 100;
pInt("j",j);  pInt("k",k);
i = j + k; pInt("j + k", i);
i = j - k; pInt("j - k", i);
i = k / j; pInt("k / j", i);
i = k * j; pInt("k * j", i);
i = k % j; pInt("k % j", i);
j %= k; pInt("j %= k", j);
// Floating-point number tests:
float u,v,w;  // applies to doubles, too
v = rand.nextFloat();
w = rand.nextFloat();
pFlt("v", v); pFlt("w", w);
u = v + w; pFlt("v + w", u);
u = v - w; pFlt("v - w", u);
u = v * w; pFlt("v * w", u);
u = v / w; pFlt("v / w", u);
// the following also works for
// char, byte, short, int, long,
// and double:
u += v; pFlt("u += v", u);
u -= v; pFlt("u -= v", u);
u *= v; pFlt("u *= v", u);
u /= v; pFlt("u /= v", u);
}
} ///:~
138
Thinking in Java
img
The first thing you will see are some shorthand methods for printing: the
prt( ) method prints a String, the pInt( ) prints a String followed by an
int and the pFlt( ) prints a String followed by a float. Of course, they all
ultimately end up using System.out.println( ).
To generate numbers, the program first creates a Random object.
Because no arguments are passed during creation, Java uses the current
time as a seed for the random number generator. The program generates
a number of different types of random numbers with the Random object
simply by calling different methods: nextInt( ), nextLong( ),
nextFloat( ) or nextDouble( ).
The modulus operator, when used with the result of the random number
generator, limits the result to an upper bound of the operand minus one
(99 in this case).
Unary minus and plus operators
The unary minus (-) and unary plus (+) are the same operators as binary
minus and plus. The compiler figures out which use is intended by the
way you write the expression. For instance, the statement
x = -a;
has an obvious meaning. The compiler is able to figure out:
x = a * -b;
but the reader might get confused, so it is clearer to say:
x = a * (-b);
The unary minus produces the negative of the value. Unary plus provides
symmetry with unary minus, although it doesn't have any effect.
Auto increment and decrement
Java, like C, is full of shortcuts. Shortcuts can make code much easier to
type, and either easier or harder to read.
Two of the nicer shortcuts are the increment and decrement operators
(often referred to as the auto-increment and auto-decrement operators).
The decrement operator is -- and means "decrease by one unit." The
Chapter 3: Controlling Program Flow
139
img
increment operator is ++ and means "increase by one unit." If a is an int,
for example, the expression ++a is equivalent to (a = a + 1). Increment
and decrement operators produce the value of the variable as a result.
There are two versions of each type of operator, often called the prefix and
postfix versions. Pre-increment means the ++ operator appears before
the variable or expression, and post-increment means the ++ operator
appears after the variable or expression. Similarly, pre-decrement means
the -- operator appears before the variable or expression, and post-
decrement means the -- operator appears after the variable or expression.
For pre-increment and pre-decrement, (i.e., ++a or --a), the operation is
performed and the value is produced. For post-increment and post-
decrement (i.e. a++ or a--), the value is produced, then the operation is
performed. As an example:
//: c03:AutoInc.java
// Demonstrates the ++ and -- operators.
public class AutoInc {
public static void main(String[] args) {
int i = 1;
prt("i : " + i);
prt("++i : " + ++i); // Pre-increment
prt("i++ : " + i++); // Post-increment
prt("i : " + i);
prt("--i : " + --i); // Pre-decrement
prt("i-- : " + i--); // Post-decrement
prt("i : " + i);
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
The output for this program is:
i:
1
++i
:2
i++
:2
i:
3
--i
:2
140
Thinking in Java
img
i-- : 2
i:1
You can see that for the prefix form you get the value after the operation
has been performed, but with the postfix form you get the value before the
operation is performed. These are the only operators (other than those
involving assignment) that have side effects. (That is, they change the
operand rather than using just its value.)
The increment operator is one explanation for the name C++, implying
"one step beyond C." In an early Java speech, Bill Joy (one of the
creators), said that "Java=C++--" (C plus plus minus minus), suggesting
that Java is C++ with the unnecessary hard parts removed and therefore a
much simpler language. As you progress in this book you'll see that many
parts are simpler, and yet Java isn't that much easier than C++.
Relational operators
Relational operators generate a boolean result. They evaluate the
relationship between the values of the operands. A relational expression
produces true if the relationship is true, and false if the relationship is
untrue. The relational operators are less than (<), greater than (>), less
than or equal to (<=), greater than or equal to (>=), equivalent (==) and
not equivalent (!=). Equivalence and nonequivalence works with all built-
in data types, but the other comparisons won't work with type boolean.
Testing object equivalence
The relational operators == and != also work with all objects, but their
meaning often confuses the first-time Java programmer. Here's an
example:
//: c03:Equivalence.java
public class Equivalence {
public static void main(String[] args) {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println(n1 == n2);
System.out.println(n1 != n2);
}
Chapter 3: Controlling Program Flow
141
img
} ///:~
The expression System.out.println(n1 == n2) will print the result of
the boolean comparison within it. Surely the output should be true and
then false, since both Integer objects are the same. But while the
contents of the objects are the same, the references are not the same and
the operators == and != compare object references. So the output is
actually false and then true. Naturally, this surprises people at first.
What if you want to compare the actual contents of an object for
equivalence? You must use the special method equals( ) that exists for
all objects (not primitives, which work fine with == and !=). Here's how
it's used:
//: c03:EqualsMethod.java
public class EqualsMethod {
public static void main(String[] args) {
Integer n1 = new Integer(47);
Integer n2 = new Integer(47);
System.out.println(n1.equals(n2));
}
} ///:~
The result will be true, as you would expect. Ah, but it's not as simple as
that. If you create your own class, like this:
//: c03:EqualsMethod2.java
class Value {
int i;
}
public class EqualsMethod2 {
public static void main(String[] args) {
Value v1 = new Value();
Value v2 = new Value();
v1.i = v2.i = 100;
System.out.println(v1.equals(v2));
}
} ///:~
142
Thinking in Java
img
you're back to square one: the result is false. This is because the default
behavior of equals( ) is to compare references. So unless you override
equals( ) in your new class you won't get the desired behavior.
Unfortunately, you won't learn about overriding until Chapter 7, but being
aware of the way equals( ) behaves might save you some grief in the
meantime.
Most of the Java library classes implement equals( ) so that it compares
the contents of objects instead of their references.
Logical operators
The logical operators AND (&&), OR (||) and NOT (!) produce a boolean
value of true or false based on the logical relationship of its arguments.
This example uses the relational and logical operators:
//: c03:Bool.java
// Relational and logical operators.
import java.util.*;
public class Bool {
public static void main(String[] args) {
Random rand = new Random();
int i = rand.nextInt() % 100;
int j = rand.nextInt() % 100;
prt("i = " + i);
prt("j = " + j);
prt("i > j is " + (i > j));
prt("i < j is " + (i < j));
prt("i >= j is " + (i >= j));
prt("i <= j is " + (i <= j));
prt("i == j is " + (i == j));
prt("i != j is " + (i != j));
// Treating an int as a boolean is
// not legal Java
//! prt("i && j is " + (i && j));
//! prt("i || j is " + (i || j));
//! prt("!i is " + !i);
prt("(i < 10) && (j < 10) is "
Chapter 3: Controlling Program Flow
143
img
+ ((i < 10) && (j < 10)) );
prt("(i < 10) || (j < 10) is "
+ ((i < 10) || (j < 10)) );
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
You can apply AND, OR, or NOT to boolean values only. You can't use a
non-boolean as if it were a boolean in a logical expression as you can in
C and C++. You can see the failed attempts at doing this commented out
with a //! comment marker. The subsequent expressions, however,
produce boolean values using relational comparisons, then use logical
operations on the results.
One output listing looked like this:
i = 85
j=4
i > j is true
i < j is false
i >= j is true
i <= j is false
i == j is false
i != j is true
(i < 10) && (j < 10) is false
(i < 10) || (j < 10) is true
Note that a boolean value is automatically converted to an appropriate
text form if it's used where a String is expected.
You can replace the definition for int in the above program with any other
primitive data type except boolean. Be aware, however, that the
comparison of floating-point numbers is very strict. A number that is the
tiniest fraction different from another number is still "not equal." A
number that is the tiniest bit above zero is still nonzero.
Short-circuiting
When dealing with logical operators you run into a phenomenon called
"short circuiting." This means that the expression will be evaluated only
144
Thinking in Java
img
until the truth or falsehood of the entire expression can be unambiguously
determined. As a result, all the parts of a logical expression might not be
evaluated. Here's an example that demonstrates short-circuiting:
//: c03:ShortCircuit.java
// Demonstrates short-circuiting behavior.
// with logical operators.
public class ShortCircuit {
static boolean test1(int val) {
System.out.println("test1(" + val + ")");
System.out.println("result: " + (val < 1));
return val < 1;
}
static boolean test2(int val) {
System.out.println("test2(" + val + ")");
System.out.println("result: " + (val < 2));
return val < 2;
}
static boolean test3(int val) {
System.out.println("test3(" + val + ")");
System.out.println("result: " + (val < 3));
return val < 3;
}
public static void main(String[] args) {
if(test1(0) && test2(2) && test3(2))
System.out.println("expression is true");
else
System.out.println("expression is false");
}
} ///:~
Each test performs a comparison against the argument and returns true
or false. It also prints information to show you that it's being called. The
tests are used in the expression:
if(test1(0) && test2(2) && test3(2))
You might naturally think that all three tests would be executed, but the
output shows otherwise:
test1(0)
Chapter 3: Controlling Program Flow
145
img
result: true
test2(2)
result: false
expression is false
The first test produced a true result, so the expression evaluation
continues. However, the second test produced a false result. Since this
means that the whole expression must be false, why continue evaluating
the rest of the expression? It could be expensive. The reason for short-
circuiting, in fact, is precisely that; you can get a potential performance
increase if all the parts of a logical expression do not need to be evaluated.
Bitwise operators
The bitwise operators allow you to manipulate individual bits in an
integral primitive data type. Bitwise operators perform boolean algebra on
the corresponding bits in the two arguments to produce the result.
The bitwise operators come from C's low-level orientation; you were often
manipulating hardware directly and had to set the bits in hardware
registers. Java was originally designed to be embedded in TV set-top
boxes, so this low-level orientation still made sense. However, you
probably won't use the bitwise operators much.
The bitwise AND operator (&) produces a one in the output bit if both
input bits are one; otherwise it produces a zero. The bitwise OR operator
(|) produces a one in the output bit if either input bit is a one and
produces a zero only if both input bits are zero. The bitwise EXCLUSIVE
OR, or XOR (^), produces a one in the output bit if one or the other input
bit is a one, but not both. The bitwise NOT (~, also called the ones
complement operator) is a unary operator; it takes only one argument.
(All other bitwise operators are binary operators.) Bitwise NOT produces
the opposite of the input bit--a one if the input bit is zero, a zero if the
input bit is one.
The bitwise operators and logical operators use the same characters, so it
is helpful to have a mnemonic device to help you remember the meanings:
since bits are "small," there is only one character in the bitwise operators.
146
Thinking in Java
img
Bitwise operators can be combined with the = sign to unite the operation
and assignment: &=, |= and ^= are all legitimate. (Since ~ is a unary
operator it cannot be combined with the = sign.)
The boolean type is treated as a one-bit value so it is somewhat different.
You can perform a bitwise AND, OR and XOR, but you can't perform a
bitwise NOT (presumably to prevent confusion with the logical NOT). For
booleans the bitwise operators have the same effect as the logical
operators except that they do not short circuit. Also, bitwise operations on
booleans include an XOR logical operator that is not included under the
list of "logical" operators. You're prevented from using booleans in shift
expressions, which is described next.
Shift operators
The shift operators also manipulate bits. They can be used solely with
primitive, integral types. The left-shift operator (<<) produces the
operand to the left of the operator shifted to the left by the number of bits
specified after the operator (inserting zeroes at the lower-order bits). The
signed right-shift operator (>>) produces the operand to the left of the
operator shifted to the right by the number of bits specified after the
operator. The signed right shift >> uses sign extension: if the value is
positive, zeroes are inserted at the higher-order bits; if the value is
negative, ones are inserted at the higher-order bits. Java has also added
the unsigned right shift >>>, which uses zero extension: regardless of the
sign, zeroes are inserted at the higher-order bits. This operator does not
exist in C or C++.
If you shift a char, byte, or short, it will be promoted to int before the
shift takes place, and the result will be an int. Only the five low-order bits
of the right-hand side will be used. This prevents you from shifting more
than the number of bits in an int. If you're operating on a long, you'll get
a long result. Only the six low-order bits of the right-hand side will be
used so you can't shift more than the number of bits in a long.
Shifts can be combined with the equal sign (<<= or >>= or >>>=). The
lvalue is replaced by the lvalue shifted by the rvalue. There is a problem,
however, with the unsigned right shift combined with assignment. If you
use it with byte or short you don't get the correct results. Instead, these
are promoted to int and right shifted, but then truncated as they are
Chapter 3: Controlling Program Flow
147
img
assigned back into their variables, so you get -1 in those cases. The
following example demonstrates this:
//: c03:URShift.java
// Test of unsigned right shift.
public class URShift {
public static void main(String[] args) {
int i = -1;
i >>>= 10;
System.out.println(i);
long l = -1;
l >>>= 10;
System.out.println(l);
short s = -1;
s >>>= 10;
System.out.println(s);
byte b = -1;
b >>>= 10;
System.out.println(b);
b = -1;
System.out.println(b>>>10);
}
} ///:~
In the last line, the resulting value is not assigned back into b, but is
printed directly and so the correct behavior occurs.
Here's an example that demonstrates the use of all the operators involving
bits:
//: c03:BitManipulation.java
// Using the bitwise operators.
import java.util.*;
public class BitManipulation {
public static void main(String[] args) {
Random rand = new Random();
int i = rand.nextInt();
int j = rand.nextInt();
pBinInt("-1", -1);
pBinInt("+1", +1);
148
Thinking in Java
img
int maxpos = 2147483647;
pBinInt("maxpos", maxpos);
int maxneg = -2147483648;
pBinInt("maxneg", maxneg);
pBinInt("i", i);
pBinInt("~i", ~i);
pBinInt("-i", -i);
pBinInt("j", j);
pBinInt("i & j", i & j);
pBinInt("i | j", i | j);
pBinInt("i ^ j", i ^ j);
pBinInt("i << 5", i << 5);
pBinInt("i >> 5", i >> 5);
pBinInt("(~i) >> 5", (~i) >> 5);
pBinInt("i >>> 5", i >>> 5);
pBinInt("(~i) >>> 5", (~i) >>> 5);
long l = rand.nextLong();
long m = rand.nextLong();
pBinLong("-1L", -1L);
pBinLong("+1L", +1L);
long ll = 9223372036854775807L;
pBinLong("maxpos", ll);
long lln = -9223372036854775808L;
pBinLong("maxneg", lln);
pBinLong("l", l);
pBinLong("~l", ~l);
pBinLong("-l", -l);
pBinLong("m", m);
pBinLong("l & m", l & m);
pBinLong("l | m", l | m);
pBinLong("l ^ m", l ^ m);
pBinLong("l << 5", l << 5);
pBinLong("l >> 5", l >> 5);
pBinLong("(~l) >> 5", (~l) >> 5);
pBinLong("l >>> 5", l >>> 5);
pBinLong("(~l) >>> 5", (~l) >>> 5);
}
static void pBinInt(String s, int i) {
System.out.println(
s + ", int: " + i + ", binary: ");
Chapter 3: Controlling Program Flow
149
img
System.out.print("
");
for(int j = 31; j >=0; j--)
if(((1 << j) &  i) != 0)
System.out.print("1");
else
System.out.print("0");
System.out.println();
}
static void pBinLong(String s, long l) {
System.out.println(
s + ", long: " + l + ", binary: ");
System.out.print("
");
for(int i = 63; i >=0; i--)
if(((1L << i) & l) != 0)
System.out.print("1");
else
System.out.print("0");
System.out.println();
}
} ///:~
The two methods at the end, pBinInt( ) and pBinLong( ) take an int or
a long, respectively, and print it out in binary format along with a
descriptive string. You can ignore the implementation of these for now.
You'll note the use of System.out.print( ) instead of
System.out.println( ). The print( ) method does not emit a new line,
so it allows you to output a line in pieces.
As well as demonstrating the effect of all the bitwise operators for int and
long, this example also shows the minimum, maximum, +1 and -1 values
for int and long so you can see what they look like. Note that the high bit
represents the sign: 0 means positive and 1 means negative. The output
for the int portion looks like this:
-1, int: -1, binary:
11111111111111111111111111111111
+1, int: 1, binary:
00000000000000000000000000000001
maxpos, int: 2147483647, binary:
01111111111111111111111111111111
150
Thinking in Java
img
maxneg, int: -2147483648, binary:
10000000000000000000000000000000
i, int: 59081716, binary:
00000011100001011000001111110100
~i, int: -59081717, binary:
11111100011110100111110000001011
-i, int: -59081716, binary:
11111100011110100111110000001100
j, int: 198850956, binary:
00001011110110100011100110001100
i & j, int: 58720644, binary:
00000011100000000000000110000100
i | j, int: 199212028, binary:
00001011110111111011101111111100
i ^ j, int: 140491384, binary:
00001000010111111011101001111000
i << 5, int: 1890614912, binary:
01110000101100000111111010000000
i >> 5, int: 1846303, binary:
00000000000111000010110000011111
(~i) >> 5, int: -1846304, binary:
11111111111000111101001111100000
i >>> 5, int: 1846303, binary:
00000000000111000010110000011111
(~i) >>> 5, int: 132371424, binary:
00000111111000111101001111100000
The binary representation of the numbers is referred to as signed two's
complement.
Ternary if-else operator
This operator is unusual because it has three operands. It is truly an
operator because it produces a value, unlike the ordinary if-else statement
that you'll see in the next section of this chapter. The expression is of the
form:
boolean-exp ? value0 : value1
If boolean-exp evaluates to true, value0 is evaluated and its result
becomes the value produced by the operator. If boolean-exp is false,
Chapter 3: Controlling Program Flow
151
img
value1 is evaluated and its result becomes the value produced by the
operator.
Of course, you could use an ordinary if-else statement (described later),
but the ternary operator is much terser. Although C (where this operator
originated) prides itself on being a terse language, and the ternary
operator might have been introduced partly for efficiency, you should be
somewhat wary of using it on an everyday basis--it's easy to produce
unreadable code.
The conditional operator can be used for its side effects or for the value it
produces, but in general you want the value since that's what makes the
operator distinct from the if-else. Here's an example:
static int ternary(int i) {
return i < 10 ? i * 100 : i * 10;
}
You can see that this code is more compact than what you'd need to write
without the ternary operator:
static int alternative(int i) {
if (i < 10)
return i * 100;
else
return i * 10;
}
The second form is easier to understand, and doesn't require a lot more
typing. So be sure to ponder your reasons when choosing the ternary
operator.
The comma operator
The comma is used in C and C++ not only as a separator in function
argument lists, but also as an operator for sequential evaluation. The sole
place that the comma operator is used in Java is in for loops, which will
be described later in this chapter.
152
Thinking in Java
img
String operator +
There's one special usage of an operator in Java: the + operator can be
used to concatenate strings, as you've already seen. It seems a natural use
of the + even though it doesn't fit with the traditional way that + is used.
This capability seemed like a good idea in C++, so operator overloading
was added to C++ to allow the C++ programmer to add meanings to
almost any operator. Unfortunately, operator overloading combined with
some of the other restrictions in C++ turns out to be a fairly complicated
feature for programmers to design into their classes. Although operator
overloading would have been much simpler to implement in Java than it
was in C++, this feature was still considered too complex, so Java
programmers cannot implement their own overloaded operators as C++
programmers can.
The use of the String + has some interesting behavior. If an expression
begins with a String, then all operands that follow must be Strings
(remember that the compiler will turn a quoted sequence of characters
into a String):
int x = 0, y = 1, z = 2;
String sString = "x, y, z ";
System.out.println(sString + x + y + z);
Here, the Java compiler will convert x, y, and z into their String
representations instead of adding them together first. And if you say:
System.out.println(x + sString);
Java will turn x into a String.
Common pitfalls when using
operators
One of the pitfalls when using operators is trying to get away without
parentheses when you are even the least bit uncertain about how an
expression will evaluate. This is still true in Java.
An extremely common error in C and C++ looks like this:
while(x = y) {
Chapter 3: Controlling Program Flow
153
img
// ....
}
The programmer was trying to test for equivalence (==) rather than do an
assignment. In C and C++ the result of this assignment will always be
true if y is nonzero, and you'll probably get an infinite loop. In Java, the
result of this expression is not a boolean, and the compiler expects a
boolean and won't convert from an int, so it will conveniently give you a
compile-time error and catch the problem before you ever try to run the
program. So the pitfall never happens in Java. (The only time you won't
get a compile-time error is when x and y are boolean, in which case x =
y is a legal expression, and in the above case, probably an error.)
A similar problem in C and C++ is using bitwise AND and OR instead of
the logical versions. Bitwise AND and OR use one of the characters (& or
|) while logical AND and OR use two (&& and ||). Just as with = and ==,
it's easy to type just one character instead of two. In Java, the compiler
again prevents this because it won't let you cavalierly use one type where
it doesn't belong.
Casting operators
The word cast is used in the sense of "casting into a mold." Java will
automatically change one type of data into another when appropriate. For
instance, if you assign an integral value to a floating-point variable, the
compiler will automatically convert the int to a float. Casting allows you
to make this type conversion explicit, or to force it when it wouldn't
normally happen.
To perform a cast, put the desired data type (including all modifiers)
inside parentheses to the left of any value. Here's an example:
void casts() {
int i = 200;
long l = (long)i;
long l2 = (long)200;
}
As you can see, it's possible to perform a cast on a numeric value as well
as on a variable. In both casts shown here, however, the cast is
superfluous, since the compiler will automatically promote an int value to
154
Thinking in Java
img
a long when necessary. However, you are allowed to use superfluous
casts in to make a point or to make your code more clear. In other
situations, a cast may be essential just to get the code to compile.
In C and C++, casting can cause some headaches. In Java, casting is safe,
with the exception that when you perform a so-called narrowing
conversion (that is, when you go from a data type that can hold more
information to one that doesn't hold as much) you run the risk of losing
information. Here the compiler forces you to do a cast, in effect saying
"this can be a dangerous thing to do--if you want me to do it anyway you
must make the cast explicit." With a widening conversion an explicit cast
is not needed because the new type will more than hold the information
from the old type so that no information is ever lost.
Java allows you to cast any primitive type to any other primitive type,
except for boolean, which doesn't allow any casting at all. Class types do
not allow casting. To convert one to the other there must be special
methods. (String is a special case, and you'll find out later in this book
that objects can be cast within a family of types; an Oak can be cast to a
Tree and vice-versa, but not to a foreign type such as a Rock.)
Literals
Ordinarily when you insert a literal value into a program the compiler
knows exactly what type to make it. Sometimes, however, the type is
ambiguous. When this happens you must guide the compiler by adding
some extra information in the form of characters associated with the
literal value. The following code shows these characters:
//: c03:Literals.java
class Literals {
char c = 0xffff; // max char hex value
byte b = 0x7f; // max byte hex value
short s = 0x7fff; // max short hex value
int i1 = 0x2f; // Hexadecimal (lowercase)
int i2 = 0X2F; // Hexadecimal (uppercase)
int i3 = 0177; // Octal (leading zero)
// Hex and Oct also work with long.
long n1 = 200L; // long suffix
long n2 = 200l; // long suffix
Chapter 3: Controlling Program Flow
155
img
long n3 = 200;
//! long l6(200); // not allowed
float f1 = 1;
float f2 = 1F; // float suffix
float f3 = 1f; // float suffix
float f4 = 1e-45f; // 10 to the power
float f5 = 1e+9f; // float suffix
double d1 = 1d; // double suffix
double d2 = 1D; // double suffix
double d3 = 47e47d; // 10 to the power
} ///:~
Hexadecimal (base 16), which works with all the integral data types, is
denoted by a leading 0x or 0X followed by 0--9 and a--f either in upper
or lowercase. If you try to initialize a variable with a value bigger than it
can hold (regardless of the numerical form of the value), the compiler will
give you an error message. Notice in the above code the maximum
possible hexadecimal values for char, byte, and short. If you exceed
these, the compiler will automatically make the value an int and tell you
that you need a narrowing cast for the assignment. You'll know you've
stepped over the line.
Octal (base 8) is denoted by a leading zero in the number and digits from
0-7. There is no literal representation for binary numbers in C, C++ or
Java.
A trailing character after a literal value establishes its type. Upper or
lowercase L means long, upper or lowercase F means float and upper or
lowercase D means double.
Exponents use a notation that I've always found rather dismaying: 1.39 e-
47f. In science and engineering, `e' refers to the base of natural
logarithms, approximately 2.718. (A more precise double value is
available in Java as Math.E.) This is used in exponentiation expressions
such as 1.39 x e-47, which means 1.39 x 2.718-47. However, when FORTRAN
was invented they decided that e would naturally mean "ten to the
power," which is an odd decision because FORTRAN was designed for
science and engineering and one would think its designers would be
156
Thinking in Java
img
sensitive about introducing such an ambiguity.1 At any rate, this custom
was followed in C, C++ and now Java. So if you're used to thinking in
terms of e as the base of natural logarithms, you must do a mental
translation when you see an expression such as 1.39 e-47f in Java; it
means 1.39 x 10-47.
Note that you don't need to use the trailing character when the compiler
can figure out the appropriate type. With
long n3 = 200;
there's no ambiguity, so an L after the 200 would be superfluous.
However, with
float f4 = 1e-47f; // 10 to the power
the compiler normally takes exponential numbers as doubles, so without
the trailing f it will give you an error telling you that you must use a cast
to convert double to float.
Promotion
You'll discover that if you perform any mathematical or bitwise operations
on primitive data types that are smaller than an int (that is, char, byte,
or short), those values will be promoted to int before performing the
operations, and the resulting value will be of type int. So if you want to
assign back into the smaller type, you must use a cast. (And, since you're
assigning back into a smaller type, you might be losing information.) In
general, the largest data type in an expression is the one that determines
1 John Kirkham writes, "I started computing in 1962 using FORTRAN II on an IBM 1620.
At that time, and throughout the 1960s and into the 1970s, FORTRAN was an all
uppercase language. This probably started because many of the early input devices were
old teletype units that used 5 bit Baudot code, which had no lowercase capability. The `E'
in the exponential notation was also always upper case and was never confused with the
natural logarithm base `e', which is always lowercase. The `E' simply stood for exponential,
which was for the base of the number system used--usually 10. At the time octal was also
widely used by programmers. Although I never saw it used, if I had seen an octal number
in exponential notation I would have considered it to be base 8. The first time I remember
seeing an exponential using a lowercase `e' was in the late 1970s and I also found it
confusing. The problem arose as lowercase crept into FORTRAN, not at its beginning. We
actually had functions to use if you really wanted to use the natural logarithm base, but
they were all uppercase."
Chapter 3: Controlling Program Flow
157
img
the size of the result of that expression; if you multiply a float and a
double, the result will be double; if you add an int and a long, the
result will be long.
Java has no "sizeof"
In C and C++, the sizeof( ) operator satisfies a specific need: it tells you
the number of bytes allocated for data items. The most compelling need
for sizeof( ) in C and C++ is portability. Different data types might be
different sizes on different machines, so the programmer must find out
how big those types are when performing operations that are sensitive to
size. For example, one computer might store integers in 32 bits, whereas
another might store integers as 16 bits. Programs could store larger values
in integers on the first machine. As you might imagine, portability is a
huge headache for C and C++ programmers.
Java does not need a sizeof( ) operator for this purpose because all the
data types are the same size on all machines. You do not need to think
about portability on this level--it is designed into the language.
Precedence revisited
Upon hearing me complain about the complexity of remembering
operator precedence during one of my seminars, a student suggested a
mnemonic that is simultaneously a commentary: "Ulcer Addicts Really
Like C A lot."
Mnemonic
Operator type
Operators
Ulcer
Unary
+ - ++--
Addicts
Arithmetic (and shift)
* / % + - << >>
Really
Relational
> < >= <= == !=
Like
Logical (and bitwise)
&& || & | ^
C
Conditional (ternary)
A>B?X:Y
A Lot
Assignment
= (and compound
assignment like *=)
Of course, with the shift and bitwise operators distributed around the
table it is not a perfect mnemonic, but for non-bit operations it works.
158
Thinking in Java
img
A compendium of operators
The following example shows which primitive data types can be used with
particular operators. Basically, it is the same example repeated over and
over, but using different primitive data types. The file will compile
without error because the lines that would cause errors are commented
out with a //!.
//: c03:AllOps.java
// Tests all the operators on all the
// primitive data types to show which
// ones are accepted by the Java compiler.
class AllOps {
// To accept the results of a boolean test:
void f(boolean b) {}
void boolTest(boolean x, boolean y) {
// Arithmetic operators:
//! x = x * y;
//! x = x / y;
//! x = x % y;
//! x = x + y;
//! x = x - y;
//! x++;
//! x--;
//! x = +y;
//! x = -y;
// Relational and logical:
//! f(x > y);
//! f(x >= y);
//! f(x < y);
//! f(x <= y);
f(x == y);
f(x != y);
f(!y);
x = x && y;
x = x || y;
// Bitwise operators:
//! x = ~y;
x = x & y;
x = x | y;
Chapter 3: Controlling Program Flow
159
img
x = x ^ y;
//! x = x << 1;
//! x = x >> 1;
//! x = x >>> 1;
// Compound assignment:
//! x += y;
//! x -= y;
//! x *= y;
//! x /= y;
//! x %= y;
//! x <<= 1;
//! x >>= 1;
//! x >>>= 1;
x &= y;
x ^= y;
x |= y;
// Casting:
//! char c = (char)x;
//! byte B = (byte)x;
//! short s = (short)x;
//! int i = (int)x;
//! long l = (long)x;
//! float f = (float)x;
//! double d = (double)x;
}
void charTest(char x, char y) {
// Arithmetic operators:
x = (char)(x * y);
x = (char)(x / y);
x = (char)(x % y);
x = (char)(x + y);
x = (char)(x - y);
x++;
x--;
x = (char)+y;
x = (char)-y;
// Relational and logical:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
160
Thinking in Java
img
f(x == y);
f(x != y);
//! f(!x);
//! f(x && y);
//! f(x || y);
// Bitwise operators:
x= (char)~y;
x = (char)(x & y);
x  = (char)(x | y);
x = (char)(x ^ y);
x = (char)(x << 1);
x = (char)(x >> 1);
x = (char)(x >>> 1);
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// Casting:
//! boolean b = (boolean)x;
byte B = (byte)x;
short s = (short)x;
int i = (int)x;
long l = (long)x;
float f = (float)x;
double d = (double)x;
}
void byteTest(byte x, byte y) {
// Arithmetic operators:
x = (byte)(x* y);
x = (byte)(x / y);
x = (byte)(x % y);
x = (byte)(x + y);
x = (byte)(x - y);
Chapter 3: Controlling Program Flow
161
img
x++;
x--;
x = (byte)+ y;
x = (byte)- y;
// Relational and logical:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//! f(!x);
//! f(x && y);
//! f(x || y);
// Bitwise operators:
x = (byte)~y;
x = (byte)(x & y);
x = (byte)(x | y);
x = (byte)(x ^ y);
x = (byte)(x << 1);
x = (byte)(x >> 1);
x = (byte)(x >>> 1);
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
x ^= y;
x |= y;
// Casting:
//! boolean b = (boolean)x;
char c = (char)x;
short s = (short)x;
int i = (int)x;
long l = (long)x;
float f = (float)x;
162
Thinking in Java
img
double d = (double)x;
}
void shortTest(short x, short y) {
// Arithmetic operators:
x = (short)(x * y);
x = (short)(x / y);
x = (short)(x % y);
x = (short)(x + y);
x = (short)(x - y);
x++;
x--;
x = (short)+y;
x = (short)-y;
// Relational and logical:
f(x > y);
f(x >= y);
f(x < y);
f(x <= y);
f(x == y);
f(x != y);
//! f(!x);
//! f(x && y);
//! f(x || y);
// Bitwise operators:
x = (short)~y;
x = (short)(x & y);
x = (short)(x | y);
x = (short)(x ^ y);
x = (short)(x << 1);
x = (short)(x >> 1);
x = (short)(x >>> 1);
// Compound assignment:
x += y;
x -= y;
x *= y;
x /= y;
x %= y;
x <<= 1;
x >>= 1;
x >>>= 1;
x &= y;
Chapter 3: Controlling Program Flow
163
Table of Contents:
  1. Introduction to Objects:The progress of abstraction, An object has an interface
  2. Everything is an Object:You manipulate objects with references, Your first Java program
  3. Controlling Program Flow:Using Java operators, Execution control, true and false
  4. Initialization & Cleanup:Method overloading, Member initialization
  5. Hiding the Implementation:the library unit, Java access specifiers, Interface and implementation
  6. Reusing Classes:Composition syntax, Combining composition and inheritance
  7. Polymorphism:Upcasting revisited, The twist, Designing with inheritance
  8. Interfaces & Inner Classes:Extending an interface with inheritance, Inner class identifiers
  9. Holding Your Objects:Container disadvantage, List functionality, Map functionality
  10. Error Handling with Exceptions:Basic exceptions, Catching an exception
  11. The Java I/O System:The File class, Compression, Object serialization, Tokenizing input
  12. Run-time Type Identification:The need for RTTI, A class method extractor
  13. Creating Windows & Applets:Applet restrictions, Running applets from the command line
  14. Multiple Threads:Responsive user interfaces, Sharing limited resources, Runnable revisited
  15. Distributed Computing:Network programming, Servlets, CORBA, Enterprise JavaBeans
  16. A: Passing & Returning Objects:Aliasing, Making local copies, Cloning objects
  17. B: The Java Native Interface (JNI):Calling a native method, the JNIEnv argument
  18. Java Programming Guidelines:Design, Implementation
  19. Resources:Software, Books, My own list of books
  20. Index