Thursday 9 March 2017

Recursion & Introducing Access Control - Java Tutorials

Recursion


Java supports recursion. Recursion is the process of defining something in terms of itself. As it relates to Java programming, recursion is the attribute that allows a method to call itself. A method that calls itself is said to be recursive.

The classic example of recursion is the computation of the factorial of a number. The factorial of a number N is the product of all the whole numbers between 1 and N. For example, 3 factorial is 1 × 2 × 3, or 6. Here is how a factorial can be computed by use of a recursive method:

  // A simple example of recursion.
  class Factorial {
    // this is a recursive function
    int fact(int n) {
      int result;

      if(n==1) return 1;
      result = fact(n-1) * n;
      return result;
    }
  }

  class Recursion {
    public static void main(String args[]) {
      Factorial f = new Factorial();

      System.out.println("Factorial of 3 is " + f.fact(3));
      System.out.println("Factorial of 4 is " + f.fact(4));
      System.out.println("Factorial of 5 is " + f.fact(5));
    }
  }

The output from this program is shown here:

  Factorial of 3 is 6
  Factorial of 4 is 24
  Factorial of 5 is 120

If you are unfamiliar with recursive methods, then the operation of fact( ) may seem a bit confusing. Here is how it works. When fact( ) is called with an argument of 1, the function returns 1; otherwise it returns the product of fact(n–1)*n. To evaluate this expression, fact( ) is called with n–1. This process repeats until n equals 1 and the calls to the method begin returning.

To better understand how the fact( ) method works, let’s go through a short example. When you compute the factorial of 3, the first call to fact( ) will cause a second call to be made with an argument of 2. This invocation will cause fact( ) to be called a third time with an argument of 1. This call will return 1, which is then multiplied by 2 (the value of n in the second invocation). This result (which is 2) is then returned to the original invocation of fact( ) and multiplied by 3 (the original value of n). This yields the answer, 6. You might find it interesting to insert println( ) statements into fact( ) which will show at what level each call is and what the intermediate answers are.

When a method calls itself, new local variables and parameters are allocated storage on the stack, and the method code is executed with these new variables from the start. A recursive call does not make a new copy of the method. Only the arguments are new. As each recursive call returns, the old local variables and parameters are removed from the stack, and execution resumes at the point of the call inside the method. Recursive methods could be said to “telescope” out and back.

Recursive versions of many routines may execute a bit more slowly than the iterative equivalent because of the added overhead of the additional function calls. Many recursive calls to a method could cause a stack overrun. Because storage for parameters and local variables is on the stack and each new call creates a new copy of these variables, it is possible that the stack could be exhausted. If this occurs, the Java run-time system will cause an exception. However, you probably will not have to
worry about this unless a recursive routine runs wild.

The main advantage to recursive methods is that they can be used to create clearer and simpler versions of several algorithms than can their iterative relatives. For example, the QuickSort sorting algorithm is quite difficult to implement in an iterative way. Some problems, especially AI-related ones, seem to lend themselves to recursive solutions. Finally, some people seem to think recursively more easily than iteratively.

When writing recursive methods, you must have an if statement somewhere to force the method to return without the recursive call being executed. If you don’t do this, once you call the method, it will never return. This is a very common error in working with recursion. Use println( ) statements liberally during development so that you can watch what is going on and abort execution if you see that you have made a mistake.

Here is one more example of recursion. The recursive method printArray( ) prints the first i elements in the array values.

  // Another example that uses recursion.

  class RecTest {
    int values[];

    RecTest(int i) {
      values = new int[i];
    }

    // display array -- recursively
    void printArray(int i) {
      if(i==0) return;
      else printArray(i-1);
      System.out.println("[" + (i-1) + "] " + values[i-1]);
    }
  }

  class Recursion2 {
    public static void main(String args[]) {
      RecTest ob = new RecTest(10);
      int i;

      for(i=0; i<10; i++) ob.values[i] = i;

      ob.printArray(10);
    }
  }

This program generates the following output:

  [0] 0
  [1] 1
  [2] 2
  [3] 3
  [4] 4
  [5] 5
  [6] 6
  [7] 7
  [8] 8
  [9] 9





Introducing Access Control


As you know, encapsulation links data with the code that manipulates it. However, encapsulation provides another important attribute: access control. Through encapsulation, you can control what parts of a program can access the members of a class. By controlling access, you can prevent misuse. For example, allowing access to data only through a well-defined set of methods, you can prevent the misuse of that data. Thus, when correctly implemented, a class creates a “black box” which may be used, but the inner workings of which are not open to tampering. However, the classes that were presented earlier do not completely meet this goal. For example, consider the Stack class shown at the end of Chapter 6. While it is true that the methods push( ) and pop( ) do provide a controlled interface to the stack, this interface is not enforced. That is, it is possible for another part of the program to bypass these methods and access the stack directly. Of course, in the wrong hands, this could lead to trouble. In this section you will be introduced to the mechanism by which you can precisely control access to the various members of a class.

How a member can be accessed is determined by the access specifier that modifies its declaration. Java supplies a rich set of access specifiers. Some aspects of access control are related mostly to inheritance or packages. (A package is, essentially, a grouping of classes.) These parts of Java’s access control mechanism will be discussed later. Here, let’s begin by examining access control as it applies to a single class. Once you understand the fundamentals of access control, the rest will be easy.

Java’s access specifiers are public, private, and protected. Java also defines a default access level. protected applies only when inheritance is involved. The other access specifiers are described next.

Let’s begin by defining public and private. When a member of a class is modified by the public specifier, then that member can be accessed by any other code. When a member of a class is specified as private, then that member can only be accessed by other members of its class. Now you can understand why main( ) has always been preceded by the public specifier. It is called by code that is outside the program—that is, by the Java run-time system. When no access specifier is used, then by default the member of a class is public within its own package, but cannot be accessed outside of its package.

In the classes developed so far, all members of a class have used the default access mode, which is essentially public. However, this is not what you will typically want to be the case. Usually, you will want to restrict access to the data members of a class—allowing access only through methods. Also, there will be times when you will want to define methods which are private to a class.

An access specifier precedes the rest of a member’s type specification. That is, it must begin a member’s declaration statement. Here is an example:

  public int i;
  private double j;

  private int myMethod(int a, char b) { // ...

To understand the effects of public and private access, consider the following program:

  /* This program demonstrates the difference between
     public and private.
  */
  class Test {
    int a; // default access
    public int b; // public access
    private int c; // private access

    // methods to access c
    void setc(int i) { // set c's value
      c = i;
    }
    int getc() { // get c's value
      return c;
    }
  }

  class AccessTest {
    public static void main(String args[]) {
      Test ob = new Test();

      // These are OK, a and b may be accessed directly
      ob.a = 10;
      ob.b = 20;

      // This is not OK and will cause an error
  // ob.c = 100; // Error!

      // You must access c through its methods
      ob.setc(100); // OK
      System.out.println("a, b, and c: " + ob.a + " " +
                                 ob.b + " " + ob.getc());
    }
  }

As you can see, inside the Test class, a uses default access, which for this example is the same as specifying public. b is explicitly specified as public. Member c is given private access. This means that it cannot be accessed by code outside of its class. So, inside the AccessTest class, c cannot be used directly. It must be accessed through its public methods: setc( ) and getc( ). If you were to remove the comment symbol from the beginning of the following line,

  // ob.c = 100; // Error!

then you would not be able to compile this program because of the access violation. To see how access control can be applied to a more practical example, consider the following improved version of the Stack class shown at the end of Chapter 6.

  // This class defines an integer stack that can hold 10 values.
  class Stack {
    /* Now, both stck and tos are private. This means
       that they cannot be accidentally or maliciously
       altered in a way that would be harmful to the stack.
    */
    private int stck[] = new int[10];
    private int tos;

    // Initialize top-of-stack
    Stack() {
      tos = -1;
    }

    // Push an item onto the stack
    void push(int item) {
      if(tos==9)
        System.out.println("Stack is full.");
      else
        stck[++tos] = item;
    }

    // Pop an item from the stack
    int pop() {
      if(tos < 0) {
        System.out.println("Stack underflow.");
        return 0;
      }
      else
        return stck[tos--];
    }
  }

As you can see, now both stck, which holds the stack, and tos, which is the index of the top of the stack, are specified as private. This means that they cannot be accessed or altered except through push( ) and pop( ). Making tos private, for example, prevents other parts of your program from inadvertently setting it to a value that is beyond the end of the stck array.

The following program demonstrates the improved Stack class. Try removing the commented-out lines to prove to yourself that the stck and tos members are, indeed, inaccessible.

  class TestStack {
    public static void main(String args[]) {
      Stack mystack1 = new Stack();
      Stack mystack2 = new Stack();

      // push some numbers onto the stack
      for(int i=0; i<10; i++) mystack1.push(i);
      for(int i=10; i<20; i++) mystack2.push(i);

      // pop those numbers off the stack
      System.out.println("Stack in mystack1:");
      for(int i=0; i<10; i++)
        System.out.println(mystack1.pop());

      System.out.println("Stack in mystack2:");
      for(int i=0; i<10; i++)
        System.out.println(mystack2.pop());

      // these statements are not legal
      // mystack1.tos = -2;
      // mystack2.stck[3] = 100;
    }
  }

Although methods will usually provide access to the data defined by a class, this does not always have to be the case. It is perfectly proper to allow an instance variable to be public when there is good reason to do so. For example, most of the simple classes in this book were created with little concern about controlling access to instance variables for the sake of simplicity. However, in most real-world classes, you will need to allow operations on data only through methods. The next chapter will return to the topic of access control. As you will see, it is particularly important when inheritance is involved.

No comments:

Post a Comment