Main Page

Previous Next

Controlling Access to Class Members

We have not yet discussed in any detail how accessible class members are outside a class. You know that from inside a static class method you can refer to any of the static members of the class, and a non-static method can refer to any member of the class. The degree to which variables and methods within one class are accessible from other classes is more complicated. It depends on what access attributes you have specified for the members of a class, and whether the classes are in the same package. This is why we had to understand packages first.

Using Access Attributes

Let's start by considering classes in the same package. Within a given package, any class has direct access to any other class name – for declaring variables or specifying method parameter types, for example – but the variables and methods that are members of that other class are not necessarily accessible. The accessibility of these is controlled by access attributes. You have four possibilities when specifying an access attribute for a class member, including what we have used in our examples so far – that is, not to specify anything at all – and each possibility has a different effect overall. The options you have for specifying the accessibility of a variable or a method in a class are:

Attribute

Permitted access

No access attribute

From methods in any class in the same package.

public

From methods in any class anywhere.

private

Only accessible from methods inside the class. No access from outside the class at all.

protected

From methods in any class in the same package and from any sub-class anywhere.

The table shows you how the access attributes you set for a class member determine the parts of the Java environment from which you can access it. We will discuss sub-classes in the next chapter, so don't worry about these for the moment. We will be coming back to how and when you use the protected attribute then. Note that public, private, and protected are all keywords. Specifying a member as public makes it completely accessible, and at the other extreme, making it private restricts access to members of the same class.

This may sound more complicated than it actually is. Look at the next diagram, which shows the access allowed between classes within the same package.

Click To expand

Within a package such as package1, only the private members of the class Class1 can't be directly accessed by a method in another class in the same package. Declaring a class member to be private limits its availability solely to methods in the same class.

We saw earlier that a class definition must have an access attribute of public if it is to be accessible from outside the package. The next diagram shows the situation where the classes, seeking access to the members of a public class, are in different packages.

Click To expand

Here access is more restricted. The only members of Class1 that can be accessed from an ordinary class, Class2, in another package are those specified as public. Keep in mind that the class, Class1, must also have been defined with the attribute public. From a sub-class of Class1 that is in another package, the members of Class1, without an access attribute, cannot be reached, and neither can the private members – these can never be accessed externally under any circumstances.

Specifying Access Attributes

As you probably gathered from the diagrams that we just looked at, to specify an access attribute for a class member, you just add the keyword to the beginning of the declaration. Here is the Point class you saw earlier, but now with access attributes defined for its members:

Try It Out – Accessing the Point Class

Make the following changes to your Point class. If you save it in a new directory, do make sure Line.java is copied there as well. It will be useful later if they are in a directory with the name Geometry.

public class Point {
  // Create a point from its coordinates
  public Point(double xVal, double yVal) {
    x = xVal;
    y = yVal;
  }

  // Create a Point from an existing Point object
  public Point(final Point aPoint) {
    x = aPoint.x;
    y = aPoint.y;
  }
    // Move a point
  public void move(double xDelta, double yDelta) {
    // Parameter values are increments to the current coordinates
    x += xDelta;
    y += yDelta;
  }

  // Calculate the distance to another point
  public double distance(final Point aPoint) {
    return Math.sqrt((x – aPoint.x)*(x – aPoint.x)+(y – aPoint.y)*(y – aPoint.y));
  }

  // Convert a point to a string 
  public String toString() {
    return Double.toString(x) + ", " + y;    // As "x, y"
  }

  // Coordinates of the point
  private double x;
  private double y;
}

The members have been re-sequenced within the class with the private members appearing last. You should maintain a consistent ordering of class members according to their access attributes, as it makes the code easier to follow. The ordering adopted most frequently is for the most accessible members to appear first, and the least accessible last, but a consistent order is more important than the particular order you choose.

How It Works

Now the instance variables x and y cannot be accessed or modified from outside the class as they are private. The only way these can be set or modified is through methods within the class, either with constructors, or the move() method. If it is necessary to obtain the values of x and y from outside the class, as it might well be in this case, a simple function would do the trick. For example:

public double getX() {
  return x;  
}

Couldn't be easier really, could it? This makes x freely available, but prevents modification of its value from outside the class. In general, such methods are referred to as accessor methods, and usually have the form getXXX(). Methods that allow a private data member to be changed are called mutator methods, and are typically of the form setXXX() where a new value is passed as an argument. For example:

public void setX(double inputX) {
  x = inputX;  
}

It may seem odd to use a method to alter the value of a private data member when you could just make it public. The main advantage of using a method in this way is that you can apply validity checks on the new value that is to be set.

Choosing Access Attributes

As you can see from the table of access attributes, all the classes we have defined so far have had members that are freely accessible within the same package. This applies both to the methods and the variables that were defined in the classes. This is not good object-oriented programming practice. As we said in Chapter 1, one of the ideas behind objects is to keep the data members encapsulated so they cannot be modified by all and sundry, even from other classes within the same package. On the other hand, the methods in your classes generally need to be accessible. They provide the outside interface to the class and define the set of operations that are possible with objects of the class. Therefore in the majority of situations with simple classes (i.e. no sub-classes), you should be explicitly specifying your class members as either public or private, rather than omitting the access attributes.

