Although Java is object-oriented to a great extent, it is not a pure object-oriented language. One of the reasons Java is not purely object-oriented is that not everything in it is an object. For example, Java allows you to declare variables of primitive types (
boolean, etc.) that aren't objects. And Java has static fields and methods, which are independent and separate from objects. This article gives advice on how to use static fields and methods in a Java program, while maintaining an object-oriented focus in your designs.
The lifetime of a class in a Java virtual machine (JVM) has many similarities to the lifetime of an object. Just as an object can have state, represented by the values of its instance variables, a class can have state, represented by the values of its class variables. Just as the JVM sets instance variables to default initial values before executing initialization code, the JVM sets class variables to default initial values before executing initialization code. And like objects, classes can be garbage collected if they are no longer referenced by the running application.
Nevertheless, significant differences exist between classes and objects. Perhaps the most important difference is the way in which instance and class methods are invoked: instance methods are (for the most part) dynamically bound, but class methods are statically bound. (In three special cases, instance methods are not dynamically bound: invocation of private instance methods, invocation of
init methods (constructors), and invocations with the
super keyword. See Resources for more information.)
Another difference between classes and objects is the degree of data hiding granted by the private access levels. If an instance variable is declared private, only instance methods can access it. This enables you to ensure the integrity of the instance data and make objects thread-safe. The rest of the program cannot access those instance variables directly, but must go through the instance methods to manipulate the instance variables. In an effort to make a class behave like a well-designed object, you can make class variables private and define class methods that manipulate them. Nevertheless, you don't get as good a guarantee of thread safety or even data integrity in this way, because a certain kind of code has a special privilege that gives them direct access to private class variables: instance methods, and even initializers of instance variables, can access those private class variables directly.
So the static fields and methods of classes, although similar in many ways to the instance fields and methods of objects, have significant differences that should affect the way you use them in designs.
Treating classes as objects
As you design Java programs, you will likely encounter many situations in which you feel the need for an object that acts in some ways like a class. You may, for example, want an object whose lifetime matches that of a class. Or you may want an object that, like a class, restricts itself to a single instance in a given name space.
In design situations such as these, it can be tempting to create a class and use it like an object in order to define class variables, make them private, and define some public class methods that manipulate the class variables. Like an object, such a class has state. Like a well-designed object, the variables that define the state are private, and the outside world can only affect this state by invoking the class methods.
Unfortunately, some problems exist with this "class-as-object" approach. Because class methods are statically bound, your class-as-object won't enjoy the flexibility benefits of polymorphism and upcasting. (For definitions of polymorphism and dynamic binding, see the Design Techniques article, Composition versus Inheritance.) Polymorphism is made possible, and upcasting useful, by dynamic binding, but class methods aren't dynamically bound. If someone subclasses your class-as-object, they will not be able to override your class methods by declaring class methods of the same name; they will only be able to hide them. When one of these redefined class methods is invoked, the JVM will select the method implementation to execute not by the class of an object at runtime, but by the type of a variable at compile time.
In addition, the thread safety and data integrity achieved by your meticulous implementation of the class methods in your class-as-object is like a house built of straw. Your thread safety and data integrity will be guaranteed so long as everyone uses the class methods to manipulate the state stored in the class variables. But a careless or clueless programmer could, with the addition of one instance method that accesses your private class variables directly, inadvertently huff and puff and blow your thread safety and data integrity away.
For this reason, my main guideline concerning class variables and class methods is:
Don't treat classes like objects.
In other words, don't design with static fields and methods of a class as if they were the instance fields and methods of an object.
If you want some state and behavior whose lifetime matches that of a class, avoid using class variables and class methods to simulate an object. Instead, create an actual object and use a class variable to hold a reference to it and class methods to provide access to the object reference. If you want to ensure that only one instance of some state and behavior exists in a single name space, don't try to design a class that simulates an object. Instead, create a singleton -- an object guaranteed to have only one instance per name space.
So what are class members good for?
In my opinion, the best mindset to cultivate when designing Java programs is to think objects, objects, objects. Focus on designing great objects, and think of classes primarily as blueprints for objects -- the structure in which you define the instance variables and instance methods that make up your well-designed objects. Besides that, you can think of classes as providing a few special services that objects can't provide, or can't provide as elegantly. Think of classes as:
- the proper place to define "utility methods" (methods that take input and provide output only through passed parameters and the return value)
- a way to control access to objects and data
Methods that don't manipulate or use the state of an object or class I call "utility methods." Utility methods merely return some value (or values) calculated solely from data passed to the method as parameters. You should make such methods static and place them in the class most closely related to the service the method provides.
An example of a utility method is the
String copyValueOf(char data) method of class
String. This method produces its output, a return value of type
String, solely from its input parameter, an array of
copyValueOf() neither uses nor affects the state of any object or class, it is a utility method. And, like all utility methods should be,
copyValueOf() is a class method.
So one of the main ways to use class methods is as utility methods -- methods that return output calculated solely from input parameters. Other uses of class methods involve class variables.
Class variables for data hiding
One of the fundamental precepts in object-oriented programming is data hiding -- restricting access to data to minimize the dependencies between the parts of a program. If a particular piece of data has limited accessibility, that data can change without breaking those portions of the program that can't access the data.
If, for example, an object is needed only by instances of a particular class, a reference to it can be stored in a private class variable. This gives all instances of this class handy access to that object -- the instances just use it directly -- but no other code anywhere else in the program can get at it. In a similar fashion, you can use package access and protected class variables to reduce the visibility of objects that need to be shared by all members of a package and subclasses.
Public class variables are a different story. If a public class variable isn't final, it is a global variable: that nasty construct that is the antithesis of data hiding. There is never any excuse for a public class variable, unless it is final.
Final public class variables, whether primitive type or object reference, serve a useful purpose. Variables of primitive types or of type
String are simply constants, which in general help to make programs more flexible (easier to change). Code that uses constants is easier to change because you can change the constant value in one place. Public final class variables of reference types allow you to give global access to objects that are needed globally. For example,
System.err are public final class variables that give global access to the standard input output and error streams.
Thus the main way to view class variables is as a mechanism to limit the accessibility of (meaning, to hide) variables or objects. When you combine class methods with class variables, you can implement even more complicated access policies.
Using class methods with class variables
Aside from acting as utility methods, class methods can be used to control access to objects stored in class variables -- in particular, to control how the objects are created or managed. Two examples of this kind of class method are the
getSecurityManager() methods of class
System. The security manager for an application is an object that, like the standard input, output, and error streams, is needed in many different places. Unlike the standard I/O stream objects, however, a reference to the security manager is not stored in a public final class variable. The security manager object is stored in a private class variable, and the set and get methods implement a special access policy for the object.
Java's security model places a special restriction on the security manager. Prior to Java 2 (previously known as JDK 1.2), an application began its life with no security manager (
null). The first call to
setSecurityManager() established the security manager, which thereafter was not allowed to change. Any subsequent calls to
setSecurityManager() would yield a security exception. In Java 2, the application always starts out with a security manager, but similar to the previous versions, the
setSecurityManager() method will allow you to change the security manager one time, at the most.
The security manager provides a good example of how class methods can be used in conjunction with private class variables to implement a special access policy for objects referenced by the class variables. Aside from utility methods, think of class methods as the means to establish special access policies for object references and data stored in class variables.
The main point of advice given in this article is:
Don't treat classes like objects.
If you need an object, make an object. Restrict your use of class variables and methods to defining utility methods and implementing special kinds of access policies for objects and primitive types stored in class variables. Although not a pure object-oriented language, Java is nevertheless object-oriented to a great extent, and your designs should reflect that. Think objects.
Next month's Design Techniques article will be the last of this column. I'll soon begin writing a book based on the Design Techniques material, Flexible Java, and will place that material on my web site as I go. So please follow that project along and send me feedback. After a break of a month or two, I'll be back at JavaWorld and SunWorld with a new column focused on Jini.
A request for reader participation
I encourage your comments, criticisms, suggestions, flames -- all kinds of feedback -- about the material presented in this column. If you disagree with something, or have something to add, please let me know.
You can participate in a discussion forum devoted to this material, enter a comment via the form at the bottom of the article, or e-mail me directly using the link provided in my bio below.