Cover by Patrick Niemeyer, Daniel Leuck

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

O'Reilly logo

Classes

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:

    class Pendulum {
        float mass;
        float length = 1.0f;
        int cycles;

        float getPosition ( float time ) {
            ...
        }
        ...
    }

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:

    Pendulum p;
    p = new Pendulum();

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;
    float pos = 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.

Accessing Fields and Methods

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:

    class Pendulum {
        ...
        void resetEverything() {
            mass = 1.0;
            length = 1.0;
            cycles = 0;
            ...
            float startingPosition = getPosition( 0.0 );
        }
        ...
    }
Instances of the Pendulum class

Figure 5-1. Instances of the Pendulum class

Other classes access members of an object through a reference, using the dot selector notation that we discussed in the last chapter:

    class TextBook {
        ...
        void showPendulum() {
            Pendulum bob = new Pendulum();
            ...
            int i = 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:

    class Pendulum {
        ...
        private int cycles;
        ...

Now we can’t access cycles from TextBook:

    class TextBook {
        ...
        void showPendulum() {
            ...
            int i = 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.

Static Members

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:

    class Pendulum {
        ...
        static float gravAccel = 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 variables shared by all instances of a class

Figure 5-2. Static variables shared by all instances of a class

Static members can be accessed like instance members. Inside our Pendulum class, we can refer to gravAccel like any other variable:

    class Pendulum {
        ...
        float getWeight () {
            return mass * 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:

    class Pendulum {
        ...
        static final float EARTH_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:

    class Pendulum {
        ...
        static int SIMPLE = 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:

    Pendulum pendy = new Pendulum();
    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:

    class Pendulum {
        ...
        void resetEverything() {
            setType ( SIMPLE );
            ...
        }
        ...
    }

Constants versus enumerations

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:

    public enum PendulumTypes { 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:

    new Pendulum(OneSpring );

We’ll go into detail about enumerations later in this chapter after we’ve covered objects in more depth.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required