Access Issues

When you look through the many Java tutorials that are available, they all talk about access restrictions. The words private, protected, and public are some of the first keywords that a newbie Java programmer learns. However, most of these tutorials discuss access restrictions only with regards to the impact of restrictions on the code. By now, you should know what a private method is and the difference between private and protected. Therefore, I won't bother rehashing this familiar territory. Instead, I would like to take your understanding of access restrictions to another level. Instead of focusing on what they do, I will focus on which to use in various situations.

Preferred Restrictions

While writing Java programs, many programmers fall into a definable pattern. All attributes are private, all interface methods are public, and all helper methods are private. Unfortunately, this causes a ton of problems in the real world. Consider the following common GUI code:

package oracle.hcj.review;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class SomeDialogApp extends JDialog implements ActionListener {
 private JButton okBtn = null;
 private JButton someBtn = null;
 // . . . etc.
 public SomeDialogApp( ) {
 setJMenuBar(buildMenu( ));
 buildContents( );
 }
 public void actionPerformed(final ActionEvent event) {
 Object source = event.getSource( );
 if (source == this.okBtn) {
 handleOKBtn( );
 } else if (source == this.someBtn) {
 handleSomeBtn( );
 }
 // . . . etc.
 }
 private void buildContents( ) {
 this.okBtn = new JButton("OK");
 this.okBtn.addActionListener(this);
 this.someBtn = new JButton("Something");
 this.someBtn.addActionListener(this);
 // . . . etc.
 }
 private JMenuBar buildMenu( ) {
 JMenuBar result = new JMenuBar( );
 // . . . add items and menus
 return result;
 }
 private void handleOKBtn( ) {
 // handler code
 }
 private void handleSomeBtn( ) {
 // handler code
 }
}


In this code, the attributes are all private. There are also four private helper methods: handleSomeBtn( ), handleOKBtn( ), buildContents( ), and buildMenu( ). Everything in this class is okay until someone wants to modify the class. For example, what if I only want to change the functionality of the handleOKbtn( ) method and reuse the rest of the class? In this case, I would basically have to reimplement the entire dialog. Accessing the button instance itself is impossible, so I wouldn't be able to rebuild the actionPerfomed( ) method. Furthermore, since the helper method is private, I can't access that either. Time to reinvent the wheel. On the other hand, if all those helper methods were protected instead of private, I would be able to simply redefine the meaning of the handleOKBtn( ) helper method and reuse the entire class. When developing classes that others will use, you can never be sure what they will want to do to the class. But if your goal is to promote reuse, making the helper methods protected allows the users to extend the class. Also, since protected blocks users attempting to use the method directly, you won't be giving up any security on that front. On the other hand, those inheriting from your class will have access to these helper methods. However, the general assumption you should be making is that people extending the class generally know what they are doing. Even if they don't, the worst they can do is break their derived class.

Screenshot

I came hard up against this problem recently when trying to extend the Introspector class to perform some functionality. I merely wanted to redefine one method. However, because that method was private, and Introspector is implicitly final (which we will discuss in the next chapter), I couldn't do it.


In the end, you are better off using private for attributes—public for public methods, and protected for helper methods. This gives your class maximum reusability without breaking encapsulation.

Friends Allow Unrestricted Access

Most access permissions are clearly laid out in the class file. However, many Java programmers don't understand the access permissions as they are related to instances of the same class. I call these instances friend instances. In real life, friends don't allow unrestricted access. You can rarely borrow a friend's car without asking. Java is much more friendly and trusting. In Java, instances of the same class are friends and give unrestricted access to all of their properties and methods. This unlimited access is a serious hazard that must be carefully avoided while making solid code. The class in Example 1-10 shows how friend instance access works.

Example 1-10. Demonstration of friend instance access
package oracle.hcj.review;
public class FriendAccess {
 private int value;
 public FriendAccess(final int value) {
 setValue(value);
 }
 public void setValue(final int value) {
 this.value = value;
 }
 public int getValue( ) {
 return value;
 }
 public void someMethod(final FriendAccess obj) {
 if ((obj.value == 5) && (this.value == 5)) {
 obj.value = 25; // <= Ouch, this works and bypasses the setter.
 }
 }
}


In someMethod, different instances of the same class have complete access to each other. This can become a problem when you modify the class a little bit. Hack on the class until you end up with this variation, which demonstrates the dangers of friend access:

