Book Home Java Enterprise in a Nutshell Search this book

3.5. Data Hiding and Encapsulation

We started this chapter by describing a class as "a collection of data and methods." One of the important object-oriented techniques we haven't discussed so far is hiding the data within the class and making it available only through the methods. This technique is known as encapsulation because it seals the data (and internal methods) safely inside the "capsule" of the class, where it can be accessed only by trusted users (i.e., by the methods of the class).

Why would you want to do this? The most important reason is to hide the internal implementation details of your class. If you prevent programmers from relying on those details, you can safely modify the implementation without worrying that you will break existing code that uses the class.

Another reason for encapsulation is to protect your class against accidental or willful stupidity. A class often contains a number of interdependent fields that must be in a consistent state. If you allow a programmer (including yourself) to manipulate those fields directly, he may change one field without changing important related fields, thus leaving the class in an inconsistent state. If, instead, he has to call a method to change the field, that method can be sure to do everything necessary to keep the state consistent. Similarly, if a class defines certain methods for internal use only, hiding these methods prevents users of the class from calling them.

Here's another way to think about encapsulation: when all the data for a class is hidden, the methods define the only possible operations that can be performed on objects of that class. Once you have carefully tested and debugged your methods, you can be confident that the class will work as expected. On the other hand, if all the fields of the class can be directly manipulated, the number of possibilities you have to test becomes unmanageable.

There are other reasons to hide fields and methods of a class, as well:

3.5.1. Access Control

All the fields and methods of a class can always be used within the body of the class itself. Java defines access control rules that restrict members of a class from being used outside the class. In an number of examples in this chapter, you've seen the public modifier used in field and method declarations. This public keyword, along with protected and private, are accesscontrolmodifiers ; they specify the access rules for the field or method.

3.5.1.1. Access to packages

A package is always accessible to code defined within the package. Whether it is accessible to code from other packages depends on the way the package is deployed on the host system. When the class files that comprise a package are stored in a directory, for example, a user must have read access to the directory and the files within it in order to have access to the package. Package access is not part of the Java language itself. Access control is usually done at the level of classes and members of classes instead.

3.5.1.2. Access to classes

By default, top-level classes are accessible within the package in which they are defined. However, if a top-level class is declared public, it is accessible everywhere (or everywhere that the package itself is accessible). The reason that we've restricted these statements to top-level classes is that, as we'll see later in this chapter, classes can also be defined as members of other classes. Because these inner classes are members of a class, they obey the member access-control rules.

3.5.1.3. Access to members

As I've already said, the members of a class are always accessible within the body of the class. By default, members are also accessible throughout the package in which the class is defined. This implies that classes placed in the same package should trust each other with their internal implementation details.[6] This default level of access is often called packageaccess. It is only one of four possible levels of access. The other three levels of access are defined by the public, protected, and private modifiers. Here is some example code that uses these modifiers:

[6]C++ programmers might say that all classes within a package are friend-ly to each other.

public class Laundromat {      // People can use this class. 
  private Laundry[] dirty;     // They cannot use this internal field,
  public void wash() { ... }   // but they can use these public methods
  public void dry() { ... }    // to manipulate the internal field. 
}

Here are the access rules that apply to members of a class:

protected access requires a little more elaboration. Suppose that the field r of our Circle class had been declared protected and that our PlaneCircle class had been defined in a different package. In this case, every PlaneCircle object inherits the field r, and the PlaneCircle code can use that field as it currently does. Now suppose that PlaneCircle defines the following method to compare the size of a PlaneCircle object to the size of some other Circle object:

// Return true if this object is bigger than the specified circle
public boolean isBigger(Circle c) {
  return (this.r > c.r);  // If r is protected, c.r is illegal access!
}

In this scenario, this method does not compile. The expression this.r is perfectly legal, since it accesses a protected field inherited by PlaneCircle. Accessing c.r is not legal, however, since it is attempting to access a protected field it does not inherit. To make this method legal, we either have to declare PlaneCircle in the same package as Circle or change the type of the isBigger() parameter to be a PlaneCircle instead of a Circle.

3.5.1.4. Access control and inheritance

The Java specification states that a subclass inherits all the instance fields and instance methods of its superclass accessible to it. If the subclass is defined in the same package as the superclass, it inherits all non-private instance fields and methods. If the subclass is defined in a different package, however, it inherits all protected and public instance fields and methods. private fields and methods are never inherited; neither are class fields or class methods. Finally, constructors are not inherited; they are chained, as described earlier in this chapter.

