Saturday 11 March 2017

Nested try Statements, throw & finally - Java Tutorials

Nested try Statements

The try statement can be nested. That is, a try statement can be inside the block of another try. Each time a try statement is entered, the context of that exception is pushed on the stack. If an inner try statement does not have a catch handler for a particular exception, the stack is unwound and the next try statement’s catch handlers are inspected for a match. This continues until one of the catch statements succeeds, or until all of the nested try statements are exhausted. If no catch statement matches, then the Java run-time system will handle the exception. Here is an example that uses nested try statements:

  // An example of nested try statements.
  class NestTry {
    public static void main(String args[]) {
      try {
        int a = args.length;

        /* If no command-line args are present,
           the following statement will generate
           a divide-by-zero exception. */
        int b = 42 / a;

        System.out.println("a = " + a);

        try { // nested try block
          /* If one command-line arg is used,
             then a divide-by-zero exception
             will be generated by the following code. */
          if(a==1) a = a/(a-a); // division by zero

          /* If two command-line args are used,
             then generate an out-of-bounds exception. */
          if(a==2) {
            int c[] = { 1 };
            c[42] = 99; // generate an out-of-bounds exception
          }
        } catch(ArrayIndexOutOfBoundsException e) {
          System.out.println("Array index out-of-bounds: " + e);
        }

      } catch(ArithmeticException e) {
        System.out.println("Divide by 0: " + e);
      }
    }
  }

As you can see, this program nests one try block within another. The program works as follows. When you execute the program with no command-line arguments, a divide-by-zero exception is generated by the outer try block. Execution of the program by one command-line argument generates a divide-by-zero exception from within the nested try block. Since the inner block does not catch this exception, it is passed on to the outer try block, where it is handled. If you execute the program with two command-line arguments, an array boundary exception is generated from within the inner try block. Here are sample runs that illustrate each case:

  C:\>java NestTry
  Divide by 0: java.lang.ArithmeticException: / by zero

  C:\>java NestTry One
  a = 1
  Divide by 0: java.lang.ArithmeticException: / by zero

  C:\>java NestTry One Two
  a = 2
  Array index out-of-bounds:
    java.lang.ArrayIndexOutOfBoundsException

Nesting of try statements can occur in less obvious ways when method calls are involved. For example, you can enclose a call to a method within a try block. Inside that method is another try statement. In this case, the try within the method is still nested inside the outer try block, which calls the method. Here is the previous program recoded so that the nested try block is moved inside the method nesttry( ):

  /* Try statements can be implicitly nested via
     calls to methods. */
  class MethNestTry {
    static void nesttry(int a) {
      try { // nested try block
        /* If one command-line arg is used,
           then a divide-by-zero exception
           will be generated by the following code. */
        if(a==1) a = a/(a-a); // division by zero

        /* If two command-line args are used,
           then generate an out-of-bounds exception. */
        if(a==2) {
          int c[] = { 1 };
          c[42] = 99; // generate an out-of-bounds exception
        }
      } catch(ArrayIndexOutOfBoundsException e) {
        System.out.println("Array index out-of-bounds: " + e);
      }
    }

    public static void main(String args[]) {
      try {
        int a = args.length;

        /* If no command-line args are present,
           the following statement will generate
           a divide-by-zero exception. */
        int b = 42 / a;
        System.out.println("a = " + a);

        nesttry(a);
      } catch(ArithmeticException e) {
        System.out.println("Divide by 0: " + e);
      }
    }
  }

The output of this program is identical to that of the preceding example.





throw

So far, you have only been catching exceptions that are thrown by the Java run-time system. However, it is possible for your program to throw an exception explicitly, using the throw statement. The general form of throw is shown here:

  throw ThrowableInstance;

Here, ThrowableInstance must be an object of type Throwable or a subclass of Throwable. Simple types, such as int or char, as well as non-Throwable classes, such as String and Object, cannot be used as exceptions. There are two ways you can obtain a Throwable object: using a parameter into a catch clause, or creating one with the new operator.

The flow of execution stops immediately after the throw statement; any subsequent statements are not executed. The nearest enclosing try block is inspected to see if it has a catch statement that matches the type of the exception. If it does find a match, control is transferred to that statement. If not, then the next enclosing try statement is inspected, and so on. If no matching catch is found, then the default exception handler halts the program and prints the stack trace.

