When to make accessors public, and when to keep them private
This article gives guidance on the visibility of accessor methods. It is modified from Chapter 8 of The Object Primer 2nd Edition.
We introduced accessors in "Accessors increase robustness of Java code" where we saw that accessors (member functions that directly manipulate the value of fields) may add minimal overhead to your code. At the same time, accessors increase the robustness of classes and components because they help to hide the implementation details of your classes. Accessors come in two flavors: setters and getters. A setter modifies the value of a field; a getter obtains its value.
Your choice of visibility for a member function, which defines the level of access to it by Java objects, provides an opportunity to reduce the coupling within your software. Because accessors are simply Java member functions, it is therefore important that you choose the visibility of your accessors wisely. You saw in "Member function visibility in Java programs" that Java offers four levels of visibility: public, protected, private, and default.
- A member function with public visibility can be invoked by any other member function in any other object or class.
- A member function with protected visibility can be invoked by any member function in the class in which it is defined or any subclasses of that class.
- A member function with private visibility can only be invoked by other member functions in the class in which it is defined, but not in the subclasses.
- A member function with default visibility is effectively public to all other classes within the same package, but private to classes external to the package. This is sometimes called package visibility or friendly visibility.
So how do you determine the proper visibility of an accessor method? My experience is that you should always strive to make accessors protected, so only subclasses can access the attributes. You should try to make accessors private if subclasses don’t need access to the attribute. Only when an external class or object needs to access an attribute should you make the appropriate getter or setter public. It is quite common for the visibility of corresponding getter and setter methods to be different: in the class
Seminar, you see the
getTitle() method has public visibility, yet
setTitle() has private visibility. As Figure 1 reveals, the only place the setter method is called is in the getter to formulate the title of the seminar, a combination of the course number, seminar number, and course name.
The important thing to understand is that you don’t need to make all of your accessors public. Instead, you can define their visibility as you see fit. Unfortunately, this advice only holds for Java classes that aren’t beans — accessors that represent public JavaBean properties must be public as must accessors for persistent properties of an Enterprise JavaBean (EJB) under the 2.x EJB specification. Note that you may still have accessor methods for JavaBeans that do not have public visibility; it is just that the attributes that those accessors encapsulate cannot be accessed via the public interface of the bean (which is likely what you wanted to achieve in the first place). Similarly, EJB accessor methods that are not public imply that the attributes that they encapsulate cannot be persistent.
As an aside, the implementation of the getter,
getTitle(), in Figure 1 is an example of lazy initialization, an approach where the value of an attribute is initialized (set) when it is first accessed. The advantage of this approach is you only incur the expense of obtaining the value when and if you need it. On the surface, this doesn’t appear like much of a savings for determining the seminar name, but it could be if the course object resided on another server, needed to be read in from persistent storage, and then needed to have its name transmitted across the network. The main disadvantage of lazy initialization is your code becomes more complex because you need to check to see if the attribute has been defined yet and, if not, obtain its value. Lazy initialization is typically used when an attribute is expensive to calculate or obtain (perhaps it is very large and would take significant time to transmit across the network) and when it is not always required each time the object is brought into memory.
The implementation of the setter,
setTitle(), in Figure 1 is interesting from a naming convention point of view, a topic that I discussed in "Java naming conventions." The parameter is named the same as the instance attribute (field) itself, in this case
title. This is what is called a naming collision, one that is luckily resolved by preceding the instance attribute with "
this." . A better approach in this case may have been simply to use a different name for the text parameter, such as
newTitle, so as not to have this problem in the first place.