4.10 Other Modifiers for Members

Certain characteristics of fields and/or methods can be specified in their declarations by the following keywords:

  • static

  • final

  • abstract

  • synchronized

  • native

  • transient

  • volatile

static Members

The declaration of static members is prefixed by the keyword static to distinguish them from instance members.

Static members belong to the class in which they are declared, and are not part of any instance of the class. Depending on the accessibility modifiers of the static members in a class, clients can access these by using the class name, or through object references of the class. The class need not be instantiated to access its static members.

Static variables (also called class variables) only exist in the class they are defined in. They are not instantiated when an instance of the class is created. In other words, the values of these variables are not a part of the state of any object. When the class is loaded, static variables are initialized to their default values if no explicit initialization expression is specified (see Section 8.2, p. 336).

Static methods are also known as class methods. A static method in a class can directly access other static members in the class. It cannot access instance (i.e., non-static) members of the class, as there is no notion of an object associated with a static method. However, note that a static method in a class can always use a reference of the class's type to access its members, regardless of whether these members are static or not.

A typical static method might perform some task on behalf of the whole class and/or for objects of the class. In Example 4.10, the static variable counter keeps track of the number of instances of the Light class created. The example shows that the static method writeCount can only access static members directly, as shown at (2), but not non-static members, as shown at (3). The static variable counter will be initialized to the value 0 when the class is loaded at runtime. The main() method at (4) in class Warehouse shows how static members of class Light can be accessed using the class name, and via object references having the class type.

A summary of how static members are accessed in static and non-static code is given in Table 4.1.

Example 4.10 Accessing Static Members
class Light {
    // Fields
    int     noOfWatts;      // wattage
    boolean indicator;      // on or off
    String  location;       // placement

    // Static variable
    static int counter;     // No. of Light objects created.         (1)

    // Explicit Default Constructor
    Light() {
        noOfWatts = 50;
        indicator = true;
        location  = "X";
        ++counter;          // Increment counter.
    }

    // Static method
    public static void writeCount() {
         System.out.println("Number of lights: " + counter);      // (2)
         // Compile error. Field noOfWatts is not accessible:
         // System.out.println("Number of Watts: " + noOfWatts);  // (3)
    }
}
public class Warehouse {
    public static void main(String[] args) {                      // (4)

        Light.writeCount();                       // Invoked using class name
        Light aLight = new Light();               // Create an object
        System.out.println(
            "Value of counter: " + Light.counter  // Accessed via class name
        );
        Light bLight = new Light();               // Create another object
        bLight.writeCount();                      // Invoked using reference
        Light cLight = new Light();               // Create another object
        System.out.println(
            "Value of counter: " + cLight.counter // Accessed via reference
        );
    }
}

Output from the program:

Number of lights: 0
Value of counter: 1
Number of lights: 2
Value of counter: 3

final Members

A final variable is a constant, despite being called a variable. Its value cannot be changed once it has been initialized. This applies to instance, static and local variables, including parameters that are declared final.

  • A final variable of a primitive data type cannot change its value once it has been initialized.

  • A final variable of a reference type cannot change its reference value once it has been initialized, but the state of the object it denotes can still be changed.

These variables are also known as blank final variables. Final static variables are commonly used to define manifest constants (also called named constants), for example Integer.MAX_VALUE, which is the maximum int value. Variables defined in an interface are implicitly final (see Section 6.4, p. 251). Note that a final variable need not be initialized at its declaration, but it must be initialized once before it is used. For a discussion on final parameters, see Section 3.22, p. 94.

A final method in a class is complete (i.e., has an implementation) and cannot be overridden in any subclass (see Section 6.2, p. 233). Subclasses are then restricted in changing the behavior of the method.

Final variables ensure that values cannot be changed, and final methods ensure that behavior cannot be changed. Final classes are discussed in Section 4.8.

The compiler is able to perform certain code optimizations for final members, because certain assumptions can be made about such members.

In Example 4.11, the class Light defines a final static variable at (1) and a final method at (2). An attempt to change the value of the final variable at (3) results in a compile-time error. The subclass TubeLight attempts to override the final method setWatts() from the superclass Light at (4), which is not permitted. The class Warehouse defines a final local reference aLight at (5). The state of the object denoted by aLight can be changed at (6), but its reference value cannot be changed as attempted at (7).

Example 4.11 Accessing Final Members
class Light {
    // Final static variable                  (1)
    final public static double KWH_PRICE = 3.25;

    int noOfWatts;

    // Final instance method                  (2)
    final public void setWatts(int watt) {
         noOfWatts = watt;
    }
    public void setKWH() {
      // KWH_PRICE = 4.10;                 // (3) Not OK. Cannot be changed.
    }
}

class TubeLight extends Light {
    // Final method in superclass cannot be overridden.
    // This method will not compile.
    /*
    public void setWatts(int watt) {       // (4) Attempt to override.
         noOfWatts = 2*watt;
    }
    */
}

public class Warehouse {
    public static void main(String[] args) {

        final Light aLight = new Light();// (5) Final local variable.
        aLight.noOfWatts = 100;          // (6) OK. Changing object state.
    //  aLight = new Light();            // (7) Not OK. Changing final reference.
    }
}

abstract Methods

An abstract method has the following syntax:


abstract <accessibility modifier> <return type> <method name(<parameter list>)
            <throws clause>;

An abstract method does not have an implementation; that is, no method body is defined for an abstract method, only the method prototype is provided in the class definition. Its class is then abstract (i.e., incomplete) and must be explicitly declared as such (see Section 4.8, p. 134). Subclasses of an abstract class must then provide the method implementation; otherwise, they are also abstract. See Section 4.8, where Example 4.8 also illustrates the usage of abstract methods.

Only an instance method can be declared abstract. Since static methods cannot be overridden, declaring an abstract static method would make no sense. A final method cannot be abstract (i.e., cannot be incomplete) and vice versa. The keyword abstract cannot be combined with any nonaccessibility modifiers for methods. Methods specified in an interface are implicitly abstract, as only the method prototypes are defined in an interface (see Section 6.4, p. 251).

synchronized Methods

Several threads can be executing in a program (see Section 9.4, p. 359). They might try to execute several methods on the same object simultaneously. If it is desired that only one thread at a time can execute a method in the object, the methods can be declared synchronized. Their execution is then mutually exclusive among all threads. At any given time, at the most one thread can be executing a synchronized method on an object. This discussion also applies to static synchronized methods of a class.

In Example 4.12, both the push() and the pop() methods are synchronized in class StackImpl. Now, only one thread at a time can execute a synchronized method in an object of the class StackImpl. This means that it is not possible for the state of an object of the class StackImpl to be corrupted, for example, while one thread is pushing an element and another is popping the stack.

Example 4.12 Synchronized Methods
class StackImpl {
    private Object[] stackArray;
    private int topOfStack;
    // ...
    synchronized public void push(Object elem) { // (1)
        stackArray[++topOfStack] = elem;
    }

    synchronized public Object pop() {           // (2)
        Object obj = stackArray[topOfStack];
        stackArray[topOfStack] = null;
        topOfStack--;
        return obj;
    }

    // Other methods, etc.
    public Object peek() { return stackArray[topOfStack]; }
}

native Methods

Native methods are also called foreign methods. Their implementation is not defined in Java but in another programming language, for example, C or C++. Such a method can be declared as a member in a Java class definition. Since its implementation appears elsewhere, only the method prototype is specified in the class definition. The method prototype is prefixed with the keyword native. It can also specify checked exceptions in a throws clause, which cannot be checked by the compiler since the method is not implemented in Java. The next example shows how native methods are used.

The Java Native Interface (JNI) is a special API that allows Java methods to invoke native functions implemented in C.

In the following example, a native method in class Native is declared at (2). The class also uses a static initializer block (see Section 8.2, p. 336) at (1) to load the native library when the class is loaded. Clients of the Native class can call the native method like any another method, as at (3).

class Native {

    /*
     * The static block ensures that the native method library
     * is loaded before the native method is called.
     */
    static {
        System.loadLibrary("NativeMethodLib");  // (1) Load native library.
    }
   
    native void nativeMethod();                 // (2) Native method prototype.
    // ...
   
}

class Client {
    //...
    public static void main(String[] args) {
        Native aNative = new Native();
        aNative.nativeMethod();                 // (3) Native method call.
    }
    //...
}

transient Fields

Objects can be stored using serialization. Serialization transforms objects into an output format that is conducive for storing objects. Objects can later be retrieved in the same state as when they were serialized, meaning that all fields included in the serialization will have the same values as at the time of serialization. Such objects are said to be persistent.

A field can be specified as transient in the class declaration, indicating that its value should not be saved when objects of the class are written to persistent storage. In the following example, the field currentTemperature is declared transient at (1), because the current temperature is most likely to have changed when the object is restored at a later date. However, the value of the field mass, declared at (2), is likely to remain unchanged. When objects of the class Experiment are serialized, the value of the field currentTemperature will not be saved, but that of the field mass will be as part of the state of the serialized object.

class Experiment implements Serializable {
    // ...
    // The value of currentTemperature will not persist
    transient int currentTemperature;     // (1) Transient value.
    double mass;                          // (2) Persistent value.
}

Specifying the transient modifier for static variables is redundant and, therefore, discouraged. Static variables are not part of the persistent state of a serialized object.

volatile Fields

During execution, compiled code might cache the values of fields for efficiency reasons. Since multiple threads can access the same field, it is vital that caching is not allowed to cause inconsistencies when reading and writing the value in the field. The volatile modifier can be used to inform the compiler that it should not attempt to perform optimizations on the field, which could cause unpredictable results when the field is accessed by multiple threads.

In the simple example that follows, the value of the field clockReading might be changed unexpectedly by another thread while one thread is performing a task that involves always using the current value of the field clockReading. Declaring the field as volatile ensures that a write operation will always be performed on the master field variable, and a read operation will always return the correct current value.

class VitalControl {
    // ...
    volatile long clockReading;
    // Two successive reads might give different results.
}

Table 4.5. Summary of Other Modifiers for Members

Modifiers

Fields

Methods

static

Defines a class variable.

Defines a class method.

final

Defines a constant.

The method cannot be overridden.

abstract

Not relevant.

No method body is defined. Its class must also be designated abstract.

synchronized

Not relevant.

Only one thread at a time can execute the method.

native

Not relevant.

Declares that the method is implemented in another language.

transient

The value in the field will not be included when the object is serialized.

Not applicable.

volatile

The compiler will not attempt to optimize access to the value in the field.

Not applicable.