Here is a sample program that creates and throws an exception. The handler that catches the exception rethrows it to the outer handler.

  // Demonstrate throw.
  class ThrowDemo {
    static void demoproc() {
      try {
        throw new NullPointerException("demo");
      } catch(NullPointerException e) {
        System.out.println("Caught inside demoproc.");
        throw e; // rethrow the exception
      }
    }

    public static void main(String args[]) {
      try {
        demoproc();
      } catch(NullPointerException e) {
        System.out.println("Recaught: " + e);
      }
    }
  }

This program gets two chances to deal with the same error. First, main( ) sets up an exception context and then calls demoproc( ). The demoproc( ) method then sets up another exception-handling context and immediately throws a new instance of NullPointerException, which is caught on the next line. The exception is then rethrown. Here is the resulting output:

  Caught inside demoproc.
  Recaught: java.lang.NullPointerException: demo

The program also illustrates how to create one of Java’s standard exception objects. Pay close attention to this line:

  throw new NullPointerException("demo");

Here, new is used to construct an instance of NullPointerException. All of Java’s built-in run-time exceptions have at least two constructors: one with no parameter and one that takes a string parameter. When the second form is used, the argument specifies a string that describes the exception. This string is displayed when the object is used as an argument to print( ) or println( ). It can also be obtained by a call to getMessage( ), which is defined by Throwable.


throws

If a method is capable of causing an exception that it does not handle, it must specify this behavior so that callers of the method can guard themselves against that exception. You do this by including a throws clause in the method’s declaration. A throws clause lists the types of exceptions that a method might throw. This is necessary for all exceptions, except those of type Error or RuntimeException, or any of their subclasses. All other exceptions that a method can throw must be declared in the throws clause. If they are not, a compile-time error will result.

This is the general form of a method declaration that includes a throws clause:

  type method-name(parameter-list) throws exception-list
  {
    // body of method
  }

Here, exception-list is a comma-separated list of the exceptions that a method can throw.

Following is an example of an incorrect program that tries to throw an exception that it does not catch. Because the program does not specify a throws clause to declare this fact, the program will not compile.

  // This program contains an error and will not compile.
  class ThrowsDemo {
    static void throwOne() {
      System.out.println("Inside throwOne.");
      throw new IllegalAccessException("demo");
    }
    public static void main(String args[]) {
      throwOne();
    }
  }

To make this example compile, you need to make two changes. First, you need to declare that throwOne( ) throws IllegalAccessException. Second, main( ) must define a try/catch statement that catches this exception.

The corrected example is shown here:

  // This is now correct.
  class ThrowsDemo {
    static void throwOne() throws IllegalAccessException {
      System.out.println("Inside throwOne.");
      throw new IllegalAccessException("demo");
    }
    public static void main(String args[]) {
      try {
        throwOne();
      } catch (IllegalAccessException e) {
        System.out.println("Caught " + e);
      }
    }
  }

Here is the output generated by running this example program:

  inside throwOne
  caught java.lang.IllegalAccessException: demo




finally

When exceptions are thrown, execution in a method takes a rather abrupt, nonlinear path that alters the normal flow through the method. Depending upon how the method is coded, it is even possible for an exception to cause the method to return prematurely. This could be a problem in some methods. For example, if a method opens a file upon entry and closes it upon exit, then you will not want the code that closes the file to be bypassed by the exception-handling mechanism. The finally keyword is designed to address this contingency.

finally creates a block of code that will be executed after a try/catch block has completed and before the code following the try/catch block. The finally block will execute whether or not an exception is thrown. If an exception is thrown, the finally block will execute even if no catch statement matches the exception. Any time a method is about to return to the caller from inside a try/catch block, via an uncaught exception or an explicit return statement, the finally clause is also executed just before the method returns. This can be useful for closing file handles and freeing up any other resources that might have been allocated at the beginning of a method with the intent of disposing of them before returning. The finally clause is optional. However, each try statement requires at least one catch or a finally clause.

Here is an example program that shows three methods that exit in various ways,
none without executing their finally clauses:

  // Demonstrate finally.
  class FinallyDemo {
    // Through an exception out of the method.
    static void procA() {
      try {
        System.out.println("inside procA");
        throw new RuntimeException("demo");
      } finally {
        System.out.println("procA's finally");
      }
    }

    // Return from within a try block.
    static void procB() {
      try {
        System.out.println("inside procB");
        return;
      } finally {
        System.out.println("procB's finally");
      }
    }
    // Execute a try block normally.
    static void procC() {
      try {
        System.out.println("inside procC");
      } finally {
        System.out.println("procC's finally");
      }
    }

    public static void main(String args[]) {
      try {
        procA();
      } catch (Exception e) {
        System.out.println("Exception caught");
      }
      procB();
      procC();
    }
  }

In this example, procA( ) prematurely breaks out of the try by throwing an exception. The finally clause is executed on the way out. procB( )’s try statement is exited via a return statement. The finally clause is executed before procB( ) returns. In procC( ), the try statement executes normally, without error. However, the finally block is still executed.

If a finally block is associated with a try, the finally block will be executed upon conclusion of the try.

Here is the output generated by the preceding program:

  inside procA
  procA’s finally
  Exception caught
  inside procB
  procB’s finally
  inside procC
  procC’s finally

Exception-Handling Fundamentals, Using try and catch & Multiple catch Clauses - Java Tutorials

Exception-Handling Fundamentals

A Java exception is an object that describes an exceptional (that is, error) condition that has occurred in a piece of code. When an exceptional condition arises, an object representing that exception is created and thrown in the method that caused the error. That method may choose to handle the exception itself, or pass it on. Either way, at some point, the exception is caught and processed. Exceptions can be generated by the Java run-time system, or they can be manually generated by your code. Exceptions thrown by Java relate to fundamental errors that violate the rules of the Java language or the constraints of the Java execution environment. Manually generated exceptions are typically used to report some error condition to the caller of a method.

Java exception handling is managed via five keywords: try, catch, throw, throws, and finally. Briefly, here is how they work. Program statements that you want to monitor for exceptions are contained within a try block. If an exception occurs within the try block, it is thrown. Your code can catch this exception (using catch) and handle it in some rational manner. System-generated exceptions are automatically thrown by the Java run-time system. To manually throw an exception, use the keyword throw. Any exception that is thrown out of a method must be specified as such by a throws clause. Any code that absolutely must be executed before a method returns is put in a finally block.

This is the general form of an exception-handling block:

  try {
    // block of code to monitor for errors
  }

  catch (ExceptionType1 exOb) {
    // exception handler for ExceptionType1
  }
  catch (ExceptionType2 exOb) {
    // exception handler for ExceptionType2
  }
  // ...
    finally {
    // block of code to be executed before try block ends
  }

Here, ExceptionType is the type of exception that has occurred. The remainder of this chapter describes how to apply this framework.


Exception Types

All exception types are subclasses of the built-in class Throwable. Thus, Throwable is at the top of the exception class hierarchy. Immediately below Throwable are two subclasses that partition exceptions into two distinct branches. One branch is headed by Exception. This class is used for exceptional conditions that user programs should catch. This is also the class that you will subclass to create your own custom exception types. There is an important subclass of Exception, called RuntimeException. Exceptions of this type are automatically defined for the programs that you write and include things such as division by zero and invalid array indexing.

The other branch is topped by Error, which defines exceptions that are not expected to be caught under normal circumstances by your program. Exceptions of type Error are used by the Java run-time system to indicate errors having to do with the run-time environment, itself. Stack overflow is an example of such an error. This chapter will not be dealing with exceptions of type Error, because these are typically created in response to catastrophic failures that cannot usually be handled by your program.


Uncaught Exceptions

Before you learn how to handle exceptions in your program, it is useful to see what happens when you don’t handle them. This small program includes an expression that intentionally causes a divide-by-zero error.

  class Exc0 {
    public static void main(String args[]) {
      int d = 0;
      int a = 42 / d;
    }
  }

When the Java run-time system detects the attempt to divide by zero, it constructs a new exception object and then throws this exception. This causes the execution of Exc0 to stop, because once an exception has been thrown, it must be caught by an exception handler and dealt with immediately. In this example, we haven’t supplied any exception handlers of our own, so the exception is caught by the default handler provided by the Java run-time system. Any exception that is not caught by your program will ultimately be processed by the default handler. The default handler displays a string describing the exception, prints a stack trace from the point at which the exception occurred, and terminates the program.

Here is the output generated when this example is executed.

  java.lang.ArithmeticException: / by zero
           at Exc0.main(Exc0.java:4)

Notice how the class name, Exc0; the method name, main; the filename, Exc0.java; and the line number, 4, are all included in the simple stack trace. Also, notice that the type of the exception thrown is a subclass of Exception called ArithmeticException, which more specifically describes what type of error happened. As discussed later in this chapter, Java supplies several built-in exception types that match the various sorts of run-time errors that can be generated.

The stack trace will always show the sequence of method invocations that led up to the error. For example, here is another version of the preceding program that introduces the same error but in a method separate from main( ):

  class Exc1 {
    static void subroutine() {
      int d = 0;
      int a = 10 / d;
    }
    public static void main(String args[]) {
      Exc1.subroutine();
    }
  }

The resulting stack trace from the default exception handler shows how the entire call stack is displayed:

  java.lang.ArithmeticException: / by zero
      at Exc1.subroutine(Exc1.java:4)
      at Exc1.main(Exc1.java:7)

As you can see, the bottom of the stack is main’s line 7, which is the call to subroutine( ), which caused the exception at line 4. The call stack is quite useful for debugging, because it pinpoints the precise sequence of steps that led to the error.




Using try and catch

Although the default exception handler provided by the Java run-time system is useful for debugging, you will usually want to handle an exception yourself. Doing so provides two benefits. First, it allows you to fix the error. Second, it prevents the program from automatically terminating. Most users would be confused (to say the least) if your program stopped running and printed a stack trace whenever an error occurred! Fortunately, it is quite easy to prevent this.

To guard against and handle a run-time error, simply enclose the code that you want to monitor inside a try block. Immediately following the try block, include a catch clause that specifies the exception type that you wish to catch. To illustrate how easily this can be done, the following program includes a try block and a catch clause which processes the ArithmeticException generated by the division-by-zero error:

  class Exc2 {
    public static void main(String args[]) {
      int d, a;
    try { // monitor a block of code.
        d = 0;
        a = 42 / d;
        System.out.println("This will not be printed.");
     } catch (ArithmeticException e) { // catch divide-by-zero error
        System.out.println("Division by zero.");
      }
      System.out.println("After catch statement.");
    }
  }

This program generates the following output:

  Division by zero.
  After catch statement.

Notice that the call to println( ) inside the try block is never executed. Once an exception is thrown, program control transfers out of the try block into the catch block. Put differently, catch is not “called,” so execution never “returns” to the try block from a catch. Thus, the line “This will not be printed.” is not displayed. Once the catch statement has executed, program control continues with the next line in the program following the entire try/catch mechanism.

A try and its catch statement form a unit. The scope of the catch clause is restricted to those statements specified by the immediately preceding try statement. A catch statement cannot catch an exception thrown by another try statement (except in the case of nested try statements, described shortly). The statements that are protected by try must be surrounded by curly braces. (That is, they must be within a block.) You cannot use try on a single statement.

The goal of most well-constructed catch clauses should be to resolve the exceptional condition and then continue on as if the error had never happened. For example, in the next program each iteration of the for loop obtains two random integers. Those two integers are divided by each other, and the result is used to divide the value 12345. The final result is put into a. If either division operation causes a divide-by-zero error, it is caught, the value of a is set to zero, and the program continues.

  // Handle an exception and move on.
  import java.util.Random;

  class HandleError {
    public static void main(String args[]) {
      int a=0, b=0, c=0;
      Random r = new Random();

      for(int i=0; i<32000; i++) {
        try {
          b = r.nextInt();
          c = r.nextInt();
          a = 12345 / (b/c);
        } catch (ArithmeticException e) {
          System.out.println("Division by zero.");
          a = 0; // set a to zero and continue
        }
        System.out.println("a: " + a);
      }
    }
  }


Displaying a Description of an Exception

Throwable overrides the toString( ) method (defined by Object) so that it returns a string containing a description of the exception. You can display this description in a println( ) statement by simply passing the exception as an argument. For example, the catch block in the preceding program can be rewritten like this:

  catch (ArithmeticException e) {
    System.out.println("Exception: " + e);
    a = 0; // set a to zero and continue
  }

When this version is substituted in the program, and the program is run, each divide-by-zero error displays the following message:

  Exception: java.lang.ArithmeticException: / by zero

While it is of no particular value in this context, the ability to display a description of an exception is valuable in other circumstances—particularly when you are experimenting with exceptions or when you are debugging.




Multiple catch Clauses

In some cases, more than one exception could be raised by a single piece of code. To handle this type of situation, you can specify two or more catch clauses, each catching a different type of exception. When an exception is thrown, each catch statement is inspected in order, and the first one whose type matches that of the exception is executed. After one catch statement executes, the others are bypassed, and execution continues after the try/catch block. The following example traps two different exception types:

  // Demonstrate multiple catch statements.
  class MultiCatch {
    public static void main(String args[]) {
      try {
        int a = args.length;
        System.out.println("a = " + a);
        int b = 42 / a;
        int c[] = { 1 };
        c[42] = 99;
      } catch(ArithmeticException e) {
        System.out.println("Divide by 0: " + e);
      } catch(ArrayIndexOutOfBoundsException e) {
        System.out.println("Array index oob: " + e);
      }
      System.out.println("After try/catch blocks.");
    }
  }

This program will cause a division-by-zero exception if it is started with no commandline parameters, since a will equal zero. It will survive the division if you provide a command-line argument, setting a to something larger than zero. But it will cause an ArrayIndexOutOfBoundsException, since the int array c has a length of 1, yet the program attempts to assign a value to c[42].

Here is the output generated by running it both ways:

  C:\>java MultiCatch
  a = 0
  Divide by 0: java.lang.ArithmeticException: / by zero
  After try/catch blocks.

  C:\>java MultiCatch TestArg
  a = 1
  Array index oob: java.lang.ArrayIndexOutOfBoundsException
  After try/catch blocks.

When you use multiple catch statements, it is important to remember that exception subclasses must come before any of their superclasses. This is because a catch statement that uses a superclass will catch exceptions of that type plus any of its subclasses. Thus, a subclass would never be reached if it came after its superclass. Further, in Java, unreachable code is an error. For example, consider the following program:

  /* This program contains an error.

     A subclass must come before its superclass in
     a series of catch statements. If not,
     unreachable code will be created and a
     compile-time error will result.
  */
  class SuperSubCatch {
    public static void main(String args[]) {
      try {
        int a = 0;
        int b = 42 / a;
      } catch(Exception e) {
        System.out.println("Generic Exception catch.");
      }
      /* This catch is never reached because
         ArithmeticException is a subclass of Exception. */
      catch(ArithmeticException e) { // ERROR - unreachable
        System.out.println("This is never reached.");
      }
    }
  }

If you try to compile this program, you will receive an error message stating that the second catch statement is unreachable because the exception has already been caught. Since ArithmeticException is a subclass of Exception, the first catch statement will handle all Exception-based errors, including ArithmeticException. This means that the second catch statement will never execute. To fix the problem, reverse the order of the catch statements.

Interfaces - Java Tutorials

Using the keyword interface, you can fully abstract a class’ interface from its implementation. That is, using interface, you can specify what a class must do, but not how it does it. Interfaces are syntactically similar to classes, but they lack instance variables, and their methods are declared without any body. In practice, this means that you can define interfaces which don’t make assumptions about how they are implemented. Once it is defined, any number of classes can implement an interface. Also, one class can implement any number of interfaces.

To implement an interface, a class must create the complete set of methods defined by the interface. However, each class is free to determine the details of its own implementation. By providing the interface keyword, Java allows you to fully utilize the “one interface, multiple methods” aspect of polymorphism.

Interfaces are designed to support dynamic method resolution at run time. Normally, in order for a method to be called from one class to another, both classes need to be present at compile time so the Java compiler can check to ensure that the method signatures are compatible. This requirement by itself makes for a static and nonextensible classing environment. Inevitably in a system like this, functionality gets pushed up higher and higher in the class hierarchy so that the mechanisms will be available to more and more subclasses. Interfaces are designed to avoid this problem. They disconnect the definition of a method or set of methods from the inheritance hierarchy. Since interfaces are in a different hierarchy from classes, it is possible for classes that are unrelated in terms of the class hierarchy to implement the same interface. This is where the real power of interfaces is realized.

