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:
Internal fields and methods that are visible outside the class just clutter up the API. Keeping visible fields to a minimum keeps your class tidy and therefore easier to use and understand.
If a field or method is visible to the users of your class, you have to document it. Save yourself time and effort by hiding it instead.
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.
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.
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.
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:
If a member of a class is declared with the public modifier, it means that the member is accessible anywhere the containing class is accessible. This is the least restrictive type of access control.
If a member of a class is declared private, the member is never accessible, except within the class itself. This is the most restrictive type of access control.
If a member of a class is declared protected, it is accessible to all classes within the package (the same as the default package accessibility) and also accessible within the body of any subclass of the class, regardless of the package in which that subclass is defined. This is more restrictive than public access, but less restrictive than package access.
If a member of a class is not declared with any of these modifiers, it has the default package access: it is accessible to code within all classes that are defined in the same package, but inaccessible outside of the package.
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.
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:
A class inherits all instance fields and instance methods (but not constructors) of its superclass.
The body of a class can always access all the fields and methods it declares itself. It can also access the accessible fields and members it inherits from its superclass.
Table 3-1 summarizes the member access rules.
Here are some simple rules of thumb for using visibility modifiers:
Use public only for methods and constants that form part of the public API of the class. Certain important or frequently used fields can also be public, but it is common practice to make fields non-public and encapsulate them with public accessor methods.
Use protected for fields and methods that aren't required by most programmers using the class, but that may be of interest to anyone creating a subclass as part of a different package. Note that protected members are technically part of the exported API of a class. They should be documented and cannot be changed without potentially breaking code that relies on them.
Use the default package visibility for fields and methods that are internal implementation details, but are used by cooperating classes in the same package. You cannot take real advantage of package visibility unless you use the package directive to group your cooperating classes into a package.
Use private for fields and methods that are used only inside the class and should be hidden everywhere else.
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.
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.
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).
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |