Полезная информация

Java in a Nutshell

Previous Chapter 3
Classes and Objects in Java
Next
 

3.8 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 that we haven't discussed so far is hiding the data within the class, and making it available only through the methods. This technique is often known as encapsulation, because it seals the class's 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, then 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 variables that are interdependent and must be in a consistent state. If you allow a programmer (this may be you yourself) to manipulate those variables directly, she may change one variable without changing important related variables, thus leaving the class in an inconsistent state. If, instead, she had to call a method to change the variable, that method can be sure to do everything necessary to keep the state consistent.

Here's another way to think about encapsulation: When all of a class's variables are hidden, the class's 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 variables can be directly manipulated, the number of possibilities you have to test becomes unmanageable.

There are other reasons to hide data, too:

Visibility Modifiers

In most of our examples so far, you've probably noticed the public keyword being used. When applied to a class, it means that the class is visible everywhere. When applied to a method or variable, it means that the method or variable is visible everywhere.

To hide variables (or methods, for that matter) you just have to declare them private:

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

A private field of a class is visible only in methods defined within that class. (Or, in Java 1.1, in classes defined within the class.) Similarly, a private method may only be invoked by methods within the class (or methods of classes within the class). Private members are not visible within subclasses, and are not inherited by subclasses as other members are. [11] Of course, non-private methods that invoke private methods internally are inherited and may be invoked by subclasses.

[11] Every object does, of course, have its own copy of all fields of all superclasses, including the private fields. The methods defined by the object can never refer to or use the private fields of superclasses, however, and so we say that those fields are not inherited.

Besides public and private, Java has two other visibility levels: protected and the default visibility level, "package visibility," which applies if none of the public, private, and protected keywords are used.

A protected member of a class is visible within the class where it is defined, of course, and within all subclasses of the class, and also within all classes that are in the same package as that class. You should use protected visibility when you want to hide fields and methods from code that uses your class, but want those fields and methods to be fully accessible to code that extends your class.

The default package visibility is more restrictive than protected, but less restrictive than private. If a class member is not declared with any of the public, private, or protected keywords, then it is visible only within the class that defines it and within classes that are part of the same package. It is not visible to subclasses unless those subclasses are part of the same package.

A note about packages: A package is a group of related and possibly cooperating classes. All non-private members of all classes in the package are visible to all other classes in the package. This is okay because the classes are assumed to know about, and trust, each other. [12] The only time difficulty arises is when you write programs without a package statement. These classes are thrown into a default package with other package-less classes, and all their non-private members are visible throughout the package. (The default package usually consists of all classes in the current working directory.)

[12] If you're a C++ programmer, you can say that classes within the same package are friend-ly to each other.

There is an important point to make about subclass access to protected members. A subclass inherits the protected members of its superclass, but it can only access those members through instances of itself, not directly in instances of the superclass. Suppose, for example, that A, B, and C are public classes, each defined in a different package, and that a, b, and c are instances of those classes. Let B be a subclass of A, and C be a subclass of B. Now, if A has a protected field x, then the class B inherits that field, and its methods can use this.x, b.x and c.x. But it cannot access a.x. Similarly, if A has a protected method f(), then the methods of class B can invoke this.f(), b.f(), and c.f(), but they cannot invoke a.f().

Table 3.1 shows the circumstances under which class members of the various visibility types are accessible to other classes.

Table 3.1: Class Member Accessibility
Accessible to: public protected package private
Same class yes yes yes yes
Class in same package yes yes yes no
Subclass in different package yes yes no no
Non-subclass, different package yes no no no

The details of member visibility in Java can become quite confusing. Here are some simple rules of thumb for using visibility modifiers:

Note that you can't take advantage of package visibility unless you use the package statement to group your related classes into packages. See Chapter 13, Java Syntax, Java Syntax, for a table that summarizes the Java visibility modifiers and other modifiers.

Data Access Methods

In the Circle example we've been using, we've declared the circle position and radius to be public fields. In fact, the Circle class is one where it may well make sense to keep those visible--it is a simple enough class, with no dependencies between the variables.

On the other hand, suppose we wanted to impose a maximum radius on objects of the Circle class. Then it would be better to hide the r variable so that it could not be set directly. Instead of a visible r variable, we'd implement a setRadius() method that verifies that the specified radius isn't too large and then sets the r variable internally. Example 3.14 shows how we might implement Circle with encapsulated data and a restriction on radius size. For convenience, we use protected fields for the radius and position variables. This means that subclasses of Circle, or cooperating classes within the shapes package are able to access these variables directly. To any other classes, however, the variables are hidden. Also, note the private constant and method used to check whether a specified radius is legal. And finally, notice the public methods that allow you to set and query the values of the instance variables.

Example 3.14: Hiding Variables in the Circle Class

package shapes;             // Specify a package for the class.
public class Circle {       // Note that the class is still public!
    protected double x, y;  // Position is hidden, but visible to subclasses.
    protected double r;     // Radius is hidden, but visible to subclasses.
    private static final double MAXR = 100.0;    // Maximum radius (constant).
    private boolean check_radius(double r) { return (r <= MAXR); }
    // Public constructors
    public Circle(double x, double y, double r) {
        this.x = x; this.y = y; 
        if (check_radius(r)) this.r = r;
        else this.r = MAXR;
    }
    public Circle(double r) { this(0.0, 0.0, r); }
    public Circle() { this(0.0, 0.0, 1.0); }
    // Public data access methods
    public void moveto(double x, double y) { this.x = x; this.y = y;}
    public void move(double dx, double dy) { x += dx;  y += dy; }
    public void setRadius(double r) { this.r = (check_radius(r))?r:MAXR; }
    // Declare these trivial methods final so we don't get dynamic
    // method lookup and so that they can be inlined by the compiler.
    public final double getX() { return x; };
    public final double getY() { return y; };
    public final double getRadius() { return r; };
}


Previous Home Next
Overriding Methods Book Index Abstract Classes and Interfaces

Java in a Nutshell Java Language Reference Java AWT Java Fundamental Classes Exploring Java