package oracle.hcj.review;
public class FriendAccess {
 private int value;
 public FriendAccess(final int value) {
 setValue(value);
 }
 public void setValue(final int value) {
 if (value > 10) {
 throw new IllegalArgumentException( );
 }
 this.value = value;
 }
 public int getValue( ) {
 return value;
 }
 public void someMethod(final FriendAccess obj) {
 if ((obj.value == 5) && (this.value == 5)) {
 obj.value = 25; }
 }
}


In this variation, the range-checking code that is emphasized in the setValue( ) method was added. Now, if you look back at someMethod( ), you see that the method body sets the property value of the instance passed and completely bypasses the setter. The problem is that you set value to something that the setter would have rejected as illegal. However, since you have access to the variables directly, you can make the change and get away with it. Later, when some other class uses the object, expecting its value to be less than or equal to 10, your code will explode. Fortunately, you can easily block this with another edit to the code:

package oracle.hcj.review;
public class FriendAccess {
 private int value;
 public FriendAccess(final int value) {
 setValue(value);
 }
 public void setValue(final int value) {
 if (value > 10) {
 throw new IllegalArgumentException( );
 }
 this.value = value;
 }
 public int getValue( ) {
 return value;
 }
 public void someMethod(final FriendAccess obj) {
 if ((obj.value == 5) && (this.value == 5)) {
 obj.setValue(25); // <= IllegalArgumentException
 }
 }
}


If you write your class this way, you generate a lot of overhead due to the call to the setter. However, you also reap the benefits of having your setters work and your debugging time massively reduced. The bug in the above class should take any competent programmer about 10 seconds to fix once he sees the RuntimeException. Directly setting member variables of a different instance is a problem waiting to happen and should be avoided. The compiler won't stop you from doing it—then again, a gun won't stop you from shooting yourself in the foot either. In fact, I wouldn't even advise setting properties directly in the same instance. Properties that use setters and getters are special little creatures with their own needs. Many of the setters perform logic checks or do other tasks that you may miss if you set them directly in other utility methods. (See Example 1-11.)

Example 1-11. A bean that uses friendship to bypass setters
package oracle.hcj.review;
public class FriendBean extends MutableObject {
 private int value;
 public FriendBean( ) {
 super( );
 }
 public void setValue(final int value) {
 if (value > 10) {
 throw new IllegalArgumentException( );
 }
 int oldValue = this.value;
 this.value = value;
 propertyChangeSupport.firePropertyChange("value", oldValue, value);
 }
 public int getValue( ) {
 return value;
 }
 public void someMethod( ) {
 if (Calendar.getInstance( ).get(Calendar.DAY_OF_WEEK) ==
 Calendar.THURSDAY) {
 this.value = 25;
 }
 }
}


One problem with this code is that someMethod( ) sets the property value directly while bypassing the setter and the property change event. If any GUI objects are registered as property change listeners, they won't know about the change and will display stale data. To fix this, you could potentially alter the method so that the method fires the property change event. This will work, but, of course, you will have to replicate all of the other logic in the setter method whenever you set the value in a utility method. To make matters worse, whenever you add new logic to the setter, you must also remember to add the same logic to someMethod( ). This would be extremely bad practice in object-oriented, or even procedural, software engineering. If you use the setter for the property, as the variation below shows, your life will be much easier:

package oracle.hcj.review;
public class FriendBean extends MutableObject {
 private int value;
 public FriendBean( ) {
 super( );
 }
 public void setValue(final int value) {
 if (value > 10) {
 throw new IllegalArgumentException( );
 }
 int oldValue = this.value;
 this.value = value;
 propertyChangeSupport.firePropertyChange("value", oldValue, value);
 }
 public int getValue( ) {
 return value;
 }
 public void someBetterMethod( ) {
 if (Calendar.getInstance( ).get(Calendar.DAY_OF_WEEK) ==
 Calendar.THURSDAY) {
 this.setValue(25);
 }
 } }


In this variation of your class, you reuse the functionality of the setter by calling the setter whenever you want to change the property's value. This approach is far easier to maintain and to change if the need arises; you have to change only the setter and not any other code. Overall, you should not be setting the value of properties, even in the same instance, without using the setters in the class. In fact, if a property has a setter, that setter should be the only thing that ever alters that property. In addition to preventing possible bugs, this technique implements proper encapsulation.

      
Comments