3.6. 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., 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, 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.Other reasons to hide fields and methods of a class include:
- 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.
3.6.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 a 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
access
control modifiers ; they
specify the access rules for the field or
method.
3.6.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.6.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.6.1.3 Access to members
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. This
default level of access is often called package access . 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:
public class Laundromat { // People can use this class.These access rules apply to members of a 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.
protected int temperature; // A subclass might want to tweak this field
}
- 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 class A declares a
protected field x and is
extended by a class B, which is defined in a
different package (this last point is important). Class
B inherits the protected field
x, and its code can access that field in the
current instance of B or in any other instances of
B that the code can refer to. This does not mean,
however, that the code of class B can start
reading the protected fields of arbitrary instances of
A! If an object is an instance of
A but is not an instance of B,
its fields are obviously not inherited by B, and
the code of class B cannot read them.
3.6.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. Earlier in this section we said 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, we must 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.
3.6.1.5 Member access summary
Table 3-1 summarizes the member access
rules.
Member visibility | ||||
---|---|---|---|---|
Accessible to | Public | Protected | Package | Private |
Defining 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 |
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 backward-compatible change and can break code
that relies on access to those members.
3.6.2. Data Accessor Methods
In the Circle example,
we 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 classWe have defined the Circle class within a package
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 void 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; }
}
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 a common convention 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 7), 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).