The statement that a subclass does not inherit the inaccessible fields and methods of its superclass can be a confusing one. It would seem to imply that when you create an instance of a subclass, no memory is allocated for any private fields defined by the superclass. This is not the intent of the statement, however. Every instance of a subclass does, in fact, include a complete instance of the superclass within it, including all inaccessible fields and methods. It is simply a matter of terminology. Because the inaccessible fields cannot be used in the subclass, we say they are not inherited. I stated earlier in this section that the members of a class are always accessible within the body of the class. If this statement is to apply to all members of the class, including inherited members, then we have to define "inherited members" to include only those members that are accessible. If you don't care for this definition, you can think of it this way instead:

3.5.1.5. Member access summary

Table 3-1 summarizes the member access rules.

Table 3-1. Class Member Accessibility

Member Visibility
Accessible to:publicprotectedpackageprivate
Defining classYesYesYesYes
Class in same packageYesYesYesNo
Subclass in different packageYesYesNoNo
Non-subclass different packageYesNoNoNo

Here are some simple rules of thumb for using visibility modifiers:

If you are not sure whether to use protected, package, or private accessibility, it is better to start with overly restrictive member access. You can always relax the access restrictions in future versions of your class, if necessary. Doing the reverse is not a good idea because increasing access restrictions is not a backwards-compatible change.

3.5.2. Data Accessor Methods

In the Circle example we've been using, we've declared the circle radius to be a public field. The Circle class is one in which it may well be reasonable to keep that field publicly accessible; it is a simple enough class, with no dependencies between its fields. On the other hand, our current implementation of the class allows a Circle object to have a negative radius, and circles with negative radii should simply not exist. As long as the radius is stored in a public field, however, any programmer can set the field to any value she wants, no matter how unreasonable. The only solution is to restrict the programmer's direct access to the field and define public methods that provide indirect access to the field. Providing public methods to read and write a field is not the same as making the field itself public. The crucial difference is that methods can perform error checking.

Example 3-4 shows how we might reimplement Circle to prevent circles with negative radii. This version of Circle declares the r field to be protected and defines accessor methods named getRadius() and setRadius() to read and write the field value while enforcing the restriction on negative radius values. Because the r field is protected, it is directly (and more efficiently) accessible to subclasses.

Example 3-4. The Circle Class Using Data Hiding and Encapsulation

package shapes;           // Specify a package for the class

public class Circle {     // The class is still public
  // This is a generally useful constant, so we keep it public
  public static final double PI = 3.14159;

  protected double r;     // Radius is hidden, but visible to subclasses

  // A method to enforce the restriction on the radius
  // This is an implementation detail that may be of interest to subclasses
  protected checkRadius(double radius) { 
    if (radius < 0.0) 
      throw new IllegalArgumentException("radius may not be negative.");
  }

  // The constructor method
  public Circle(double r) {
    checkRadius(r);
    this.r = r;
  }

  // Public data accessor methods
  public double getRadius() { return r; };
  public void setRadius(double r) { 
    checkRadius(r);
    this.r = r;
  }

  // Methods to operate on the instance field
  public double area() { return PI * r * r; }
  public double circumference() { return 2 * PI * r; }
}

We have defined the Circle class within a package named shapes. Since r is protected, any other classes in the shapes package have direct access to that field and can set it however they like. The assumption here is that all classes within the shapes package were written by the same author or a closely cooperating group of authors, and that the classes all trust each other not to abuse their privileged level of access to each other's implementation details.

Finally, the code that enforces the restriction against negative radius values is itself placed within a protected method, checkRadius(). Although users of the Circle class cannot call this method, subclasses of the class can call it and even override it if they want to change the restrictions on the radius.

Note particularly the getRadius() and setRadius() methods of Example 3-4. It is almost universal in Java that data accessor methods begin with the prefixes "get" and "set." If the field being accessed is of type boolean, however, the get() method may be replaced with an equivalent method that begins with "is." For example, the accessor method for a boolean field named readable is typically called isReadable() instead of getReadable(). In the programming conventions of the JavaBeans component model (covered in Chapter 6, "JavaBeans"), a hidden field with one or more data accessor methods whose names begin with "get," "is," or "set" is called a property. An interesting way to study a complex class is to look at the set of properties it defines. Properties are particularly common in the AWT and Swing APIs, which are covered in Java Foundation Classes in a Nutshell (O'Reilly).



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.