Interfaces add most of the functionality that is required for many applications which would normally resort to using multiple inheritance in a language such as C++.


Defining an Interface

An interface is defined much like a class. This is the general form of an interface:

  access interface name {
    return-type method-name1(parameter-list);
    return-type method-name2(parameter-list);
    type final-varname1 = value;
    type final-varname2 = value;
    // ...
    return-type method-nameN(parameter-list);
    type final-varnameN = value;
  }

Here, access is either public or not used. When no access specifier is included, then default access results, and the interface is only available to other members of the package in which it is declared. When it is declared as public, the interface can be used by any other code. name is the name of the interface, and can be any valid identifier. Notice that the methods which are declared have no bodies. They end with a semicolon after the parameter list. They are, essentially, abstract methods; there can be no default implementation of any method specified within an interface. Each class that includes an interface must implement all of the methods.

Variables can be declared inside of interface declarations. They are implicitly final and static, meaning they cannot be changed by the implementing class. They must also be initialized with a constant value. All methods and variables are implicitly public if the interface, itself, is declared as public.

Here is an example of an interface definition. It declares a simple interface which contains one method called callback( ) that takes a single integer parameter.

  interface Callback {
    void callback(int param);
  }


Implementing Interfaces

Once an interface has been defined, one or more classes can implement that interface. To implement an interface, include the implements clause in a class definition, and then create the methods defined by the interface. The general form of a class that includes the implements clause looks like this:

  access class classname [extends superclass]
                [implements interface [,interface...]] {
    // class-body
  }

Here, access is either public or not used. If a class implements more than one interface, the interfaces are separated with a comma. If a class implements two interfaces that declare the same method, then the same method will be used by clients of either interface. The methods that implement an interface must be declared public. Also, the type signature of the implementing method must match exactly the type signature specified in the interface definition.

Here is a small example class that implements the Callback interface shown earlier.

  class Client implements Callback {
    // Implement Callback's interface
    public void callback(int p) {

      System.out.println("callback called with " + p);
    }
  }

Notice that callback( ) is declared using the public access specifier.

When you implement an interface method, it must be declared as public.

It is both permissible and common for classes that implement interfaces to define additional members of their own. For example, the following version of Client implements callback( ) and adds the method nonIfaceMeth( ):

  class Client implements Callback {
    // Implement Callback's interface
    public void callback(int p) {
      System.out.println("callback called with " + p);
    }

    void nonIfaceMeth() {
      System.out.println("Classes that implement interfaces " +
                         "may also define other members, too.");
    }
  }


Accessing Implementations Through Interface References

You can declare variables as object references that use an interface rather than a class type. Any instance of any class that implements the declared interface can be referred to by such a variable. When you call a method through one of these references, the correct version will be called based on the actual instance of the interface being referred to. This is one of the key features of interfaces. The method to be executed is looked up dynamically at run time, allowing classes to be created later than the code which calls methods on them. The calling code can dispatch through an interface without having to know anything about the “callee.” This process is similar to using a superclass reference to access a subclass object.

Because dynamic lookup of a method at run time incurs a significant overhead when compared with the normal method invocation in Java, you should be careful not to use interfaces casually in performance-critical code.

The following example calls the callback( ) method via an interface reference variable:

  class TestIface {
    public static void main(String args[]) {
      Callback c = new Client();
      c.callback(42);
    }
  }

The output of this program is shown here:

  callback called with 42

Notice that variable c is declared to be of the interface type Callback, yet it was assigned an instance of Client. Although c can be used to access the callback( ) method, it cannot access any other members of the Client class. An interface reference variable only has knowledge of the methods declared by its interface declaration. Thus, c could not be used to access nonIfaceMeth( ) since it is defined by Client but not Callback.

While the preceding example shows, mechanically, how an interface reference variable can access an implementation object, it does not demonstrate the polymorphic power of such a reference. To sample this usage, first create the second implementation of Callback, shown here:

  // Another implementation of Callback.
  class AnotherClient implements Callback {
    // Implement Callback's interface
    public void callback(int p) {
      System.out.println("Another version of callback");
      System.out.println("p squared is " + (p*p));
    }
  }

Now, try the following class:

  class TestIface2 {
    public static void main(String args[]) {
      Callback c = new Client();
      AnotherClient ob = new AnotherClient();

      c.callback(42);

      c = ob; // c now refers to AnotherClient object
      c.callback(42);
    }
  }

The output from this program is shown here:

  callback called with 42
  Another version of callback
  p squared is 1764

As you can see, the version of callback( ) that is called is determined by the type of object that c refers to at run time. While this is a very simple example, you will see another, more practical one shortly.


Partial Implementations

If a class includes an interface but does not fully implement the methods defined by that interface, then that class must be declared as abstract. For example:

  abstract class Incomplete implements Callback {
    int a, b;
    void show() {
      System.out.println(a + " " + b);
    }
    // ...
  }

Here, the class Incomplete does not implement callback( ) and must be declared as abstract. Any class that inherits Incomplete must implement callback( ) or be declared abstract itself.


Applying Interfaces

To understand the power of interfaces, let’s look at a more practical example. In earlier chapters you developed a class called Stack that implemented a simple fixed-size stack. However, there are many ways to implement a stack. For example, the stack can be of a fixed size or it can be “growable.” The stack can also be held in an array, a linked list, a binary tree, and so on. No matter how the stack is implemented, the interface to the stack remains the same. That is, the methods push( ) and pop( ) define the interface to the stack independently of the details of the implementation. Because the interface to a stack is separate from its implementation, it is easy to define a stack interface, leaving it to each implementation to define the specifics. Let’s look at two examples.

First, here is the interface that defines an integer stack. Put this in a file called IntStack.java. This interface will be used by both stack implementations.

  // Define an integer stack interface.
  interface IntStack {
    void push(int item); // store an item
    int pop(); // retrieve an item
  }

The following program creates a class called FixedStack that implements a fixed-length version of an integer stack:

  // An implementation of IntStack that uses fixed storage.
  class FixedStack implements IntStack {
    private int stck[];
    private int tos;

    // allocate and initialize stack
    FixedStack(int size) {
      stck = new int[size];
      tos = -1;
    }

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

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

  class IFTest {
    public static void main(String args[]) {
      FixedStack mystack1 = new FixedStack(5);
      FixedStack mystack2 = new FixedStack(8);

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

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

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

Following is another implementation of IntStack that creates a dynamic stack by use of the same interface definition. In this implementation, each stack is constructed with an initial length. If this initial length is exceeded, then the stack is increased in size. Each time more room is needed, the size of the stack is doubled.

  // Implement a "growable" stack.
  class DynStack implements IntStack {
    private int stck[];
    private int tos;

    // allocate and initialize stack
    DynStack(int size) {
      stck = new int[size];
      tos = -1;
    }

    // Push an item onto the stack
    public void push(int item) {
      // if stack is full, allocate a larger stack
      if(tos==stck.length-1) {
        int temp[] = new int[stck.length * 2]; // double size
        for(int i=0; i<stck.length; i++) temp[i] = stck[i];
        stck = temp;
        stck[++tos] = item;
      }
      else
        stck[++tos] = item;
    }

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

  class IFTest2 {
    public static void main(String args[]) {
      DynStack mystack1 = new DynStack(5);
      DynStack mystack2 = new DynStack(8);

      // these loops cause each stack to grow
      for(int i=0; i<12; i++) mystack1.push(i);
      for(int i=0; i<20; i++) mystack2.push(i);

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

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

The following class uses both the FixedStack and DynStack implementations. It does so through an interface reference. This means that calls to push( ) and pop( ) are resolved at run time rather than at compile time.

  /* Create an interface variable and
  access stacks through it.
  */
  class IFTest3 {
    public static void main(String args[]) {
      IntStack mystack; // create an interface reference variable
      DynStack ds = new DynStack(5);
      FixedStack fs = new FixedStack(8);

      mystack = ds; // load dynamic stack
      // push some numbers onto the stack
      for(int i=0; i<12; i++) mystack.push(i);

      mystack = fs; // load fixed stack
      for(int i=0; i<8; i++) mystack.push(i);

      mystack = ds;
      System.out.println("Values in dynamic stack:");
      for(int i=0; i<12; i++)
        System.out.println(mystack.pop());

      mystack = fs;
      System.out.println("Values in fixed stack:");
      for(int i=0; i<8; i++)
        System.out.println(mystack.pop());
    }
  }

In this program, mystack is a reference to the IntStack interface. Thus, when it refers to ds, it uses the versions of push( ) and pop( ) defined by the DynStack implementation. When it refers to fs, it uses the versions of push( ) and pop( ) defined by FixedStack. As explained, these determinations are made at run time. Accessing multiple implementations of an interface through an interface reference variable is the most powerful way that Java achieves run-time polymorphism.


Variables in Interfaces

You can use interfaces to import shared constants into multiple classes by simply declaring an interface that contains variables which are initialized to the desired values. When you include that interface in a class (that is, when you “implement” the interface), all of those variable names will be in scope as constants. This is similar to using a header file in C/C++ to create a large number of #defined constants or const declarations. If an interface contains no methods, then any class that includes such an interface doesn’t actually implement anything. It is as if that class were importing the constant variables into the class name space as final variables. The next example uses this technique to implement an automated “decision maker”:

  import java.util.Random;

  interface SharedConstants {
    int NO = 0;
    int YES = 1;
    int MAYBE = 2;
    int LATER = 3;
    int SOON = 4;
    int NEVER = 5;
  }

  class Question implements SharedConstants {
    Random rand = new Random();
    int ask() {
      int prob = (int) (100 * rand.nextDouble());
      if (prob < 30)
        return NO;        // 30%
      else if (prob < 60)
        return YES;       // 30%
      else if (prob < 75)
        return LATER;     // 15%
      else if (prob < 98)
        return SOON;      // 13%
      
      else
        return NEVER;     // 2%
    }
  }

  class AskMe implements SharedConstants {
    static void answer(int result) {
      switch(result) {
        case NO:
          System.out.println("No");
          break;
        case YES:
          System.out.println("Yes");
          break;
        case MAYBE:
          System.out.println("Maybe");
          break;
        case LATER:
          System.out.println("Later");
          break;
        case SOON:
          System.out.println("Soon");
          break;
        case NEVER:
          System.out.println("Never");
          break;
      }
    }
    
    public static void main(String args[]) {
      Question q = new Question();
      answer(q.ask());
      answer(q.ask());
      answer(q.ask());
      answer(q.ask());
    }
  }

Notice that this program makes use of one of Java’s standard classes: Random. This class provides pseudorandom numbers. It contains several methods which allow you to obtain random numbers in the form required by your program. In this example, the method nextDouble( ) is used. It returns random numbers in the range 0.0 to 1.0.

In this sample program, the two classes, Question and AskMe, both implement the SharedConstants interface where NO, YES, MAYBE, SOON, LATER, and NEVER are defined. Inside each class, the code refers to these constants as if each class had defined or inherited them directly. Here is the output of a sample run of this program. Note that the results are different each time it is run.

  Later
  Soon
  No
  Yes


Interfaces Can Be Extended

One interface can inherit another by use of the keyword extends. The syntax is the same as for inheriting classes. When a class implements an interface that inherits another interface, it must provide implementations for all methods defined within the interface inheritance chain. Following is an example:

  // One interface can extend another.
  interface A {
    void meth1();
    void meth2();
  }

  // B now includes meth1() and meth2() -- it adds meth3().
  interface B extends A {
    void meth3();
  }

  // This class must implement all of A and B
  class MyClass implements B {
    public void meth1() {
      System.out.println("Implement meth1().");
    }

    public void meth2() {
      System.out.println("Implement meth2().");
    }

    public void meth3() {
      System.out.println("Implement meth3().");
    }

  }

  class IFExtend {
    public static void main(String arg[]) {
      MyClass ob = new MyClass();
      ob.meth1();
      ob.meth2();
      ob.meth3();
    }
  }

As an experiment you might want to try removing the implementation for meth1( ) in MyClass. This will cause a compile-time error. As stated earlier, any class that implements an interface must implement all methods defined by that interface, including any that are inherited from other interfaces.

Although the examples we’ve included in this book do not make frequent use of packages or interfaces, both of these tools are an important part of the Java programming environment. Virtually all real programs and applets that you write in Java will be contained within packages. A number will probably implement interfaces as well. It is important, therefore, that you be comfortable with their usage.