Before getting into the details of the Java language, let’s jump right into some working code. In this chapter, we’ll build a friendly little application that illustrates a number of techniques we use throughout the book. We’ll take this opportunity to introduce general features of the Java language and of Java applications. However, many details won’t be fleshed out here, but in subsequent chapters.
This chapter also serves as a brief introduction to the object-oriented and multithreaded features of Java. If these concepts are new to you, you can take comfort in the knowledge that encountering them for the first time in Java should be a straightforward and pleasant experience. If you have worked with another object-oriented or multithreaded programming environment, clear your mind; you will especially appreciate Java’s simplicity and elegance.
We can’t stress enough the importance of experimentation as you learn new concepts. Don’t just examine the examples—run them. Copy the source code from the accompanying CD-ROM, or from our web site at http://www.oreilly.com/catalog/learnjava. Compile the programs on your machine, and run them.
If you follow along with the online examples, be sure to take some time and compile them locally. Then, turn our examples into your example: play with them; change their behavior, break them, fix them, and, as Java architect Arthur van Hoff would say: “Have fun!”
In the tradition of introductory programming texts, we begin with Java’s equivalent of the archetypal “Hello World” application. In the spirit of our new world, we’ll call it HelloJava .
We’ll take four passes at this example (HelloJava1, HelloJava2, etc.), adding features and introducing new concepts along the way. Here’s a minimalist version:[6]
//file: HelloJava1.java public class HelloJava1 extends javax.swing.JComponent { public static void main(String[] args) { javax.swing.JFrame f = new javax.swing.JFrame("HelloJava1"); f.setSize(300, 300); f.getContentPane().add(new HelloJava1( )); f.setVisible(true); } public void paintComponent(java.awt.Graphics g) { g.drawString("Hello, Java!", 125, 95); } }
Place this text in a file called HelloJava1.java. Now compile this source:
% javac HelloJava1.java
This produces the Java byte-code binary class file HelloJava1.class.
You can run the application by starting the Java runtime system, specifying the class name (not the filename) as an argument:
% java HelloJava1
(The name of the Java interpreter varies among implementations.
Microsoft’s is named jview
, not
java
.) You should see the proclamation shown in
Figure 2.1. Now congratulate yourself: you
have written your first application! Take a moment to bask in the
glow of your monitor.
When you click on the window’s close box, the window goes away, but your program will still be running. To stop the runtime system and return control to your command-line interpreter, type Ctrl-C or whatever key sequence stops a running application on your platform. We’ll remedy this shortcoming in a later version of the example.
HelloJava1 may be a small program, but there is actually quite a bit going on behind the scenes. Those few lines represent the tip of an iceberg. What lies under the surface are layers of functionality provided by the Java language and its foundation class libraries. In this chapter, we’ll cover a lot of ground quickly in an effort to show you the big picture. We’ll try to offer enough detail for a firm understanding of what is happening in each example, deferring full explanations until the appropriate chapters. This holds for both elements of the Java language and the object-oriented concepts that apply to them. Later chapters will provide more detailed cataloging of Java’s syntax, components, and object-oriented features.
The previous example defines a
class named HelloJava1
.
Classes are the fundamental building blocks of most object-oriented
languages. A class in Java is very much like a class in C++, and
somewhat like a struct
in C. It’s a group of data items, with
associated functions that perform operations on this data. The data
items in a class are
called
fields or
variables
;
the functions are called
methods
.
A class might represent something concrete, like a button on a screen
or the information in a spreadsheet, or it could be something more
abstract, such as a sorting algorithm or possibly the sense of ennui
in your MUD character. A hypothetical spreadsheet class might, for
example, have variables that represent the values of its individual
cells and methods that perform operations on those cells, such as
“clear a row” or “compute values.”
We’ll talk more about this in a little while.
Our HelloJava1
class contains an entire Java
application. It holds two general types of variables and methods:
those we need for our specific application’s tasks and some
special predesignated ones we provide to interact with the outside
world. The Java runtime system, in this case the
java
command-line tool, calls methods in
HelloJava1
to pass us information and prod us to
perform actions. Our simple HelloJava1
class
implements two important methods. The first, main( )
, is called when the application is first started.
We’ll talk more about it in the next section. The second
method, paintComponent( )
, is called by Java when
it’s time for our application to draw itself on the screen.
When you run our example, what really happens? The
java command looks in the
HelloJava1
class to see if it contains a special
method called main( )
. If it
does, this method is run. The main( )
method is
simply an entry point for an application. It’s a piece of code
that you want to be run when the application first starts.
The main( )
method sets up a
window (a JFrame
)
that will contain the visual output of the
HelloJava1
class. What really interests us here is
not the main( )
method but the rest of the class.
We’ll go through several incarnations of this class, adding
features and methods. But the main( )
method will
remain largely unchanged, keeping its basic function of creating a
window that holds the HelloJava example.
Let’s quickly walk through the main( )
method, just so you know what it does. First, main( )
creates a JFrame
, a window that will
hold our example:
javax.swing.JFrame f = new javax.swing.JFrame("HelloJava1");
The new
word in this line of code is tremendously
important:
javax.swing.JFrame
( just JFrame
for short) is the name of a class
that represents a window you can see on your screen. The class itself
is just a template, like a building plan. The new keyword tells Java
to allocate memory and initialize a new JFrame
object.
When frame windows are first created, they are very small. Our next task is to set the size to something reasonable:
f.setSize(300, 300);
Then we create our actual example and put it inside the frame window:
f.getContentPane().add(new HelloJava1( ));
Here, we’re actually creating a new
HelloJava1
object and placing it inside the
JFrame
we just created.
main( )
’s final task is to show the frame
window and its contents, which otherwise would be invisible. An
invisible window makes for a pretty boring application.
f.setVisible(true);
That’s the whole main( )
method. As we
progress through the examples in this chapter, it will remain mostly
unchanged as the HelloJava
class evolves around
it. Let’s get started!
A class is a blueprint for a part of an application; it lists methods and variables that go into making up that part. Many individual working copies of a given class can exist while an application is active. These individual incarnations are called instances of the class, or objects. Two instances of a given class may contain different data, but they always have the same methods.
As an example, consider a Button
class. There is
only one Button
class, but an application can
create many different Button
objects, each one an
instance of the same class. Furthermore, two
Button
instances might contain different data,
perhaps giving each a different appearance and performing a different
action. In this sense, a class can be considered a mold for making
the object it represents: something like a cookie cutter stamping out
working instances of itself in the memory of the computer. As
you’ll see later, there’s a bit more to it than
that—a class can in fact share information among its
instances—but this explanation suffices for now. Chapter 5, has the whole story on classes and objects.
The term object is very general and in some
other contexts is used almost interchangeably with
class. Objects are the abstract entities all
object-oriented languages refer to in one form or another. We will
use object as a generic term for an instance of a class. We might,
therefore, refer to an instance of the Button
class as a Button, a Button
object, or,
indiscriminately, as an object.
The main( )
method in the previous example creates
a single instance of the HelloJava1
class and
shows it in an instance of the JFrame
class. You
could modify main( )
to create many instances of
HelloJava1
, perhaps each in a separate window.
In Java, every class defines a new
type (data type). A variable can be of this type
and then hold instances of that class. A variable could, for example,
be of type Button
and hold an instance of the
Button
class, or of type
SpreadSheetCell
and hold a
SpreadSheetCell
object, just as it could be any of
the more familiar types such as int
or
float
.
Ignoring the main( )
method for the moment, there
is only one variable in our simple HelloJava example. It’s
found in the declaration of the paintComponent( )
method:
public void paintComponent(java.awt.Graphics g) {...}
Just like functions in C (and many
other languages), a method in Java declares a list of variables that
hold its arguments, and it specifies the types of those arguments.
Our paintComponent( )
method takes one argument
named (somewhat tersely) g
, which is of type
Graphics
. When the paintComponent( )
method is invoked, a Graphics
object
is assigned to g
, which we use in the body of the
method. We’ll say more about paintComponent( )
and the Graphics
class in a moment.
But first, a few words about variables. We have loosely referred to variables as holding objects. In reality, variables that have class types don’t so much contain objects as point to them. Class-type variables are references to objects. A reference is a pointer to or a name for an object.
If you declare a class-type variable without assigning it to an
object, it doesn’t point to anything. It’s assigned the
default value of null
, meaning “no
value.” If you try to use a variable with a null value as if it
were pointing to a real object, a runtime error
(NullPointerException
)
occurs.
Where do you get an instance of a class to assign to a variable in
the first place? The answer is through the use of the
new
operator.
We’ll examine object creation a little later in the chapter.
Java classes are
arranged in a parent-child hierarchy, in which the parent and child
are known as the superclass and
subclass, respectively. We’ll explore
these concepts fully in Chapter 6. In Java, every
class has exactly one superclass (a single parent), but possibly many
subclasses. The only exception to this rule is the
Object
class, which sits atop the entire class
hierarchy; it has no superclass.
The
declaration of our class in the previous example uses the keyword
extends
to specify that
HelloJava1
is a subclass of the
JComponent
class:
public class HelloJava1 extends javax.swing.JComponent {...}
A subclass may be allowed to inherit some or all of the variables and methods of its superclass. Through inheritance, the subclass can use those variables and methods as if it has declared them itself. A subclass can add variables and methods of its own, and it can also override the meaning of inherited variables and methods. When we use a subclass, overridden variables and methods are hidden (replaced) by the subclass’s own versions of them. In this way, inheritance provides a powerful mechanism whereby a subclass can refine or extend its superclass.
For example, the hypothetical spreadsheet class might be subclassed to produce a new scientific spreadsheet class with extra mathematical functions and special built-in constants. In this case, the source code for the scientific spreadsheet might declare methods for the added mathematical functions and variables for the special constants, but the new class automatically has all the variables and methods that constitute the normal functionality of a spreadsheet; they are inherited from the parent spreadsheet class. This means the scientific spreadsheet maintains its identity as a spreadsheet, and we can use it anywhere the simpler spreadsheet is used.
Our HelloJava1
class is a subclass of the
JComponent
class and inherits many variables and
methods not explicitly declared in our source code. These members
operate in the same way as the ones we add or override.
The JComponent
class provides the framework for
building user
interface components (called controls or widgets in other windowing
systems). Particular components, such as buttons, labels, and list
boxes, are implemented as subclasses of
JComponent
.
We override methods in such a subclass to implement the behavior of
our particular component. This may sound restrictive, as if we are
limited to some predefined set of routines, but that is not the case
at all. Keep in mind that the methods we are talking about are means
of interacting with the windowing system. A realistic application
might involve hundreds or even thousands of classes, with legions of
methods and variables and multiple threads of execution. The vast
majority of these are related to the particulars of our job. The
inherited methods of the JComponent
class, and of
other predefined classes, serve as a framework on which to hang code
that handles certain types of events and performs special tasks.
The paintComponent( )
method is an important
method of the JComponent
class; we override it to
implement the way our particular component displays itself on the
screen. The default behavior of paintComponent( )
doesn’t do any drawing at all; here, we’re overriding
paintComponent( )
to do something interesting. We
don’t override any of the other inherited members of
JComponent
because they provide basic
functionality and reasonable defaults for this (trivial) example. As
HelloJava grows, we’ll delve deeper into the inherited members
and use additional methods. We will also add some
application-specific methods and variables for the needs of
HelloJava.
JComponent
is really the tip of another iceberg
called SwingSwing.
Swing is Java’s user interface toolkit; we’ll discuss it
in some detail in Chapter 13 through Chapter 18.
We can correctly refer to HelloJava1
as a
JComponent
because subclassing can be thought of
as creating an “is a” relationship, in which the subclass
is a kind of its superclass. HelloJava1
is
therefore a kind of JComponent
. When we refer to a
kind of object, we mean any instance of that object’s class or
any of its
subclasses.
Later, we will look more closely at the
Java class hierarchy and see that JComponent
is
itself a subclass of the
Container
class, which is further derived from a
class called
Component
, and so on, as shown in Figure 2.2.
In this sense, a HelloJava1
object is a kind of
JComponent
, which is a kind of
Container
, and each of these can ultimately be
considered to be a kind of Component
. It’s
from these classes that HelloJava1
inherits its
basic graphical user interface functionality and the ability to have
other graphical components embedded within it.
Component
is a
subclass of the top-level Object
class, so all of
these classes define kinds of Object
s. Every other
class in the Java API inherits behavior from
Object
, which defines a few basic methods, as
you’ll see in Chapter 7. We’ll
continue to use the word object (lowercase o) in a generic way to
refer to an instance of any class; we’ll use
Object
to refer specifically to that class.
In our previous
example, the JComponent
class is referenced by its
fully qualified name javax.swing.JComponent
:
public class HelloJava1 extends javax.swing.JComponent {...}
The prefix on the class name identifies it as belonging to the
javax.swing
package. Packages provide a means for
organizing Java classes. A package is a group of
Java classes that are related by purpose or by application. Classes
in the same package have special access privileges with respect to
one another and may be designed to work together. Package names are
hierarchical and are used somewhat like Internet domain and host
names, to distinguish groups of classes by organization and
application. Classes may be dynamically loaded over networks from
arbitrary locations; within this context, packages provide a crude
namespace of Java classes.[7]
javax.swing
identifies a particular package that contains classes related to
Swing, Java 2’s fancy graphical user interface toolkit.
javax.swing.JComponent
identifies a specific
class, the JComponent
class, within that package.
The java.
hierarchy is special. Any package that
begins with java.
is part of the core Java API and
is available on any platform that supports Java. While
javax
normally denotes a standard extension to the
core platform, javax.swing
is an
exception—it really is part of the core API. Figure 2.3 illustrates some of the core
Java packages, showing a
representative class or two from each.
Some other notable core packages include:
java.lang
, which contains fundamental classes
needed by the Java language itself; java.awt
,
which contains classes of the pre-Java 2 Abstract Window Toolkit; and
java.net
, which contains the networking classes.
A few classes contain methods that are not written in Java, but are instead part of the native Java implementation on a particular platform. These are the only classes that have to be ported to a new platform. They form the basis for all interaction with the operating system. All other classes are built on or around these and are completely platform-independent.
The source for our HelloJava1
class defines a
method, paintComponent( )
,
that overrides the paintComponent( )
method from
the JComponent
class:
public void paintComponent(java.awt.Graphics g) { g.drawString("Hello, Java!", 125, 95); }
The paintComponent( )
method is called when
it’s time for our example to draw itself on the screen. It
takes a single argument, a Graphics
object, and
doesn’t return any type of value (void
) to
its caller.
Modifiers
are keywords placed before classes, variables, and methods to alter
their accessibility, behavior, or semantics. paintComponent( )
is declared as
public
, which means it can be invoked
(called) by methods in classes other than
HelloJava1
. In this case, it’s the Java
windowing environment that is calling our paintComponent( )
method. A method or variable declared as
private
is
inaccessible from outside of its class.
The Graphics
object, an
instance of the Graphics
class, represents a
particular graphical drawing area. (It is also called a
graphics context.) It contains methods that can
be used to draw in this area, and variables that represent
characteristics such as clipping or drawing modes. The particular
Graphics
object we are passed in the
paintComponent( )
method corresponds to our
component’s area of the screen.
The Graphics
class
provides methods for rendering shapes, images, and text. In
HelloJava1
, we invoke the drawString( )
method of our Graphics
object to
scrawl our message at the specified coordinates. (For a description
of the methods available in the Graphics
class,
see Chapter 17.)
As in C++, a method or variable of an
object is accessed in a
hierarchical way by appending a dot (.
) and its
name to the object that holds it. We invoked the drawString( )
method of the Graphics
object
(referenced by our g
variable) in this way:
g.drawString("Hello, Java!", 125, 95);
You may need to get used to the idea that our application is drawn by a method that is called by an outside agent at arbitrary times. How can we do anything useful with this? How do we control what gets done and when? These answers will be forthcoming. For now, just think about how you would structure applications that draw themselves on command.
[6] All of the
ready-to-run examples in this book are included on the accompanying
CD-ROM. The comment line //file: ...
indicates the
name of the source file.
[7] There are many efforts under way to find a general solution to the problem of locating resources in a globally distributed computing environment. The Uniform Resource Identifier Working Group of the IETF has proposed Uniform Resource Names (URNs). A URN would be a more abstract and persistent identifier that would be resolved to a URL through the use of a name service. We can imagine a day when there will exist a global namespace of trillions of persistent objects forming the infrastructure for all computing resources. Java provides an important evolutionary step in this direction.
Get Learning Java now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.