Access


Java's access control is based on the idea that certain features of a class should not be usable by other classes. Before you learn the details of access control, let's look at why this idea is sound.

One of the fundamental concepts of object-oriented programming is data hiding. This is the practice of making a class's data as inaccessible as possible to other classes. Why would this be beneficial?

Often there are many valid ways to represent information. A temperature might be stored as degrees Kelvin, Celsius, or Fahrenheit. A price might be listed in various currencies. A color might be represented as a name, as red/green/blue levels, as red/yellow/blue levels, or as hue/ saturation/brightness levels. Maintenance considerations might force class code to be rewritten so as to change the internal representation. For example, if an Italian company bought a company in the United States, money representation might be converted from dollars to euros, and temperature representation might be converted from Fahrenheit to Celsius.

Even if data representation does not change, it makes sense to localize the code that knows about representation inside a single class. It is wasteful to make all classes know how data is represented internally, and it creates the risk of bugs (if the other classes misinterpret the internal representation).

Imagine a class called Thermometer, which somehow reads a physical thermometer device. A very clean design is to give the class a getTempCelsius() method. The method name leaves no room for confusion as to the units of the return value. There could also be getTempFahrenheit() and getTempKelvin() methods, so that nobody ever has to look up the conversion formulas. Moreover, nobody ever needs to know how temperature is represented within the class. It might be Fahrenheit, Celsius, or Kelvin. It might change from one rev of the class software to another. The benefit to those of us who use the Thermometer class is enormous: We never have to worry about the internal representation.

The general principle of data hiding is that an object's data should never be accessed directly from outside the object. Instead, the object's class should provide methods for reading and setting the data. These methods are officially called accessors and mutators, but they are often called by their nicknames: getters and setters. An accessor/getter has an empty argument list and returns a data value. A mutator/setter has a void return type and a single argument. By common convention, the name of an accessor method begins with get, followed by the property to be retrieved. The name of a mutator begins with set, followed by the property to be modified.

To support this data-hiding approach, object-oriented languages provide facilities to let you restrict access to a class's data and methods. In Java, this is done with access modifier keywords. Java has three access modifiers:

  • public

  • private

  • protected

These keywords appear before the declarations of the data or methods they apply to. The public modifier may also appear before a class definition. Before we define what the various access modes mean, let's look at an example to clarify the syntax:

public class AccessExample {   public int              x;   private double          d;   protected static float  f;   char                    c;   public int getX()   {     return x;   }   private void printC()   {     System.out.println("c = " + c);   }   protected void setD(double newD)   {     d = newD;   }   void bumpX()   {     x++;   } }

Access modifiers cannot apply to data defined within a method. (Since such data ceases to exist after the method returns, we don't need to think about which outside classes may use it.) Notice the declaration of f, which is both protected and static. Access modifiers can be freely combined with non-access modifiers such as static. Modifier order is unrestricted, so you could equivalently say static protected float f;. For the sake of readability, it's good practice to align the first character of your variable names on a tab stop, as the preceding example shows.

Java has three access modifier keywords, but four access modes. The fourth access mode is what you get if you don't specify public, private, or protected. This fourth mode is called default, although you might also sometimes see it called package or friendly. In the preceding example, variable c and method bumpX() both have default access.

Now let's look at what the different access modes do.

Public Access

Public access is completely unrestricted. Classes, data, and methods can be designated public. A public class can be used by any other class. Public data can be read and written by any code (violating the spirit of data hiding). Public methods can be called from any code.

When you run an application, the Java Virtual Machine creates a class loader, which loads your application class. The class loader is itself a class, and your application class must be public so that the loader can load it.

Private Access

The most restrictive access mode is private. Data and methods can be designated private, but not classes. (There is a kind of class called an inner class that can be private or protected, but inner classes are beyond the scope of this book.) A private variable can be written or read only by an instance of the class that defines the variable. A private method can be called only by an instance of the class that defines the variable.

Private access may not be quite as private as you expect. Let's look at an example:

 1. public class Employee  2. {  3.   private float salary;  4.  5.   public float getSalary()  6.   { 7.     return salary;  8.   }  9. 10.   public void setSalary(float newSalary) 11.   { 12.     salary = newSalary; 13.   } 14. 15.   public boolean earnsMoreThan(Employee other) 16.   { 17.     if (salary > other.salary) 18.       return true; 19.     else 20.       return false; 21.   } 22. 23.   . . . 24. }

On line 3, salary is declared private. This makes sense, because one's salary should be kept private. The getSalary() and setSalary() methods are in the spirit of data hiding. Public methods get and set private data, and there are no surprises. But look at line 17, where the code compares the current employee object's salary to the salary of a different employee. Any object that executes line 17 reads a private variable of a different object.

That's just how private access works. Any instance of Employee can read and write not just its own private data, but the private data of any instance. Similarly, any instance of Employee can call any private method on any instance of Employee.

Default Access

Default access is all about packages. Fortunately, you have just learned all about packages, so you're in great shape to learn about default access.

Default access does not correspond to a modifier keyword. Instead, it's the access mode you get when you don't mark a class, variable, or method as public, private, or protected. A default-access class can be used by any instance of any class that's in the same package. A default-access variable can be read or written only by an instance of a class in the same package as the class that owns the default-access variable. A default-access method can be called only by an instance of a class in the same package as the class that owns the default-access variable. In other words, anything with default access can be used by anything in the same package, and it cannot be used from outside the package.

Default access is useful in this common situation: You sell a package of classes that solve a particular problem. A few of the classes are for direct use by your customers. These are public and thoroughly documented (you'll see how this is done in Chapter 11). The rest of the classes in the package perform purely secondary roles. They are never used directly by your customers, and are used only by the public classes to help in their internal workings.

There is no need for your customers to know about these secondary classes. In fact, everybody is better off if nobody but you knows about them. This is true not just for classes, but for data and methods as well. Some classes, data, and methods are part of your package's publicly visible interface, while others are nobody's business but your own.

We can draw a parallel between private features in a class and default-access features in a package. In a class, private data and methods are only for the internal working of the class. In a package, default-access classes, data, and methods are only for the internal working of the package.

You have already seen packages with default-access features, although you may not have realized it. When a JVM is executed, it builds a package called the unnamed package. This consists of all classes in the current working directory that do not explicitly contain package declarations. Consider the example classes you used in the previous chapter: Employee and its subclasses Worker, Manager, and so on. Those classes didn't use packages, so the obvious way to proceed would be to put them all in the same directory, and to compile with a command like javac *.java. Eventually, all the class files would exist in the current working directory. Assuming one of those classes had a main() method, a JVM could run that application. Then all the classes would be considered to be in the no-name package. None of the data or methods in those classes had access modifiers (since access modifiers weren't introduced until this chapter), so they got default access invisibly. And everything could work smoothly. Any instance of any class could use any data, and call any method, of any instance of any class.

The no-name package allows your code organization to evolve as your understanding becomes more sophisticated. At first, you don't know about packages or access. Related source files live together in a directory, along with the corresponding class files. Everything can access everything. Later on, you learn about packages and access. You have several reasonable choices for organizing your source code. All source files can be together in the same directory, or the source can be organized into subdirectories that reflect the package structure, or you can use some other scheme. No matter how you organize your source code, your class files are organized automatically (by the compiler, when you use the -d option) into directories that reflect the package structure.

If you are developing code that involves more than just a few classes, it's a good idea to use packages. For smaller projects, it's fine to use a single directory and take advantage of the no-name package. In this book, the code examples are as simple as possible. Example classes have package declarations only when using a package is relevant to the topic of the example.

Protected Access

Protected access is default access plus a little bit more. Only data and methods may be protected; classes may not. (Actually, inner classes may be protected, but they are beyond the scope of this book.)

Protected access is useful in a certain interesting situation. You already saw that default access comes into play when you are sharing a package of interrelated classes, some of which will be directly used by your customers. Protected access comes into play when you are sharing a package of interrelated classes, some of which will be subclassed by your customers.

It makes sense for your customers to leave your package intact. You certainly don't want dozens of different evolutions of your package out there in the world, one evolution per customer. It is cleaner for everyone if the various subclasses created by your various customers are in separate packages. But the subclasses might want access to non-public data or methods of their superclasses. Even if those desirable variables and methods had default access, they still wouldn't be useful because they would be in a different package. So protected access grants access to subclasses of the class that owns the protected features, even if the subclasses live in different packages.

A protected method may be overridden in any subclass of the class that owns the method, even if the subclass is in a different package. A protected method may be called by any instance of any subclass of the class that owns the method, even if the subclass is in a different package.

Protected data is more complicated than protected methods. If a variable is protected, it is not accessible by just any instance of any other-package subclass. It can be accessed only by the instance of the other-package subclass that owns the data.

Let's look at some simple examples. Here's a superclass:

package mystuff; public class Fish {   protected float weight; }

Here's a subclass in a different package that makes appropriate use of the protected variable:

import mystuff.Fish; package yourstuff;  // Different package! public class Tuna extends Fish {   void printWeight()   {     System.out.println("I weigh: " + weight);   } } 

The code is legal because any instance of Tuna that executes printWeight() is accessing its own version of the protected variable weight.

Now here's an example that won't compile, to show you what protected access does not mean:

Import mystuff.Fish; package yourstuff;  // Different package! public class Tuna extends Fish {   void printSomeonesWeight(Fish someone)   {     System.out.println("Someone weighs: " +                         someone.weight);   } }

This code is illegal, because protected access doesn't mean that any Tuna may access any other Tuna's protected data. Protected access is different from private access in this regard.

Bear in mind that this restrictive meaning of "protected" only matters in a different-package subclass. The following code is perfectly legal:

package mystuff;  // Same package as superclass public class Snapper extends Fish {   void printSomeonesWeight(Fish someone)   {     System.out.println("Someone weighs: " +                         someone.weight);   } }

Here the situation is different, because the subclass and the superclass are in the same package. Remember that protected access is default access plus a little more. Even if weight were default instead of protected, it could be accessed by any class in the same package, independent of any superclass-subclass relationships.

Access and Overriding

Java has a rule that seems strange at first glance: When you override a method, the subclass's version may not have a more restrictive access mode than the superclass's version. This is shown in Table 9.1:

Table 9.1: Legal Access Modes for Overriding Methods

Superclass Version Access

Subclass Version Access

Public

public

Protected

public, protected

Default

public, protected, default

Private

public, protected, default, private

This rule seems arbitrarily restrictive, but on closer inspection, it is absolutely necessary. The previous chapter discussed polymorphism, and you saw what a powerful tool it can be. It turns out that polymorphism is only possible if you have the access/overriding rule. Let's see why this is.

Chapter 8 presented the Employee class, which had a printCheck() method. Employee had a subclass called Manager, which had a subclass called Officer. The Officer class overrode printCheck(). You saw that you could have an array, typed as Employee[], that contained references to objects of a variety of classes. Either Employee itself or any of its subclasses were allowed, as shown in Figure 9.6.


Figure 9.6: Polymorphism revisited

You could then traverse the array as follows:

for (int i=0; i<employees.length; i++)   employees[i].printCheck();

Each object would use its own class's version of the printCheck() method. This is especially useful if some subclasses override the method.

The clean polymorphic system breaks down if a subclass is allowed to override a method so that the method's access becomes more restricted. To see why this is so, let's assume that the check-printing loop is in some method in a class called Paymaster. Let's create a new subclass of Employee, called PartTimer:

class PartTimer extends Employee {   private void printCheck()   {       // Whatever   } }

This class won't compile. That's good. If the class were allowed to override printCheck() as shown (making its access more restricted, thus violating the rule), it would not be possible for an instance of Paymaster to call printCheck() on an instance of PartTimer. A private method can be called only by an instance of the owning class, so a PartTimer's printCheck() could be called only by an instance of PartTimer.

Consider what would happen in the absence of this rule. The polymorphic loop in Paymaster would work its way through the array, calling printCheck() on workers, managers, and officers. Eventually, it would need to make an illegal call to printCheck() on a PartTimer. This situation must be avoided, and the designers of Java had several options for preventing it. The no-restrictive-overriding rule is sometimes an inconvenience, but actually it is an excellent solution because it gives priority to clean polymorphism.




Ground-Up Java
Ground-Up Java
ISBN: 0782141900
EAN: 2147483647
Year: 2005
Pages: 157
Authors: Philip Heller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net