Broadly, unless you have good reasons for declaring them otherwise, the variables in a public class should be private and the methods that will be called from outside the class should be public. Even where access to the values of the variables from outside a class is necessary, you don't need to make them public or leave them without an access attribute. As we've just seen, you can provide access quite easily by adding a simple public method to return the value of a data member.

Of course, there are always exceptions:

  • For classes in a package that are not public, and therefore not accessible outside the package, it may sometimes be convenient to allow other classes in the package direct access to the data members.

  • If you have data members that have been specified as final so that their values are fixed, and they are likely to be useful outside the class, you might as well declare them to be public.

  • You may well have methods in a class that are only intended to be used internally by other methods in the class. In this case you should specify these as private.

  • In a class like the standard class, Math, which is just a convenient container for utility functions and standard data values, you will want to make everything public.

All of this applies to simple classes. We will see in the next chapter, when we will be looking at sub-classes, that there are some further aspects of class structure that you must take into account.

Using a Package and Access Attributes

Let's put together an example that uses a package that we will create. We could put the Point and Line classes that we defined earlier in a package we could call Geometry. We can then write a program that will import these classes and test them.

Try It Out – Packaging Up the Line and Point Classes

The source and .class files for each class in the package must be in a directory with the name Geometry. Remember that you need to ensure the path to the directory (or directories if you are storing .class files separately) Geometry appears in the CLASSPATH environment variable setting before you try compile or use either of these two classes. You can do this by specifying the -classpath option when you run the compiler or the interpreter.

To include the class Point in the package, the code in Point.java will be:

package Geometry;

public class Point {

  // Create a point from its coordinates
  public Point(double xVal, double yVal) {
    x = xVal;
    y = yVal;
  }

  // Create a Point from an existing Point object
  public Point(final Point aPoint) {
    x = aPoint.x;
    y = aPoint.y;
  }

    // Move a point
  public void move(double xDelta, double yDelta) {
    // Parameter values are increments to the current coordinates
    x += xDelta;
    y += yDelta;
  }

  // Calculate the distance to another point
  public double distance(final Point aPoint) {
    return Math.sqrt((x – aPoint.x)*(x – aPoint.x)+(y – aPoint.y)*(y – aPoint.y));
  }

  // Convert a point to a string 
  public String toString() {
    return Double.toString(x) + ", " + y;    // As "x, y"
  }

  // Retrieve the x coordinate
  public double getX() {
    return x;  
  }

  // Retrieve the y coordinate
  public double getY() {
    return y;  
  }

  // Set the x coordinate
  public void setX(double inputX) {
    x = inputX;  
  }

  // Set the y coordinate
  public void setY(double inputY) {
    y = inputY;  
  }

  // Coordinates of the point
  private double x;
  private double y;
}

Note that we have added the getX(), getY(), setX() and setY() methods to the class to make the private data members accessible.

The Line class also needs to be amended to make the methods public and to declare the class as public. We also need to change its intersects() method so that it can access the private data members of Point objects using the set...() and get...() methods in the Point class. The code in Line.java, with changes highlighted, will be:

package Geometry;

public class Line {

  // Create a line from two points
  public Line(final Point start, final Point end) {
    this.start = new Point(start);
    this.end = new Point(end);
  }

  // Create a line from two coordinate pairs
  public Line(double xStart, double yStart, double xEnd, double yEnd) {
    start = new Point(xStart, yStart);       // Create the start point
    end = new Point(xEnd, yEnd);             // Create the end point
  }

  // Calculate the length of a line
  public double length() {
    return start.distance(end);            // Use the method from the Point class
  }

