All Categories :
Java
Chapter 1
The Java Development Environment
CONTENTS
Java is a programming language that provides a foundation for
developing Internet applications. Java does this through applets,
which are programs executed as part of a Web page and displayed
with a Java-enabled browser, such as HotJava or Netscape Navigator
2.0. However, Java's features need to be understood in a context
much broader than just running Internet applications. Java is
a language designed to solve a variety of problems that have plagued
the computing community for years; running well on the Internet
is almost a by-product of solving these problems.
What are these problems? And how does Java solve them? This chapter
will attempt to answer those questions. Through the course of
this chapter, you will be given the background needed for understanding
why Java, applets, and the Internet are such a natural match.
This background will not only help you better understand the techniques
used to program the Java applets developed throughout this book,
but the extra knowledge of the "big picture" may also
help you come up with novel ways to apply this new technology.
Java and the Internet are very much like the old Western frontier-pathfinders
who break new ground may find the gold that lies underneath.
A key word often used when discussing the "information superhighway"
is convergence. People talk about the convergence of telecommunications
and computing, television and computers, consumer appliances and
telecommunications, and so forth. Each of these converging factors
comes from areas of technology that, until recently, have not
had much in common. For example, the technology of fiber optics
does not have much to do directly with the problems of computing.
At the same time that these disparate technologies have
converged, there have also been pressures from within computing
to bring together disciplines historically considered unrelated.
This convergence within computing has been brought about by various
pressures that have arisen:
- An overwhelming need to deal with the
software crisis has developed. This term has been
coined to describe problems that have plagued the software community.
Software development has been marred by high cost, low quality,
and ever-increasing demand; these problems have been both caused
and exacerbated by increasing software complexity. Because of
these trends, there is tremendous interest in any technique that
can increase the productivity of software developers, improve
reliability, and make it easier to design software that tackles
complex problems. Over the last decade the technology that has
been viewed as having the best chance of successfully dealing
with these problems is object-oriented programming. Object-oriented
features of abstraction, encapsulation, and modularity are particularly
well suited for making complex real-world problems easier to model
and, hence, design. A true object-oriented language's support
for inheritance and dynamic binding improves software reuse and
also, as a consequence, programmer productivity. The garbage collection
facility in some object-oriented languages improves reliability
by freeing the programmer from the intricate concerns of memory
management.
- The predominance of a variety of operating
systems and platforms has created a heavy demand for portable
software. Programs need to be written that will not only run on
several different platforms, but do so in a way that fits into
the native "look and feel" of the environment. Furthermore,
portable software needs to have the high performance that
is typical of the software written for the target platform. Interest
in soft-ware that is portable but slow is waning rapidly.
- The rapid growth of computer networks
has been matched by a corresponding rise of interest in distributed
computing. This discipline is concerned with the problems
of software distributed across multiple computers. Distributed
computing issues include interprocess communication, concurrent
processing, data sharing and replication, and security. Although
the Internet is the most well-known distributed environment, it
is in just the early stages of using the full potential of distributed
computing. Until recently, for example, there has been no solution
to the problem of dynamically downloading an application both
efficiently and securely. Efficiency is important since
you want to download only the code you need when you need it;
you don't want to wait thirty minutes to download a 3MB application.
Even more important, you need to be concerned with the security
of the code. You won't run applications from the Internet if it
means giving the green light to hackers around the world to attack
your hard disk. You also do not want a poorly written Internet
application to crash your system-the software has to be reliable.
Each of these problems has been seriously addressed from many
fronts, but no approach has simultaneously addressed all these
problems. For example, there might be a programming language that's
good for rapid development but isn't portable, or a development
environment might be portable, but could not work in a distributed
environment without opening you up to serious breaches of security.
A single solution to all these problems has been lacking
until
Java!
The designers of Java created a programming environment to attack
each and every one of these problems. As a comprehensive solution
to these pressing issues, Java needs to be understood as not just
a programming language, but as a general-purpose environment.
Its unique combination of a programming language, compiler, and
runtime environment provides a general architecture well suited
for addressing many of the concerns that have been plaguing the
computing community for years. This general development environment
is often referred to with the solitary term Java.
In part, Java's evolution into a comprehensive solution stems
from its twisted history of being a language for general consumer
electronics to being one for PDAs and set-top boxes for interactive
television. Features common to each of these potential platforms
include severe memory constraints and the need to support multiple
operating systems. These problems forced the language to address
the problems of distributed computing, efficiency, security, portability,
and reliability. For Java to be a successful language, furthermore,
it would also have to address the problems associated with the
software crisis-it would need to be object-oriented.
Being object-oriented carried another bonus for Java. The object-oriented
message-passing model of invoking class methods makes it a natural
for network-based application development. Consequently, being
object-oriented is one of the cornerstones of Java, so it's a
good place to begin a tour of its general architectural features.
Why is it important for Java to be an object-oriented language?
As stated before, modern programming languages need to address
the problems of the software crisis. Perhaps the most serious
difficulty a software engineer encounters is the inherent complexity
involved in modeling a real-world problem. Through the past decades
of computing, analysis techniques have come in and out of fashion
that aim to make modeling the world a more comprehensible and
stable practice. When you were taking your first classes in programming,
you might have seen flowcharts used as a modeling tool. The Structured
Analysis school of thought uses data flow diagrams to model the
world. These approaches have some contributions to make, but they
have one serious problem: they are heavily oriented toward the
procedural side of the activities being modeled. The problem
with a procedural approach is that it does not translate well
to software that is compact, easy to maintain, and reusable. In
its worst implementations, a system based on the procedural model
has a different function for every different type of procedure
that could occur. You have probably seen such systems-they are
so incomprehensible and hard to follow that the only goal of the
poor soul who has to maintain the system is to "just make
it work."
Object-oriented analysis, design, and programming is a radical
departure from the procedural model. Its focus is not on the procedures
of the modeled world, but on the objects appearing in it.
Objects are a natural thing to model because that's what the world
is made of. Planes, dogs, and computers are all objects. Even
abstract concepts, such as a priority queue, can be thought of
as an object. What all objects have in common is that they have
state and behavior. For example, the state of a
plane can be partially indicated by its speed and location, but
its behavior might be represented by its ability to change altitude.
Another feature of objects is that they are self-contained; in
other words, they are modular. When modeling a car engine,
for example, you can focus on the characteristics of each object
in the engine as opposed to the flow of gas and energy through
it. The object model breaks the engine down into a series of components
that can be described on their own terms, as opposed to what they
are in a complex global view of things. Instead of writing a single
huge procedure describing what's going on in the car, you can
focus on how each of the individual elements behaves.
Finally, objects have the quality of being hierarchical. A large
complex object, a house, for example, consists of other objects,
such as its physical structure, the electrical system, the plumbing,
and so forth. These are in turn made up of yet other objects,
such as a chimney, the light switches, and a sink. These can be
broken down into yet smaller components such as wood, wires, and
pipes. By modeling a system based on hierarchy, you can
use the model of smaller, more easily understood objects, such
as a wire, to construct more complex objects, such as a light
switch. Since the switch is now seen as an object that consists
of yet simpler objects, its complexity is easier to understand
and manage.
In object-oriented systems, objects communicate through a message-passing
mechanism. When an object receives a message, it may change its
state and behavior as part of the response. For example, sending
a message of "play" to a CD player object might result
in the CD playing a music track. This message-passing aspect of
objects translates very nicely into the needs of network programming,
where transactions are carried out by the mechanism of messages.
It is particularly well suited to distributed systems because
objects communicate by a standard message-passing mechanism, in
which the actual location of the objects becomes less important.
If your object needs to talk to an object, it isn't particularly
important if it is on your computer or a remote host. All that
really counts is that the object gets your message.
Java is unusual for an object-oriented programming language in
that it is "objects all the way down." Unlike C++, which
is a confusing combination of objects and functions, everything
in Java is an object. Strings are objects, numbers are objects,
threads are objects, even applets are objects. Because of this,
Java has all the helpful features of object-oriented systems just
described. Its core constructs of classes, objects, methods, and
instance variables are, by their very nature, managed in a modular
fashion. Java's support for inheritance allows you to build new
classes from other classes. Each class you construct becomes a
tool that can be used to create yet more complex classes.
Java's particular object-oriented implementation allows it to
have yet even more desirable features than those already discussed.
It has a runtime garbage collector that removes objects
from memory that you no longer need. No longer will your program
run out of memory because you forgot to explicitly delete an object.
The garbage collector, which your program doesn't even have to
be aware of, does this for you. Furthermore, Java's total orientation
toward objects removes another construct that has been a blight
of programmers-the pointer. Java hides almost all aspects of memory
management from the programmer. C or C++ programmers who have
had to deal with complex bugs caused by the misuse of pointers
or bad pointer arithmetic will no longer have to deal with this
entire class of problems. In Java, you will never deal with pointers,
only objects. Because of this combination of garbage collection
and removal of the pointer construct, Java has generally taken
the problem of memory management away from you. Consequently,
programs written in Java are more reliable.
Java's unique object-oriented implementation gives it another
set of desirable characteristics-simplicity and familiarity.
In many ways, its syntax is similar to C and C++, thus making
it familiar. On the other hand, it strips out all the programming
constructs of this language that are historical artifacts contributing
little to writing object-oriented programs. For example, the structure
construct is removed because everything in Java is an object.
Unions are removed because they make you think too much about
how memory is laid out, a low-level memory consideration that
Java's garbage collection and removal of pointers tries to abolish.
Other procedural features of C and C++ that are removed include
functions, which are replaced by class methods; preprocessor constructs,
such as typedef and ifdef;
the hated goto statement;
and, of course, pointers. Java also replaces the complex and troublesome
multiple inheritance of C++ by a simpler combination of single
inheritance and interfaces. With all these features in mind, Java
becomes a simpler and easier language to use.
Java has all the object-oriented features discussed in the previous
section. It uses message passing to let objects communicate, and
it supports dynamic binding, allowing a message to be sent
to an object even though its specific type may not be known until
runtime. Abstract classes and interfaces enable you to specify
a design without having to worry about a specific implementation.
With Java's access control constructs, you can define different
levels of access to your class's methods and variables that are
available to external classes.
Portability is critical to success in the emerging world
of networked applications and commerce. On the Internet, you cannot
make assumptions about what kind of platform your applet will
run on. Not only do you have to write software that will run on
the client's underlying architecture-such as 80x86, PowerPC, or
68000 series-but you must also ensure it will have the correct
look and feel of the native interface. To make things even tougher,
this software needs to be fast and compact.
Java takes a multipronged approach to this challenge. At the heart
of this approach is the fact that the Java compiler generates
bytecodes that are interpreted at runtime. The fact
that bytecodes are generated is important because it avoids the
problem of basing the binary code on a basic set of primitive
types-such as integers and floating point-that would be tied to
a specific platform. For example, even something as simple as
an integer data type is implemented in a different manner on different
platforms (16-bit on some, 32-bit on others). Java defines its
own set of primitive data types and reconstructs large 16-bit
or 32-bit values at runtime by composing them out of individual
bytecodes. Java consistently uses the big-endian format to do
this, thus avoiding another portability conflict about how platforms
store primitive data types. You can also quickly and efficiently
convert the bytecodes at runtime to the underlying native formats
if necessary. In short, Java's underlying encoding scheme is architecture-neutral.
The bytecode-based system is important to writing a portable interpreter.
The bytecodes generated by the compiler are based on the specification
of a Java Virtual Machine, which, as its name suggests,
is not a specific hardware platform but a machine implemented
in software. The virtual machine is very similar to a real CPU
with its own instruction set, storage formats, and registers.
Since it is written in software, however, it is portable. All
that's needed to take Java code compiled on one platform and run
it on another is to provide a Java interpreter and runtime environment.
The runtime system is written in an easily portable fashion. Once
you have this system, everything becomes easy. You don't even
have to port the Java compiler-it is written in Java!
Another advantage of the Java interpreter is that it improves
software development by eliminating the link phase of the development
process. You can go straight from compiling your code to executing
it. Linking actually occurs at runtime through mechanisms discussed
in the section "Java as a Secure Environment."
A couple of higher-level features also improve Java's portability.
The Abstract Window Toolkit (or AWT) is constructed to be a visual
interface that is portable across platforms. This way your applets
will look good regardless of the underlying interface. They will
have the proper "look and feel" on Microsoft Windows,
the Macintosh, or X-Windows. Although not a portability issue
in the strict sense, Java stores characters by using the 16-bit
Unicode format as opposed to the 8-bit ASCII standard. Unicode
is used if you need to support international character sets. Since
the Internet is an international network, this consideration is
important.
One of the reasons why Java's portable solution is such a coup
is that interpreted platforms have generally been very slow. Often
their performance is so poor that systems based on these interpreters
have been unusable. Java's bytecode system, however, provides
a "lean and mean" interpreted solution. It isn't based
on multimegabyte executable "image" files. Rather, the
runtime system works with small binary class files compiled
to Java virtual machine code. These files typically have a size
of only a few kilobytes.
However, Java offers more than just the byte-code system to get
high performance. Since the byte-code system is at a low level,
it can be easily converted at runtime to the native platform.
Just-in-time Java compilers will be out in the near future to
allow this. Another performance enhancement is a by-product of
another Java feature. When a class is brought into memory at runtime,
it runs through a verification process as part of the Java security
mechanism (discussed in the section "Java as a Secure Environment").
This process not only guarantees that the code you are loading
is secure, but the end result is code that requires less runtime
checks. These checks, which otherwise would hurt performance,
now don't need to be executed. For example, the runtime system
does not have to check for stack overflows on verified code.
One of the key features that Java offers to improve performance
is multithreading. Most interactive applications are characterized
by large periods of time during which the user pauses between
actions to decide what to do next. In traditional single-threaded
environments, the application may sit idle during such periods.
In multithreaded environments, however, the application can perform
other tasks during these types of delays or at any other time.
This is critical for network applications, which can take long
periods of time to load files. Wouldn't it be more efficient if
you could read the current page of text while the next page is
being downloaded? Multithreading also greatly improves the usability
of multimedia applications. For example, you need to be able to
interact with your system while a sound or a movie is playing.
Multithreading is useful for even more down-to-earth things, such
as running a background thread that spell checks your document
as you are typing in words.
You can use Java's easy-to-use multithreading environment to perform
all kinds of optimizations to your program. In fact, the designers
of Java have taken great pains to make sure it's easy to write
multithreaded programs. Historically, writing multithreaded applications
has been quite difficult-a key reason they have appeared infrequently.
Furthermore, Java uses threads to improve its own performance.
The garbage collector that takes care of your memory management
concerns runs in the background as a low-priority thread. So even
when your program is doing nothing, the garbage collector could
be busy optimizing memory!
There are some other interesting things Java does to guarantee
good performance. As stated earlier, Java is "objects all
the way down." In some object-oriented systems, primitives
such as integers and floating point numbers are implemented only
as objects. So to perform an operation such as adding two numbers,
you actually have to call an object method. If you are doing some
serious number crunching, this will absolutely kill your performance.
Java has an intermediate solution to resolve the dilemma between
having good performance and being a pure object-oriented system.
It implements "type wrappers" to take primitives, such
as numbers, and make them appear as objects. This way, methods-such
as converting numbers to strings-can be performed by using the
object-oriented method style. However, if you need to work with
raw data types, you can use the primitive formats to produce such
things as CPU-intensive images like fractals. Java can even be
used to create the computation-hungry Mandelbrot set. It's rather
remarkable that Java can do this; if you don't believe it can,
then see Chapter 14, "Advanced Image
Processing."
If you really need that final push to your performance, Java can
link to executable code written in a low-level native language
such as C. Java code that does this is said to use native methods.
This technique can also be used to implement platform-specific
features. However, using native methods is not without its drawbacks;
it will probably compromise the portability of your code. Fortunately,
Java performance is generally good enough that you don't need
to bring in native methods very often.
Finally, it is important to remember that the Java community is
constantly working on techniques to improve performance but not
compromise all the other features of Java, such as portability.
The just-in-time compilers that will be out later this year are
among the most impressive of such optimizations.
Much of what has been discussed so far makes Java well suited
for network computing. In particular, the lightweight nature of
the binary class files makes it possible to download Java code
without serious performance hits. Multithreading is also critical
for latency-laden network operations; your applet can download
code and resources in the background while the end user is deciding
the next action to take. Portability frees you from knowing how
your applet will run and what it will look like on your potential
client's unknown platform.
Another feature of Java that makes it excellent for distributed
computing is that it's dynamic. Since there is no linking
phase after compilation, the resolution of references is put off
until runtime. Java also does not calculate the layout of objects
in memory until they are used at runtime. Consequently, if you
add a new method to one class, you don't have to recompile another
class that uses the class but not the method. In C++, you are
constantly having to recompile multiple classes because one is
"dependent" on the other-this is known as the "fragile
superclass problem." In its typical efficient manner, Java
solves the superclass problem while taking care of other issues.
Since the Java dynamic system removes difficulties caused by unnecessary
dependencies, it's easy to download a special subclass to handle
a specific situation without worrying about "linking"
problems.
The Java development package also gives you a variety of good
communication constructs. The message-passing mechanism of its
objects can be used to let two different applets communicate;
this is known as "inter-applet communication." Sockets,
pipes, and thread synchronization constructs also provide other
easy-to-use communication mechanisms. In short, Java provides
all the basics for creating distributed systems.
Although distributed systems are extremely powerful, they open
the door to a range of security problems, which are particularly
serious on a vast open network like the Internet. For normal data
transmission, you have to be concerned with someone eavesdropping
on your information as it passes through the network. If you have
a server on the Internet, you have to worry about someone breaking
in and wreaking havoc on your internal network. And if you're
downloading applications from the net, you have to worry about
it causing damage to your host system.
The kind of damage a poorly written application could inflict
on your host spans a range of extremes. On one hand, a poorly
written application can misuse the resources of your system, taking
resources you might need elsewhere. For example, a C program that
mismanages memory can simply take memory that another one of your
applications needs. If the program is really bad, it can cause
your system to lock up. Fortunately, it's usually easy to recover
from such problems. However, the other end of the extreme is not
quite so innocent. A malicious program can attack some of the
most critical resources of your computer, such as your hard disk,
causing damage less easily fixed. If the program is a virus, it
can be even more pernicious. It can lie in wait for weeks or even
months, then one day pounce on your computer and connecting network,
causing hours or even days of lost work time. If this attack came
from an application that you ran from the World Wide Web, you
would probably be reluctant to use the Web again. Consequently,
bad Web programs not only threaten your system, but the viability
of the Web as a whole.
The designers of Java know just how important security is. For
this reason, they built a multi-layered security system whose
presence is felt throughout Java. This security model consists
of four main layers:
- The language itself is designed
to be safe. A strict compiler prevents generating bytecodes
that don't completely follow extensive safety rules.
- A runtime bytecode verifier
that inspects class bytecodes as they are loaded into the system.
Since code loaded at runtime has already passed through the compilation
stage, Java cannot know that it hasn't been produced by a malicious
or weak compiler that does not follow all the language's safety
rules.
- Once code has been verified, it needs
to be loaded into a runtime namespace. This is performed by a
module called the Class Loader, represented
by a like-named class. Java has a different namespace depending
on whether the loaded class came from a local file system or across
a network. These separate namespaces prevent a class from passing
itself off as a new low-level class. Therefore, it can't do such
things as replacing the existing Security Manager (discussed briefly
in the next bulleted item).
- The final layer performs checks on the
code as it's being executed. This layer makes sure the code does
not violate any security restrictions defined for the current
environment; it's generally represented by a module called the
Security Manager, which corresponds to a Java class
of a similar name. An applet that can delete a file on your computer
is an example of something that falls under the auspices of the
Security Manager. The workings of Security Managers can vary on
different environments. Extremely conservative Java-enabled browsers,
such as Netscape Navigator, actually prevent applets from reading
or writing to your local file system altogether. The HotJava browser,
on the other hand, can be configured to authorize file operations
more flexibly.
Figure 1.1 gives an overview of the lifetime of Java code as it
travels through the layers of security. Those modules related
to security are set off in boldface.
Since security is such a critical part of Java, it is worth taking
a moment to look at it in greater detail. In doing so, you will
get some greater insight into the inner workings of Java.
Figure 1.1 : The Java life-cycle in relationship to its security layers.
First Security Layer: The Language and Compiler
You have already seen a couple of reasons why Java is a secure
language. A key ingredient here is removing pointers from Java.
Without pointers, a bad or malicious program cannot invade the
memory space of other applications. This removes a major source
of attack for viruses. For example, a threatening program cannot
use Java as a launching pad to directly attack your operating
system kernel. Furthermore, the absence of pointers makes the
Java applet itself more secure and reliable. An applet won't crash
because of a "dangling pointer," as is often the case
in C and C++.
Another security feature of the language is its class access mechanism.
Classes can control the kinds of access other classes have to
their methods and variables. There are four access modifiers,
ranging from public, which
indicates availability to all classes, to private,
which makes a method or variable accessible only from within the
class where it's defined. These modifiers can make your class
more secure by denying other classes access to critical behavior.
For example, if you have a class that manages critical data in
a private method, another class cannot invoke that method to change
the data. Access modifiers will be discussed in more detail in
the next section.
The compiler uses very strict checking to ensure adherence to
these and other Java language constructs. Java is a strongly typed
language, so runtime bugs aren't introduced because of freely
casting one type of object to another. A language like C is notorious
for its loose casting mechanism. A pointer to a structure can
be fairly easily cast into a long integer, and vice versa. When
such casting is done incorrectly, pernicious and difficult-to-find
bugs can be introduced, but this kind of casting is eliminated
in Java. All casts must be explicit, and those that don't follow
the semantics of the language are disallowed. Because the compiler
is so strict, many errors that would otherwise appear at runtime
are caught at compilation. Since runtime bugs are potentially
dangerous and can be time-consuming and difficult to track down,
it's good to catch as many mistakes as possible during compilation.
Java's strict compiler is one reason the environment is said to
be robust.
Second Security Layer: The ByteCode Verifier
The bytecode verifier is the most critical line of defense in
the Java security system. If a rogue class can get through this
layer, you could be in real trouble! However, this is unlikely.
The verifier uses various techniques, including a theorem prover,
to ensure that the bytecodes of the class being loaded do not
violate any of the structural constraints Java places on incoming
code. The verifier is, therefore, positioned to catch any potential
malicious actions caused by bytecodes produced by a hostile compiler
or subject to post-compilation tampering.
Verification goes through a couple of steps. The first step is
to verify the format of the incoming file to make sure it is indeed
a Java class and has been properly constructed. The next step
is much more complex. The verifier basically sets out to prove
that the code has a variety of properties. If the bytecodes make
it through this phase of the verifier, then you'll know the following
things:
- The code does not cause stack overflows
or underflows.
- The operand types of the bytecode opcodes
are proper. For example, an opcode that works with an integer
operand cannot have an operand that is an object reference.
- No illegal casting is attempted.
- Object access rules are followed.
As a result of these properties being proved, the runtime system
will know a couple of other important things, the most important
being no forged pointers in the code.
A beneficial side effect of the verification process is that the
interpreter is free from performing many of these checks as the
code is being executed. For example, it does not need to conduct
expensive checks to see if a stack overflow is about to occur.
Because such checks are not necessary, the interpreter will run
much faster.
Another security-related step occurs when the interpreter loads
the verified bytecodes. When the interpreter brings in a class,
it defines the memory layout of the class. Recall that
this is a feature of Java's dynamic linking used to solve the
"fragile superclass" problem. Dynamic linking also has
a security advantage. In traditional languages, memory is laid
out at compile time. A malicious programmer, knowing the layout
of memory in the executable code, could then tamper with the pointers
to get around security problems. Since Java performs memory layout
at runtime, however, this potential security bypass is thwarted.
Third Security Layer: The Class Loader
After a class is verified, it's ready for runtime use. The Class
Loader brings each class into a unique namespace that corresponds
to its origin. The default namespace is for classes that come
from the local file system. Such classes are called built-ins;
they can never be replaced by a class that comes from an external
source because there is a separate namespace for each network
source of classes. When a class is referenced, Java looks first
for a built-in class. If it isn't found, then Java inspects the
namespace of the referencing class. This approach prevents network
classes from replacing a built-in, or a class from one network
source overriding one from another source. The security implications
of this approach are subtle but important. Java tries to implement
as much as possible through Java classes; for example, the Security
Manager module is represented by a SecurityManager class, you
get access to system resources through the System class, and,
as will be seen shortly, class loader policies are implemented
by ClassLoader classes. By preventing built-ins from being overridden,
Java protects critical modules, such as the SecurityManager or
System. It's easy to imagine what could happen if a network class
was allowed to replace the SecurityManager class.
Subclasses of the ClassLoader class are used to enforce namespace
policies. The Class Loader system can consist of multiple instances
of ClassLoader subclasses. For example, one Class Loader can be
used for classes brought from inside a firewall, but another Class
Loader class can be used for those brought in from the Internet.
The local file system, by default, does not use a ClassLoader
class. Instead, it searches for classes in directories listed
in the CLASSPATH environment
variable; you can modify this path to include the directories
that have your classes. Keep in mind that there's a subtle difference
between the Class Loader mechanism, which applies to the entire
Java runtime environment, and instances of the ClassLoader class,
which implement specific policies.
Fourth Security Layer: The Security Manager
Even after a chunk of bytecode has gotten past the verifier and
the class loader, it is still technically in a position to cause
some damage. Suppose that a class downloaded from the Internet
has some code to delete files from your hard disk. This can be
done legitimately by calling the delete()
method of the File class and so will pass the verifier and class
loader. Fortunately, the final security layer, represented by
the SecurityManager class (also known as the Security Manager),
will prevent this from occurring. The Security Manager is responsible
for enforcing a set of policies for protecting the runtime environment
from unauthorized transactions. Whenever a potentially "dangerous"
action is about to happen, the Security Manager is asked for authorization
to perform the action. Based on how the manager is implemented,
the action may be denied or granted.
Different browsers and applet viewers can use the Security Manager
in an appropriate way. Once installed into the runtime system,
the Security Manager cannot be replaced. These browsers may grant
levels of authorization for different actions. For example, the
Netscape Navigator has a very conservative Security Manager. The
most dangerous class of actions, those of reading and writing
from the hard disk, are prohibited altogether. On the other hand,
the HotJava browser has a more flexible configuration. It can
be set up to grant full disk access from applets loaded locally,
some access to applets loaded from within the firewall, and no
access for those brought in over the Internet.
A wide variety of actions are subject to authorization by the
Security Manager. When a class is asked to perform a potentially
dangerous action, such as a file delete, it will ask the SecurityManager
class for authorization. If it isn't permitted, a security exception
will occur. Besides all file-related activities, the actions of
the most importance to security are network accesses. Once more,
restrictions are usually based on how the SecurityManager is set
up, but there are a few generalities. An applet loaded over the
Internet can connect only to the host from which it originated;
it will not be allowed to connect to anywhere from inside the
client's firewall, nor will it be permitted to use the client
to act as a launching pad into some other Internet site. An applet
is also prevented from running as a network server (this has some
implications that are explored later). Restrictions enforced by
the SecurityManager will be discussed throughout the book as the
appropriate topics dictate.
You will now be guided through a very quick tour of the basics
of the Java language. If you have experience with C or C++, then
much will be familiar-you might want to skip over parts of this
section. If you don't know these languages, don't worry. The basic
mechanics of the language are easy, and you will see many examples
throughout the book. Discussion of classes, methods, and objects
will be postponed until the next chapter.
As stated earlier, everything in Java is an object. The partial
exception to this is the primitive data types. These data types
have a standard size across all platforms; this standardization
is a key aspect of Java's portability. Table 1.1 lists the primitive
data types.
Table 1.1. Primitive Java data types.
| Data Type | Size
|
| byte |
8-bit |
| short |
16-bit |
| int |
32-bit |
| long |
64-bit |
| float |
32-bit floating point |
| double |
64-bit floating point |
| char |
16-bit Unicode |
If you are a C or C++ programmer, you might have noticed a couple
of things. First of all, there is no unsigned
type specifier. The char
data type has been replaced by the byte
primitive. The char type
is now 16 bits, instead of the old 8 bits, because Java bases
character data on the Unicode character set. Unicode is a standard
that supports international characters, thus broadening the potential
base in which your application can run. Although Unicode is a
much broader standard than ASCII, you will probably have many
opportunities to program in the 8-bit standard. Some default class
behavior and localization methods will be available for doing
this. This book will focus on ASCII output.
The only primitive data type not in Table 1.1 is boolean.
A boolean variable cannot
be converted to a number and has only two values: true
or false.
You might have noticed that these primitive data types present
a partial exception to Java's pure object-oriented nature.
It is "partial" because Java has a suite of classes
used to encapsulate these data types as objects. These classes
are called type wrappers and are discussed in Chapter 2,
"Object-Oriented Development in Java."
Literals are used to represent the primitive types. Integers
are defined in a manner similar to C. They can be literally set
to a decimal value, such as 10. A hexadecimal value is indicated
with a leading 0x; 15 is represented by 0xF. Octal values (base
8) are prefaced by 0.
Floating point numbers are represented by the standard decimal
point notation, such as 3.1415. These can be stored as a 32-bit
float or a 64-bit double;
the latter is the default. The notation style of 6.1D or 6.1F
can also be used to designate the number as a double
or float, respectively.
Characters can be represented by a single character in quotes
such as 'a'. Escape sequences
are used to represent special characters and are preceded by a
backslash (\). For example,
tab is \t, newline is \n,
and so forth. See your Java references for a listing of all the
escape characters.
The last literal is not based on a primitive data type. Strings
are represented by zero or more characters in double quotes. An
example is "This is a Java book!".
The literal string can also use escape characters. For example,
to add a new line to the previous example you would write: "This
is a Java book!\n".
String literals are implemented as objects of the String class.
Operations on strings do not occur on character arrays (as in
C), but through class methods; these operations are discussed
in more detail in the next chapter.
Java has three types of variables: instance, class,
and local. The first two types are talked about in the
next chapter in the context of the discussion on classes. Local
variables can be declared inside methods or blocks. Blocks
are statements appearing in braces { }. Any local variable declared
inside a left brace is valid until the right brace, at which point
the variable goes out of scope.
Individual variables are declared in the general format:
<type> <variable name>
For example, to declare a double
called pi:
double pi;
You can also asign a value to it:
double pi = 3.1415;
Variable names are prefaced by letters, an underscore, or a dollar
sign. They can use most characters, including numbers. However
some symbols, such as those used in operators, should not be used.
For example, you should not call a variable pi+3.
Java has three comment styles. Two are similar to those used in
C and C++. A double slash (//)
means that everything to the end of the line should be ignored:
// Ignore this
Everything between the characters /*
and */ is also ignored. This
can be spread over several lines. If the first part of the comment
starts with /**, then a special
documenting feature is indicated. How this works is discussed
in Chapter 14.
Table 1.2 lists a quick summary of operators in Java, which are
fairly simple to use. The following code declares two integers,
assigns values to them, and adds them to a third variable:
int x,y;
x = 3;
y = 4;
int z = x + y;
The final value is 7.
The following code applies the bitwise AND
operator to the end of the previous example to get the value 4.
int q = z & 4;
You'll see examples of other categories of operators in the upcoming
sections.
Table 1.2. Classification of operators.
| Classification | Operators
|
| Arithmetic | + - * / % |
| Relational Operators | < > >= <= == != && || !
|
| Bitwise Operators | & | ^ << >> >>> ~ &= |= ^=
|
| Assignments | = += -= /= %=
|
| Bitwise Assignments | &= |= <<= >>= >>>= ^=
|
| Ternary Operator (if...else shorthand)
| ?: |
| Increment | ++ |
| Decrement | -- |
Expressions that have multiple operators are resolved according
to where they are in a hierarchy of precedences, shown in Table
1.3. Operators higher up in the precedence table are evaluated
first. If multiple operators on the same line have the same precedence,
then they are resolved by left-right order. If you are confused
or cannot remember precedence, then a nice rule is "When
in doubt, use parentheses."
Table 1.3. Operator precedence.
| . [] () |
| ++ - ! ~ |
| * / % |
| + - |
| << >> >>> |
| < > <= >= |
| == != |
&
^ |
| | |
| && |
| || |
| ?: |
| = and other assignments |
| Bitwise Assignments |
Table 1.4 lists Java keywords; they are reserved for Java statements,
so you can't use them for things like variable names. They are
identifiers for such things as data types, conditionals, control
flow constructs, class definitions, and object implementations.
Most of these keywords will be described in this and the next
chapter. Those that are reserved but currently not implemented
are italicized.
Table 1.4. Java keywords.
| Abstract | boolean |
| break | byte |
| byvalue | case |
| catch | char |
| class | const |
| continue | default |
| do | double |
| else | extends |
| false | final |
| finally | float |
| for | goto |
| if | implements |
| import | instanceof |
| int | interface |
| long | native |
| new | null |
| package | private |
| protected | public |
| return | short |
| static | super |
| switch | synchronized |
| this | threadsafe |
| throw | transient |
| true | try |
| void | while |
An if...else conditional
is used to execute code based on whether a boolean
test is true. Single statements
can follow, or multiple statements can be declared in braces:
if (test == true)
// ... A single statement to execute if test if true
else {
// ... multiple statements to execute if test if false
}
The test must return a boolean
value. This means an expression such as
if (1)
is not valid because it returns a numeric,
but not a boolean. It's acceptable
to nest if...else statements
or call them in a nested series, such as if...else...if...else...if...else.
The latter can also be simulated with the switch
conditional. Not being restricted to just boolean comparisons,
the switch conditional uses
a comparison with a general primitive to conduct its test. The
following code uses an integer to perform its test:
switch ( Count ) {
case 1:
//
... test 1
break;
case 4: {
//
... do some operation...
return
0;
}
case 24: break;
default:
return
-1;
}
The case statements can appear
in braces.
Java has three looping operations. The for
construct loop is structured as follows:
for (init; test; post-test)
The first part of the expression is any initialization at the
start of the loop. The test is a simple or complex expression.
The last part is an expression such as an increment or decrement
of a variable. The for loop
is followed by a single statement or a block of code. For example,
for (k = 0; k < 10; ++k) {
// ... do something
}
loops ten times.
A while loop is just the
test portion of the for loop:
while (test)
The do...while construct
performs the test and the end of the loop:
do {
// ... do something
} while (test);
The break construct is used
to exit from a loop, and the continue
statement skips to the next iteration of the loop. Labeled loops
can also be used to control where to go in complex loops. If a
label follows a break statement,
then the code breaks out of the nearest loop that has the matching
label. The following example effectively quits both loops when
the variable j is greater
than 100:
int i,j;
quit: for (i = j = 0; i < 100; ++i) {
for
(int k = 0; k < 10; ++k) {
//
... Do something
if
(j > 100)
break
quit;
}
}
If the break statement in
this example was replaced by the following,
if (j > 100)
continue quit;
then the code would jump to the next iteration of the outer for
loop.
Arrays are first-class objects in Java; they are not just a pointer
to memory as in C. Consequently, Java arrays are a lot safer.
You cannot indiscriminately assign an element to just any index
in an array. Java makes sure the index is valid-this prevents
the difficult-to-track memory access violations that occur so
easily in C. If you try to access a bad array index in Java, an
exception will be thrown and no action will be taken.
Because Java arrays are objects, their semantics are a little
different from their counterparts in C or C++. The thing that
confuses most new Java programmers is how to allocate a new array.
First, you cannot declare an array with a prefixed size; it must
be declared as an uninitialized variable:
int numbers[]; // For
integer arrays
String myStrings[]; // For arrays of String objects
Another way to declare array variables is to put the braces after
the type. Some programmers consider this method more readable.
String[] myStrings;
As you might have noticed from these examples, arrays can be used
for both primitive data types and classes.
The next step is to create the array by using the new
operator to construct an object instance; this will be discussed
in more detail in the next chapter.
int numbers[] = new int[5]; //
For integer arrays of length 5
String myStrings[] = new String[20]; // For arrays
of
length 20 of String objects
These examples create an integer
array of 5 elements and a String array of 20 elements. However,
the arrays still don't actually contain anything. Each slot is
initialized to a default value. Integers are all set to 0,
and String elements are all set to null
(indicating no object).
It's easy to add elements to the array after it's created, much
as you do in C++. It is a zero-based system with integer indexes.
Therefore, to assign the first element you would call the following:
myStrings[0] = "My First String"
numbers[0] = 10;
If you try to make an invalid assignment to an invalid index,
an ArrayIndexOutOfBoundsException will be thrown:
numbers[10] = 10;
Chapter 2 covers how exceptions are handled;
for now, you just need to know that such exceptions occur at runtime.
The preceding error will not be caught by the compiler.
A public instance variable
called length is used to get the size of an array:
int q = numbers.length; //
This is 5
Array sizes are final; any attempts to change the length variable
will lead to problems.
Java doesn't support multidimensional arrays. However, they can
be effectively simulated by using arrays of arrays, which can
be initialized in several different ways. The simplest way is
to define the array with preset sizes:
int k[][] = new int[5][4];
k[1][3] = 999; // Assign a value to it
This example creates a 5 ¥ 4 array
of arrays assigned to the variable k.
The second line shows that assigning an element to the array is
straightforward, much like C or C++.
Another way to create a multidimensional array is to declare and
then later assign its sizes:
int z[][];
int outerSize = 5;
int innerSize = 4;
z = new int[outerSize][innerSize];
Not all the dimensions need to be known at the time of allocation-only
one dimension is required:
int j[][] = new int[5][];
j[0] = new int[4];
j[1] = new int[4];
In this example, the outer array is set to size 5, then two of
its elements are set to arrays of size 4. It is interesting to
note that the statement
j.length
will return size 5, but
j[0].length
will return size 4.
Java can work with two kinds of applications: Applets,
as stated at the beginning of this chapter, are programs executed
as part of a Web page and displayed with a Java-enabled browser;
standalone applications, on the other hand, are general-purpose
Java applications that don't need a browser to run. Although applets
and standalone applications are compiled in a similar fashion,
they are created differently.
Listing 1.1 shows the code of a minimum applet, which is stored
in a file called FirstApplet.java on this book's CD-ROM. It is
compiled with the following command line call to the Java compiler:
javac FirstApplet.java
The output from the compiler consists of Java bytecodes in a file
called FirstApplet.class. This class code is ready to be directly
run from the interpreter-no further steps are necessary.
The code consists of one class called FirstApplet, a subclass
of the Applet class. The Applet class is used to tie the applet
into the browser environment; it will be described in more detail
in Chapter 6, "Building a Catalog
Applet."
Listing 1.1. An applet that compiles but does nothing.
import java.applet.Applet;
public class FirstApplet extends Applet {
}
To run the applet in the browser, you need to create an HTML file.
Listing 1.2 shows the HTML text needed to run the applet. Note
the second line of the listing: The <APPLET> tag is a special
HTML extension used to launch Java applets; notice especially
the CODE attribute. The value
assigned to it indicates the class that will be run. In this case,
it is the FirstApplet class that was just compiled. The <WIDTH>
and <HEIGHT> tags are used to specify the bounding area
of the applet, so you can control what the applet looks like on
your Web page. How Applet tags are used in HTML files is discussed
in more detail later in Chapter 6.
Listing 1.2. The HTML text to run the applet.
<TITLE>First Applet</TITLE>
<APPLET CODE="FirstApplet" WIDTH=300 HEIGHT=200>
</APPLET>
The program called appletviewer is used to run a Java applet from
outside a browser. It's limited in capability-it lacks all the
features that a browser has-but it's good for rapid development.
You can launch an applet from appletviewer by passing it the name
of the HTML file that starts the applet on the command line:
appletviewer FirstApplet.html
| Note |
Most of the projects and examples in the book can be run by loading the HTML in the example directory of the CD-ROM into the appletviewer program or a browser such as Netscape. When there are additional steps required, such as in the Part IV server programs, you should look at the README file in the working directory of the CD-ROM.
|
Listing 1.3 shows the code of a simple standalone application,
which is stored in a file called Standalone.java on this book's
CD-ROM. Like the preceding sample applet, and class files in general,
it's compiled with the program javac.
The code in a standalone application differs from an applet's
code in two ways: first, standalone applications do not need an
instance of the Applet class; second,they are started by a call
to the main() method, as
in C programs. The main()
method is the first routine executed in the program, which you
start by invoking the Java interpreter:
java Standalone
The .class extension should not be specified as part of the main
parameter. Additional parameters can be specified after this;
they are passed to the main()
method as an array of String objects, as the example shows.
The example just prints text to the standard output console by
using a call to the println()
method of the System class: System.out.println.
You will see this method invoked regularly throughout the book-it
provides the same role for Java programmers as printf()
does for C programmers. It will be used for both debugging and
conveying program information.
Listing 1.3. A simple standalone application.
import java.lang.System;
public class Standalone {
public static void main(String args[]) {
System.out.println("Running standalone
applet!");
}
}
Most classes and methods can be used in both applets (which are
tied to browsers) and standalone applications (which are not).
Using a browser gives your applet access to a range of services,
such as linking your applet to another Web page or using browser
resources like the status bar. On the other hand, much of what
you can do in an applet is restricted by a browser's security
constraints. You don't have as much freedom to perform file and
network operations as you do in a standalone application.
Sometimes the security restrictions of a browser force you to
write a standalone application, as illustrated in the chapter
on network servers in Part IV of this book. Such servers generally
need to be written as standalone applications. Of course, security
is both a blessing and a curse. It's a curse because it limits
what you can do, but of course, if someone used that lax security
to break into your system, you could end up with quite another
view.
Consequently, there may come a time when people begin to write
their own Security Man-agers into standalone programs to fit special
needs. For example, a corporate "intranet" is a possibility.
This book, as its title indicates, will focus on applets. However,
some features, such as network servers, are best created as standalone
applications, so these will be used when appropriate.
Java is more than just an Internet programming language. It is
a full-featured, object-oriented language in its own right. As
the language matures, Java may be considered the language of choice
for all object-oriented programming. In this way, Java
might compete with major object-oriented languages, such as C++
or Smalltalk. It is entirely possible that corporations and other
institutions will use Java for in-house applications, such as
timesheets and client/server projects. Java will probably prove
to be especially popular on intranets. So despite its billing
as primarily an Internet language, Java's wide variety of features
could establish it as the next major language in general, for
uses ranging from in-house projects to commercial uses to the
Internet and intranets to being the first language taught in universities.
For this reason, Java is worth knowing, regardless of how you
use the Internet.
Chapter 2 moves from this overview of Java's
foundations to a deeper exploration of its object-oriented features.
You will also be introduced to the Java Development Kit (JDK),
which offers a large suite of classes for creating your applets.
Techniques for writing more readable, efficient, and extendible
code will also be discussed.

Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.