Classes are the building blocks of a Java application. A
class can contain methods (functions), variables,
initialization code, and, as we’ll discuss later, other classes. It serves
as a blueprint for making class instances, which are runtime objects
(individual copies) that implement the class structure. You declare a
class with the class keyword. Methods
and variables of the class appear inside the braces of the class
declaration:
classPendulum{floatmass;floatlength=1.0f;intcycles;floatgetPosition(floattime){...}...}
The Pendulum class contains three
variables: mass, length, and cycles. It also defines a method called getPosition(), which takes a float value as an argument and returns a
float value as a result. Variables and
method declarations can appear in any order, but variable initializers
can’t make “forward references” to other variables that appear later. Once
we’ve defined the Pendulum class, we
can create a Pendulum object (an instance of that
class) as follows:
Pendulump;p=newPendulum();
Recall that our declaration of the variable p doesn’t create a Pendulum object; it simply creates a variable
that refers to an object of type Pendulum. We still had to create the object,
using the new keyword, as shown in
the second line of the preceding code snippet. Now that we’ve created a
Pendulum object, we can access its
variables and methods, as we’ve already seen many times:
p.mass=5.0;floatpos=p.getPosition(1.0);
Two kinds of variables can be defined in a class:
instance variables and static
variables. Every object instance has its own set of instance
variables; the values of these variables in one instance of an object can
differ from the values in another object. We’ll talk about static
variables later, which, in contrast, are shared among all instances of an
object. In either case, if you don’t initialize a variable when you
declare it, it’s given a default value appropriate for its type (null, zero, or false).
Figure 5-1 shows a hypothetical
TextBook application that uses two
instances of Pendulum through the
reference-type variables bigPendulum
and smallPendulum. Each of these
Pendulum objects has its own copy of
mass, length, and cycles. As with variables, methods defined in a
class may be instance methods or static
methods. An instance method is associated with just one
instance of the class, but the relationship isn’t quite as simple as it is
for variables. Instance methods are accessed through an object instance,
but the object doesn’t really have its own “copy” of the methods (there is
no duplication of code). Instead, the association means that instance
methods can “see” and operate on the values of the instance variables of
the object. As you’ll see in Chapter 6 when we
talk about subclassing, there’s more to learn about how methods see
variables. In that chapter, we’ll also discuss how instance methods can be
“overridden” in child classes—a very important feature of object-oriented
design. Both aspects differ from static methods, which we’ll see are
really more like global functions, as they are associated with a class by
name only.
Inside a class, we can access variables and call methods
of the class directly by name. Here’s an example that expands on our
Pendulum:
classPendulum{...voidresetEverything(){mass=1.0;length=1.0;cycles=0;...floatstartingPosition=getPosition(0.0);}...}
Other classes access members of an object through a reference, using the dot selector notation that we discussed in the last chapter:
classTextBook{...voidshowPendulum(){Pendulumbob=newPendulum();...inti=bob.cycles;bob.resetEverything();bob.mass=1.01;...}...}
Here we have created a second class, TextBook, that uses a Pendulum object. It creates an instance in
showPendulum() and then invokes
methods and accesses variables of the object through the reference
bob.
Several factors affect whether class members can be accessed from
another class. You can use the visibility modifiers public, private, and protected to control
access; classes can also be placed into a package,
which affects their scope. The private modifier, for example, designates a
variable or method for use only by other members of the class itself. In
the previous example, we could change the declaration of our variable
cycles to private:
classPendulum{...privateintcycles;...
Now we can’t access cycles from
TextBook:
classTextBook{...voidshowPendulum(){...inti=bob.cycles;// Compile-time error
If we still need to access cycles in some capacity, we might add a public
getCycles() method to the Pendulum class. (Creating accessor methods
like this is a good design rule because it allows future flexibility in
changing the type or behavior of the value.) We’ll take a detailed look
at packages, access modifiers, and how they affect the visibility of
variables and methods in Chapter 6.
As we’ve said, instance variables and methods are
associated with and accessed through an instance of the class (i.e.,
through a particular object, like bob
in the previous example). In contrast, members that are declared with
the static modifier live in
the class and are shared by all instances of the class. Variables
declared with the static modifier are
called static variables or class
variables; similarly, these kinds of methods are called
static methods or class
methods. We can add a static variable to our Pendulum example:
classPendulum{...staticfloatgravAccel=9.80;...
We have declared the new float
variable gravAccel as static. That means that it is associated with
the class, not with an individual instance and if we change its value
(either directly or through any instance of a Pendulum), the value changes for all Pendulum objects, as shown in Figure 5-2.
Static members can be accessed like instance members. Inside our
Pendulum class, we can refer to
gravAccel like any other
variable:
classPendulum{...floatgetWeight(){returnmass*gravAccel;}...}
However, since static members exist in the class itself,
independent of any instance, we can also access them directly through
the class. We don’t need a Pendulum
object to get or set the variable gravAccel; instead, we can use the class to
select the variable:
Pendulum.gravAccel=8.76;
This changes the value of gravAccel as seen by all instances. Why would
we want to change the value of gravAccel? Well, perhaps we want to explore
how pendulums would work on different planets. Static variables are also
very useful for other kinds of data that is shared among classes at
runtime. For instance, you can create methods to register your object
instances so that they can communicate, or so that you can keep track of
all of them. It’s also common to use static variables to define constant
values. In this case, we use the static modifier along with the final modifier. So, if
we cared only about pendulums under the influence of the Earth’s
gravitational pull, we might change Pendulum as follows:
classPendulum{...staticfinalfloatEARTH_G=9.80;...
We have followed a common convention here and named our constant
with capital letters. The value of EARTH_G is a constant; it can be accessed
through the class Pendulum or its
instances, but its value can’t be changed at runtime.
It’s important to use the combination of static and final only for things
that are really constant. That’s because the compiler is allowed to
“inline” such values within classes that reference them. This means that
if you change a static final
variable, you may have to recompile all code that uses that class (this
is really the only case where you have to do that in Java). Static
members are useful as flags and identifiers, which can be accessed from
anywhere. They are also useful for values needed in the construction of
an instance itself. In our example, we might declare a number of static
values to represent various kinds of Pendulum objects:
classPendulum{...staticintSIMPLE=0,ONE_SPRING=1,TWO_SPRING=2;...
We might then use these flags in a method that sets the type of a
Pendulum or in a special constructor,
as we’ll discuss shortly:
Pendulumpendy=newPendulum();pendy.setType(Pendulum.ONE_SPRING);
Again, inside the Pendulum
class, we can use static members directly by name, as well; there’s no
need for the Pendulum. prefix:
classPendulum{...voidresetEverything(){setType(SIMPLE);...}...}
In the previous section, we saw two uses for static final
variables (constants). The first was to create true constants; in that
case, it was the numeric constant EARTH_G, but it could easily have been a
String or Date value. The second usage was to create a
fixed set of identifiers, SIMPLE,
ONE_SPRING, etc., whose actual
values were not as important as their uniqueness and, perhaps, their
particular order.
Enumerations were added to the Java language to replace this identifier usage with a mechanism that is both safer and, in some cases, more efficient. We could have declared our pendulum types as an enumeration like so:
publicenumPendulumTypes{Simple,OneSpring,TwoSpring}
This enumeration creates not only the values, but also a new
type, PendulumTypes, whose value is
limited to one of the three discrete identifiers. Calling code can
refer to the values as it did through our class: PendulumTypes.Simple. We’ve changed our case
convention here to diverge from the convention for integer constants,
but you can stick with uppercase if you prefer.
Later, when we talk about importing classes and packages, we’ll discuss the static import feature of Java, which allows us to import static identifiers and enumerations (which, as we’ve seen, are related) into a class so that we can use them by their simple names. For example:
newPendulum(OneSpring);
We’ll go into detail about enumerations later in this chapter after we’ve covered objects in more depth.