  // Return a point as the intersection of two lines -- called from a Line object
  public Point intersects(final Line line1) {

    Point localPoint = new Point(0, 0);

    double num =(this.end.getY() – this.start.getY()) 
              * (this.start.getX()–line1.start.getX()) 
              - (this.end.getX() – this.start.getX())
              * (this.start.getY() – line1.start.getY());

    double denom = (this.end.getY() – this.start.getY()) 
                 * (line1.end.getX() – line1.start.getX()) 
                 - (this.end.getX() – this.start.getX())
                 * (line1.end.getY() – line1.start.getY());

    localPoint.setX(line1.start.getX() + (line1.end.getX() – 
line1.start.getX())*num/denom);
localPoint.setY(line1.start.getY() + (line1.end.getY() –
line1.start.getY())*num/denom);
return localPoint; } // Convert a line to a string public String toString() { return "(" + start+ "):(" + end + ")"; // As "(start):(end)" } // that is, "(x1, y1):(x2, y2)" // Data members Point start; // Start point of line Point end; // End point of line }

Here we have left the data members without an access attribute, so they are accessible from the Point class, but not from classes outside the Geometry package.

How It Works

The package statement at the beginning of each source file defines the package to which the class belongs. Remember, you still have to save it in the correct directory, Geometry. Without the public attribute, the classes would not be available to classes outside the Geometry package.

Since we have declared the data members in the class Point as private, they will not be accessible directly. We have added the methods getX(), getY(), setX(), and setY() to the Point class to make the values accessible to any class that needs them.

The Line class hasn't been updated since our first example, so we first have to sort out the access attributes. The two instance variables are declared as before, without any access attribute, so they can be accessed from within the package but not from classes outside the package. This is an instance where exposing the data members within the package is very convenient, and we can do it without exposing the data members to any classes using the package. And we have updated the intersects() method to reflect the changes in accessibility made to the members of the Point class.

We can now write the program that is going to import and use the package that we have just created.

Try It Out – Testing the Geometry Package

We can create a succession of points, and a line joining each pair of successive points in the sequence, and then calculate the total line length.

import Geometry.*;    // Import the Point and Line classes

public class TryPackage {
  public static void main(String[] args) {
    double[][] coords = { {1.0, 0.0}, {6.0, 0.0}, {6.0, 10.0},
                          {10.0,10.0}, {10.0, -14.0}, {8.0, -14.0}};
    // Create an array of points and fill it with Point objects
    Point[] points = new Point[coords.length]; 
    for(int i = 0; i < coords.length; i++)
      points[i] = new Point(coords[i][0],coords[i][1]);

    // Create an array of lines and fill it using Point pairs
    Line[] lines = new Line[points.length – 1]; 
    double totalLength = 0.0;           // Store total line length here
    for(int i = 0; i < points.length – 1; i++) {
      lines[i] = new Line(points[i], points[i+1]); // Create a Line
      totalLength += lines[i].length();            // Add its length
      System.out.println("Line "+(i+1)+' ' +lines[i] + 
                         "  Length is " + lines[i].length());
    }
    // Output the total length
    System.out.println("\nTotal line length = " + totalLength);
  }
}

You should save this as TryPackage.java in the directory TryPackage. If the path to your Geometry directory on a PC running Windows is C:\Packages\Geometry, you can compile this with the command:

javac –classpath ".;C:\Packages" TryPackage.java

This assumes the current directory contains the TryPackage.java file. The -classpath option specifies two paths separated by a semi-colon. The first path, specified by a period, is the current directory. This is necessary to enable the TryPackage.java source file to be found. The second path is C:\Packages, which is the directory containing our Geometry package. Without this the compiler will not be able to find the classes in the Geometry package and the compilation will fail.

Once you have a successful compilation, you can execute the program with the command:

java –classpath ".;C:\Packages" TryPackage

When the program executes, you should see the following output:

Line 1 (1.0, 0.0):(6.0, 0.0)   Length is 5.0
Line 2 (6.0, 0.0):(6.0, 10.0)   Length is 10.0
Line 3 (6.0, 10.0):(10.0, 10.0)   Length is 4.0
Line 4 (10.0, 10.0):(10.0, -14.0)   Length is 24.0
Line 5 (10.0, -14.0):(8.0, -14.0)   Length is 2.0

Total line length = 45.0

How It Works

This example is a handy review of how you can define arrays, and also shows that you can declare an array of objects in the same way as you declare an array of one of the basic types. The dimensions of the array of arrays, coords, are determined by the initial values that are specified between the braces. The number of values within the outer braces determines the first dimension. Each of the elements in the array is itself an array of length two, with each pair of element values being enclosed within their own braces.

Since there are six sets of these, we have an array of six elements, each of which is itself an array of two elements. Each of these elements correspond to the (x, y) coordinates of a point.

Note 

You can see from this that, if necessary, you can create an array of arrays with each row having a different number of elements. The number of initializing values that appear, so they could all be different in the most general case, determines the length of each row.

We declare an array of Point objects with the same length as the number of (x, y) pairs in the coords array. This array is filled with Point objects in the for loop, which we created using the pairs of coordinate values from the coords array.

Since each pair of Point objects will define a Line object, we need one less element in the array lines than we have in the points array. We create the elements of the lines array in the second for loop using successive Point objects, and accumulate the total length of all the line segments by adding the length of each Line object to totalLength as it is created. On each iteration of the for loop, we output the details of the current line. Finally, we output the value of totalLength, which in this case is 45.

Note that the import statement adds the classes from the package Geometry to our program. These classes can be added to any application using the same import statement. You might like to try putting the classes in the Geometry package in a JAR file and try it out as an extension. Let's look at one other aspect of generating your own packages – compiling just the classes in the package without any program that makes use of them. We can demonstrate how this can be done on our Geometry package if you delete the Line.class and Point.class files from the package directory.

First make the directory, C:\TryPackage, that contains the package directory, current. Now you can compile just the classes in the Geometry package with the command:

javac -classpath "C:\TryPackage" Geometry/*.java

This will compile both the Line and Point classes so you should see the .class files restored in the Geometry directory. The files to be compiled are specified relative to the current directory as Geometry/*.java. Under Microsoft Windows this could equally well be Geometry\*.java. This specifies all files in the Geometry subdirectory to the current directory. The classpath must contain the path to the package directory, otherwise the compiler will not be able to find the package. We have defined it here using the -classpath option. We have not specified the current directory in classpath since we do not have any files there that need to be compiled. If we had included it in classpath it would not have made any difference – the classes in the Geometry package would compile just the same.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor