Posted on Leave a comment

Sealed Interfaces – Object-Oriented Programming

Sealed Interfaces

Analogous to sealed classes, sealed interfaces can also be defined. However, a sealed interface can have both subinterfaces and subclasses as its permitted direct subtypes. The permitted subtypes can be subclasses that implement the sealed interface and subinterfaces that extend the sealed interface. This is in contrast to a sealed class that can have direct subclasses, but not direct subinterfaces—as classes cannot be extended or implemented by interfaces.

Figure 5.10 shows the book domain from Figure 5.9 that has been augmented with a sealed interface that specifies its permitted direct subtypes in a permits clause:

Click here to view code image

public sealed interface Subscribable permits Ebook, Audiobook, VIPSubcribable {}

The sealed superinterface Subscribable has two final permitted direct subclasses (that implement the superinterface) and one non-sealed direct subinterface (that extends the superinterface). The declarations of the permitted direct subclasses Ebook and Audiobook have been updated accordingly so that they implement the direct superinterface Subscribable.

Click here to view code image

public final class Ebook extends Book implements Subscribable {}
public final class Audiobook extends Book implements Subscribable {}
public non-sealed interface VIPSubcribable extends Subscribable {}

The rest of the type declarations from Figure 5.9 remain the same in Figure 5.10:

Click here to view code image

public abstract sealed class Book permits PrintedBook, Ebook, Audiobook {}
public non-sealed class PrintedBook extends Book {}

Note that it is perfectly possible for a class or an interface to be a permitted direct subtype of more than one direct supertype—as is the case for the Ebook and the Audiobook subclasses in Figure 5.10.

Figure 5.10 Sealed Classes and Interfaces

We see from the discussion above that the permitted direct subtypes of a sealed superinterface abide by the same contract rules as for sealed superclasses:

  • A permitted direct subclass or subinterface must extend or implement its direct superinterface, respectively.
  • Any permitted subclass of a sealed interface must be declared either sealed, non-sealed or final, but any permitted subinterface can only be declared either sealed or non-sealed. The modifier final is not allowed for interfaces.
  • The same rules for locality also apply for sealed interfaces and their permitted direct subtypes: All are declared either in the same named module or in the same package (named or unnamed) in the unnamed module.
Enum and Record Types as Permitted Direct Subtypes

By definition, an enum type (p. 287) is either implicitly final (has no enum constants that have a class body, as shown at (2)) or implicitly sealed (has at least one enum constant with a class body that constitutes an implicitly declared direct subclass, as shown at (3) for the constant DVD_R that has an empty class body). Thus an enum type can be specified as a permitted direct subtype of a sealed superinterface, as shown at (1). The modifiers final and sealed cannot be explicitly specified in the enum type declaration.

Click here to view code image

sealed interface MediaStorage permits CD, DVD {}       // (1) Sealed interface
enum CD implements MediaStorage {CD_ROM, CD_R, CD_W}   // (2) Implicitly final
enum DVD implements MediaStorage {DVD_R {}, DVD_RW}    // (3) Implicitly sealed

Analogously, a record class (p. 299) is implicitly final, and can be specified as a permitted direct subtype of a sealed superinterface. The sealed interface MediaStorage at (1a) now permits the record class HardDisk as a direct subtype. Again note that the modifier final cannot be specified in the header of the HardDisk record class declared at (4).

Click here to view code image

sealed interface MediaStorage permits CD, DVD, HardDisk {}// (1a) Sealed interface
record HardDisk(double capacity) implements MediaStorage {}// (4) Implicitly final

Posted on Leave a comment

The Multi-catch Clause – Exception Handling

7.6 The Multi-catch Clause

Example 7.9 uses a try block that has multiple uni-catch clauses. This example is based on Example 7.8. The sum of the values and the number of values needed to calculate the average are now read as program arguments from the command line at (2) and (3), respectively. The example shows a try statement at (1) that uses three uni-catch clauses: at (5), (6), and (7). In a uni-catch clause, a single exception type is specified for the catch parameter.

In Example 7.9, the method printAverage() is only called at (4) if there are at least two consecutive integers specified on the command line. An unchecked ArrayIndexOutOfBoundsException is thrown if there are not enough program arguments, and an unchecked NumberFormatException is thrown if an argument cannot be converted to an int value. The astute reader will notice that the code for handling these two exceptions is the same in the body of the respective catch clauses. In order to avoid such code duplication, one might be tempted to replace the two catch clauses with a single catch clause that catches a more general exception, for example:

Click here to view code image

catch (RuntimeException rte) { // NOT RECOMMENDED!
  System.out.println(rte);
  System.out.println(“Usage: java Average9 <sum of values> <no. of values>”);
}

This is certainly not recommended, as specific exceptions are to be preferred over general exceptions, not the least because a more general exception type might unintentionally catch more exceptions than intended.

Example 7.9 Using Multiple catch Clauses

Click here to view code image

// File: IntegerDivisionByZero.java
public class IntegerDivisionByZero extends Exception {
  IntegerDivisionByZero() { super(“Integer Division by Zero”); }
}

Click here to view code image

// File: Average9.java
public class Average9 {
  public static void main(String[] args) {
    try {                                                     // (1)
      int sum         = Integer.parseInt(args[0]);            // (2)
      int numOfValues = Integer.parseInt(args[1]);            // (3)
      printAverage(sum, numOfValues);                         // (4)
    } catch (ArrayIndexOutOfBoundsException aioob) {          // (5) uni-catch
      System.out.println(aioob);
      System.out.println(“Usage: java Average9 <sum of values> <no. of values>”);
    } catch (NumberFormatException nfe) {                     // (6) uni-catch
      System.out.println(nfe);
      System.out.println(“Usage: java Average9 <sum of values> <no. of values>”);
    } catch (IntegerDivisionByZero idbz) {                    // (7) uni-catch
      idbz.printStackTrace();
      System.out.println(“Exception handled in main().”);
    } finally {                                               // (8)
      System.out.println(“Finally done in main().”);
    }
    System.out.println(“Exit main().”);                       // (9)
  }
  public static void printAverage(int totalSum, int totalCount)
      throws IntegerDivisionByZero {
    int average = computeAverage(totalSum, totalCount);
    System.out.println(“Average = ” +
        totalSum + ” / ” + totalCount + ” = ” + average);
    System.out.println(“Exit printAverage().”);
  }
  public static int computeAverage(int sum, int count)
      throws IntegerDivisionByZero {
    System.out.println(“Computing average.”);
    if (count == 0)
      throw new IntegerDivisionByZero();
    return sum/count;
  }
}

Running the program:

Click here to view code image

>
java Average9 100 twenty

java.lang.NumberFormatException: For input string: “twenty”
Usage: java Average9 <sum of values> <no. of values>
Finally done in main().
Exit main().

Running the program:

Click here to view code image

>
java Average9 100

java.lang.ArrayIndexOutOfBoundsException: 1
Usage: java Average9 <sum of values> <no. of values>
Finally done in main().
Exit main().

The multi-catch clause provides the solution, allowing specific exceptions to be declared and avoiding duplicating the same code for the body of the catch clauses. The syntax of the multi-catch clause is as follows:

Click here to view code image

catch (
exception_type
1
|
exception_type
2
|…|
exception_type
k
parameter
) {
statements
}

The multi-catch clause still has a single parameter, but now a list of exception types, delimited by the vertical bar (|), can be specified as the types for this parameter. This list defines a union of alternatives that are the exception types which the multi-catch clause can handle. The statements in the body of the multi-catch clause will be executed when an object of any of the specified exception types is caught by the multi-catch clause.

The multiple catch clauses at (5) and (6) in Example 7.9 have been replaced with a multi-catch clause at (5) in Example 7.10:

Click here to view code image

catch (ArrayIndexOutOfBoundsException |                  // (5) multi-catch
       NumberFormatException ep) {
  System.out.println(ep);
  System.out.println(“Usage: java Average10 <sum of values> <no. of values>”);
}

The multi-catch clause in Example 7.10 is semantically equivalent to the two uni-catch clauses in Example 7.9, and we can expect the same program behavior in both examples.

Example 7.10 Using the Multi-catch Clause

Click here to view code image

// File: Average10.java
public class Average10 {
  public static void main(String[] args) {
    try {                                                      // (1)
      int sum = Integer.parseInt(args[0]);                     // (2)
      int numOfValues = Integer.parseInt(args[1]);             // (3)
printAverage(sum, numOfValues);                          // (4)
    } catch (ArrayIndexOutOfBoundsException |                  // (5) multi-catch
             NumberFormatException ep) {
      System.out.println(ep);
      System.out.println(“Usage: java Average10 <sum of values> <no. of values>”);
    } catch (IntegerDivisionByZero idbz) {                     // (6) uni-catch
      idbz.printStackTrace();
      System.out.println(“Exception handled in main().”);
    } finally {                                                // (7)
      System.out.println(“Finally done in main().”);
    }
    System.out.println(“Exit main().”);                        // (8)
  }
  public static void printAverage(int totalSum, int totalCount)
      throws IntegerDivisionByZero {
    // See Example 7.9.
  }
  public static int computeAverage(int sum, int count)
      throws IntegerDivisionByZero {
    // See Example 7.9.
  }
}

A few remarks are in order regarding the alternatives of a multi-catch clause. There should be no subtype–supertype relationship between any of the specified exception types in the alternatives of a multi-catch clause. The following multi-catch clause will not compile, as ArrayIndexOutOfBoundsException is a subtype of IndexOutOfBoundsException:

Click here to view code image

catch (IndexOutOfBoundsException |                  // Compile-time error!
       ArrayIndexOutOfBoundsException e) {
  // …
}

The parameter of a multi-catch clause is also considered to be implicitly final, and therefore cannot be assigned to in the body of the multi-catch clause. In a uni-catch clause, the parameter is considered to be effectively final if it does not occur on the left-hand side of an assignment in the body of the uni-catch clause.

Click here to view code image

try {
  // Assume appropriate code to throw the right exceptions.
} catch (NumberFormatException |
         IndexOutOfBoundsException e) {    // Parameter is final.
  e = new ArrayIndexOutOfBoundsException();// Compile-time error!
                                           // Cannot assign to final parameter e.
} catch (IntegerDivisionByZero idbz) {     // Parameter is effectively final.
  idbz.printStackTrace();
} catch (IOException ioe) {                // Parameter is not effectively final.
  ioe = new FileNotFoundException(“No file.”);
}

Disallowing any subtype–supertype relationship between alternatives and the parameter being final in a multi-catch clause or effectively final in a uni-catch clause allows the compiler to perform precise exception handling analysis.

The compiler also generates effective bytecode for a single exception handler corresponding to all the alternatives in a multi-catch clause, in contrast to generating bytecode for multiple exception handlers for uni-catch clauses that correspond to the multi-catch clause.

Posted on Leave a comment

Compiling Code into Package Directories – Access Control

Compiling Code into Package Directories

Conventions for specifying pathnames vary on different platforms. In this chapter, we will use pathname conventions used on a Unix-based platform. While trying out the examples in this section, attention should be paid to platform dependencies in this regard—especially the fact that the separator characters in file paths for the Unix-based and Windows platforms are / and \, respectively.

As mentioned earlier, a package can be mapped on a hierarchical file system. We can think of a package name as a pathname in the file system. Referring to Example 6.1, the package name wizard.pandorasbox corresponds to the pathname wizard/ pandorasbox. The Java bytecode for all types declared in the source files Clown.java and LovePotion.java will be placed in the package directory with the pathname wizard/pandorasbox, as these source files have the following package declaration:

package wizard.pandorasbox;

The location in the file system where the package directory should be created is specified using the -d option (d for destination) of the javac command. The term destination directory is a synonym for this location in the file system. The compiler will create the package directory with the pathname wizard/pandorasbox (including any subdirectories required) under the specified location, and will place the Java bytecode for the types declared in the source files Clown.java and LovePotion.java inside the package directory.

Assuming that the current directory (.) is the directory /pgjc/work, and the four source code files in Figure 6.3a (see also Example 6.1) are found in this directory, the following command issued in the current directory will create a file hierarchy (Figure 6.3b) under this directory that mirrors the package structure in Figure 6.2, p. 327:

Click here to view code image

>
javac -d . Clown.java LovePotion.java Ailment.java Baldness.java

Note that two of the source code files in Figure 6.3a have multiple type declarations. Note also the subdirectories that are created for a fully qualified package name, and where the class files are located. In this command line, the space between the -d option and its argument is mandatory.

  

Figure 6.3 Compiling Code into Package Directories

The wildcard * can be used to specify all Java source files to be compiled from a directory. It expands to the names of the Java source files in that directory. The two commands below are equivalent to the command above.

>
javac -d . *.java
>
javac -d . ./*.java

We can specify any relative pathname that designates the destination directory, or its absolute pathname:

Click here to view code image

>
javac -d /pgjc/work Clown.java LovePotion.java Ailment.java Baldness.java

We can, of course, specify destinations other than the current directory where the class files with the bytecode should be stored. The following command in the current directory /pgjc/work will create the necessary packages with the class files under the destination directory /pgjc/myapp:

Click here to view code image

>
javac -d ../myapp Clown.java LovePotion.java Ailment.java Baldness.java

Without the -d option, the default behavior of the javac compiler is to place all class files directly under the current directory (where the source files are located), rather than in the appropriate subdirectories corresponding to the packages.

The compiler will report an error if there is any problem with the destination directory specified with the -d option (e.g., if it does not exist or does not have the right file permissions).

Posted on Leave a comment

Exception Types – Exception Handling

7.2 Exception Types

Exceptions in Java are objects. All exceptions are derived from the java.lang.Throwable class. Figure 7.3 shows a partial hierarchy of classes derived from the Throwable class. The two main subclasses Exception and Error constitute the main categories of throwables, the term used to refer to both exceptions and errors. Figure 7.3 also shows that not all exception classes are found in the java.lang package.

Figure 7.3 Partial Exception Inheritance Hierarchy

All throwable classes in the Java SE Platform API at least define a zero-argument constructor and a one-argument constructor that takes a String parameter. This parameter can be set to provide a detail message when an exception is constructed. The purpose of the detail message is to provide more information about the actual exception.

Throwable()
Throwable(String msg)

The first constructor constructs a throwable that has null as its detail message. The second constructor constructs a throwable that sets the specified string as its detail message.

Most exception types provide analogous constructors.

The class Throwable provides the following common methods to query an exception:

String getMessage()

Returns the detail message.

void printStackTrace()

Prints the stack trace on the standard error stream. The stack trace comprises the method invocation sequence on the JVM stack when the exception was thrown. The stack trace can also be written to a PrintStream or a PrintWriter by supplying such a destination as an argument to one of the two overloaded printStackTrace() methods. Any suppressed exceptions associated with an exception on the stack trace are also printed (p. 415). It will also print the cause of an exception (which is also an exception) if one is available (p. 405).

String toString()

Returns a short description of the exception, which typically comprises the class name of the exception together with the string returned by the getMessage() method.

In dealing with throwables, it is important to recognize situations in which a particular throwable can occur, and the source that is responsible for throwing it. By source we mean:

  • The JVM that is responsible for throwing the throwable, or
  • The throwable that is explicitly thrown programmatically by the code in the application or by any API used by the application.

In further discussion of exception types, we provide an overview of situations in which selected throwables can occur and the source responsible for throwing them.

Posted on Leave a comment

Importing Static Members of Reference Types – Access Control

Importing Static Members of Reference Types

Analogous to the type import facility, Java also allows import of static members of reference types from packages, often called static import. Imported static members can be used by their simple names, and therefore need not be qualified. Importing static members of reference types from the unnamed package is not permissible.

The two forms of static import are shown here:

  • Single static import: imports a specific static member from the designated type

import static fully_qualified_type_name.static_member_name;

  • Static import on demand: imports all static members in the designated type

import static fully_qualified_type_name.*;

Both forms require the use of the keyword import followed by the keyword static, although the feature is called static import. In both cases, the fully qualified name of the reference type we are importing from is required.

The first form allows single static import of individual static members, and is demonstrated in Example 6.2. The constant PI, which is a static field in the class java.lang.Math, is imported at (1). Note the use of the fully qualified name of the type in the static import statement. The static method named sqrt from the class java.lang.Math is imported at (2). Only the name of the static method is specified in the static import statement; no parameters are listed. Use of any other static member from the Math class requires that the fully qualified name of the class be specified. Since types from the java.lang package are imported implicitly, the fully qualified name of the Math class is not necessary, as shown at (3).

Static import on demand is easily demonstrated by replacing the two import statements in Example 6.2 with the following import statement:

Click here to view code image

import static java.lang.Math.*;

We can also dispense with the use of the class name Math at (3), as all static members from the Math class are now imported:

Click here to view code image

double hypotenuse = hypot(x, y);   // (3′) Type name can now be omitted.

Example 6.2 Single Static Import

Click here to view code image

import static java.lang.Math.PI;           // (1) Static field
import static java.lang.Math.sqrt;         // (2) Static method
// Only specified static members are imported.
public class Calculate3 {
  public static void main(String[] args) {
    double x = 3.0, y = 4.0;
double squareroot = sqrt(y);           // Simple name of static method
    double hypotenuse = Math.hypot(x, y);  // (3) Requires type name
    double area = PI * y * y;              // Simple name of static field
    System.out.printf(“Square root: %.2f, hypotenuse: %.2f, area: %.2f%n”,
                        squareroot, hypotenuse, area);
  }
}

Output from the program:

Click here to view code image

Square root: 2.00, hypotenuse: 5.00, area: 50.27

Example 6.3 illustrates how static import can be used to access interface constants (§5.6, p. 254). The static import statement at (1) allows the interface constants in the package mypkg to be accessed by their simple names. The static import facility avoids the MyFactory class having to implement the interface so as to access the constants by their simple name (often referred to as the interface constant antipattern):

Click here to view code image

public class MyFactory implements mypkg.IMachineState {
 // …
}

Example 6.3 Avoiding the Interface Constant Antipattern

Click here to view code image

package mypkg;
public interface IMachineState {
  // Fields are public, static, and final.
  int BUSY = 1;
  int IDLE = 0;
  int BLOCKED = -1;
}

Click here to view code image

import static mypkg.IMachineState.*;    // (1) Static import interface constants
public class MyFactory {
  public static void main(String[] args) {
    int[] states = { IDLE, BUSY, IDLE, BLOCKED }; // (2) Access by simple name
    for (int s : states)
      System.out.print(s + ” “);
  }
}

Output from the program:

0 1 0 -1

Static import is ideal for importing enum constants from packages, as such constants are static members of an enum type (§5.13, p. 287). Example 6.4 combines type and static imports. The enum constants can be accessed at (5) using their simple names because of the static import statement at (2). The type import at (1) is required to access the enum type State by its simple name at (4) and (6).

Example 6.4 Importing Enum Constants

Click here to view code image

package mypkg;
public enum State { BUSY, IDLE, BLOCKED }

Click here to view code image

// File: Factory.java (in unnamed package)
import mypkg.State;                  // (1) Single type import
import static mypkg.State.*;         // (2) Static import on demand
import static java.lang.System.out;  // (3) Single static import
public class Factory {
  public static void main(String[] args) {
    State[] states = {               // (4) Using type import implied by (1)
        IDLE, BUSY, IDLE, BLOCKED    // (5) Using static import implied by (2)
    };
    for (State s : states)           // (6) Using type import implied by (1)
      out.print(s + ” “);            // (7) Using static import implied by (3)
  }
}

Output from the program:

IDLE BUSY IDLE BLOCKED

Identifiers in a class can shadow static members that are imported. Example 6.5 illustrates the case where the parameter out of the method writeInfo() has the same name as the statically imported field java.lang.System.out. The type of the parameter out is ShadowImport and that of the statically imported field out is PrintStream. Both classes PrintStream and ShadowImport define the method println() that is called in the program. The only way to access the imported field out in the method write-Info() is to use its fully qualified name.

Example 6.5 Shadowing Static Import

Click here to view code image

import static java.lang.System.out;       // (1) Static import
public class ShadowImport {
  public static void main(String[] args) {
    out.println(“Calling println() in java.lang.System.out”);
    ShadowImport sbi = new ShadowImport();
    writeInfo(sbi);
  }
// Parameter out shadows java.lang.System.out:
  public static void writeInfo(ShadowImport out) {
    out.println(“Calling println() in the parameter out”);
    System.out.println(“Calling println() in java.lang.System.out”); // Qualify
  }
  public void println(String msg) {
    out.println(msg + ” of type ShadowImport”);
  }
}

Output from the program:

Click here to view code image

Calling println() in java.lang.System.out
Calling println() in the parameter out of type ShadowImport
Calling println() in java.lang.System.out

The next code snippet illustrates a common conflict that occurs when a static field with the same name is imported by several static import statements. This conflict is readily resolved by using the fully qualified name of the field. In the case shown here, we can use the simple name of the class in which the field is declared, as the java.lang package is implicitly imported by all compilation units.

Click here to view code image

import static java.lang.Integer.MAX_VALUE;
import static java.lang.Double.MAX_VALUE;
public class StaticFieldConflict {
  public static void main(String[] args) {
    System.out.println(MAX_VALUE);          // (1) Ambiguous! Compile-time error!
    System.out.println(Integer.MAX_VALUE);  // OK
    System.out.println(Double.MAX_VALUE);   // OK
  }
}

Conflicts can also occur when a static method with the same signature is imported by several static import statements. In Example 6.6, a method named binarySearch is imported 21 times by the static import statements. This method is overloaded twice in the java.util.Collections class and 18 times in the java.util.Arrays class, in addition to one declaration in the mypkg.Auxiliary class. The classes java.util.Arrays and mypkg.Auxiliary have a declaration of this method with the same signature (binarySearch(int[], int) that matches the method call at (2), resulting in a signature conflict that is flagged as a compile-time error. The conflict can again be resolved by specifying the fully qualified name of the method.

If the static import statement at (1) is removed, there is no conflict, as only the class java.util.Arrays has a method that matches the method call at (2). If the declaration of the method binarySearch() at (3) is allowed, there is also no conflict, as this method declaration will shadow the imported method whose signature it matches.

Example 6.6 Conflict in Importing a Static Method with the Same Signature

Click here to view code image

package mypkg;
public class Auxiliary {
  public static int binarySearch(int[] a, int key) { // Same in java.util.Arrays
    // Implementation is omitted.
    return -1;
  }
}

Click here to view code image

// File: MultipleStaticImport.java (in unnamed package)
import static java.util.Collections.binarySearch;  //    2 overloaded methods
import static java.util.Arrays.binarySearch;       // + 18 overloaded methods
import static mypkg.Auxiliary.binarySearch; // (1) Causes signature conflict
public class MultipleStaticImport {
  public static void main(String[] args) {
    int index = binarySearch(new int[] {10, 50, 100}, 50); // (2) Ambiguous!
    System.out.println(index);
  }
//public static int binarySearch(int[] a, int key) {       // (3)
//  return -1;
//}
}

Posted on Leave a comment

Searching for Classes on the Class Path – Access Control

6.4 Searching for Classes on the Class Path

A program typically uses other precompiled classes and libraries, in addition to the ones provided by the Java standard libraries. In order for the JDK tools to find these files efficiently, the CLASSPATH environment variable or the -classpath option can be used, both of which are explained below.

In particular, the CLASSPATH environment variable can be used to specify the class search path (usually abbreviated to just class path), which is the pathnames or locations in the file system where JDK tools should look when searching for third-party and user-defined classes. Alternatively, the -classpath option (short form -cp) of the JDK tool commands can be used for the same purpose. The CLASSPATH environment variable is not recommended for this purpose, as its class path value affects all Java applications on the host platform, and any application can modify it. However, the -classpath option can be used to set the class path for each application individually. This way, an application cannot modify the class path for other applications. The class path specified in the -classpath option supersedes the path or paths set by the CLASSPATH environment variable while the JDK tool command is running. We will not discuss the CLASSPATH environment variable here, and will assume it to be undefined.

Basically, the JDK tools first look in the directories where the Java standard libraries are installed. If the class is not found in the standard libraries, the tool searches in the class path. When no class path is defined, the default value of the class path is assumed to be the current directory. If the -classpath option is used and the current directory should be searched by the JDK tool, the current directory must be specified as an entry in the class path, just like any other directory that should be searched. This is most conveniently done by including ‘.’ as one of the entries in the class path.

We will use the file hierarchies shown in Figure 6.4 to illustrate some of the intricacies involved when searching for classes. The current directory has the absolute pathname /top/src, where the source files are stored. The package pkg will be created under the directory with the absolute pathname /top/bin. The source code in the two source files A.java and B.java is also shown in Figure 6.4.

Figure 6.4 Searching for Classes

The file hierarchy before any files are compiled is shown in Figure 6.4a. Since the class B does not use any other classes, we compile it first with the following command, resulting in the file hierarchy shown in Figure 6.4b:

>javac -d ../bin B.java

Next, we try to compile the file A.java, and we get the following results:

Click here to view code image

>
javac -d ../bin A.java

A.java:3: cannot find symbol
symbol  : class B
location: class pkg.A
public class A { B b; }
                 ^
1 error

Posted on Leave a comment

Stack-Based Execution and Exception Propagation – Exception Handling

7.1 Stack-Based Execution and Exception Propagation

The exception mechanism is built around the throw-and-catch paradigm. To throw an exception is to signal that an unexpected event has occurred. To catch an exception is to take appropriate action to deal with the exception. An exception is caught by an exception handler, and the exception need not be caught in the same context in which it was thrown. The runtime behavior of the program determines which exceptions are thrown and how they are caught. The throw-and-catch principle is embedded in the try-catch-finally construct (p. 375).

Several threads can be executing at the same time in the JVM (§22.2, p. 1369). Each thread has its own JVM stack (also called a runtime stack, call stack, or invocation stack in the literature) that is used to handle execution of methods. Each element on the stack is called an activation frame or a stack frame and corresponds to a method call. Each new method call results in a new activation frame being pushed on the stack, which stores all the pertinent information such as the local variables. The method with the activation frame on the top of the stack is the one currently executing. When this method finishes executing, its activation frame is popped from the top of the stack. Execution then continues in the method corresponding to the activation frame that is now uncovered on the top of the stack. The methods on the stack are said to be active, as their execution has not completed. At any given time, the active methods on a JVM stack make up what is called the stack trace of a thread’s execution.

Example 7.1 is a simple program to illustrate method execution. It calculates the average for a list of integers, given the sum of all the integers and the number of integers. It uses three methods:

  • The method main() calls the method printAverage() with parameters supplying the total sum of the integers and the total number of integers, (1).
  • The method printAverage() in turn calls the method computeAverage(), (3).
  • The method computeAverage() uses integer division to calculate the average and returns the result, (7).

Example 7.1 Method Execution

Click here to view code image

public class Average1 {
  public static void main(String[] args) {
    printAverage(100, 20);                                         // (1)
System.out.println(“Exit main().”);                            // (2)
  }
  public static void printAverage(int totalSum, int totalCount) {
    int average = computeAverage(totalSum, totalCount);            // (3)
    System.out.println(“Average = ” +                              // (4)
        totalSum + ” / ” + totalCount + ” = ” + average);
    System.out.println(“Exit printAverage().”);                    // (5)
  }
  public static int computeAverage(int sum, int count) {
    System.out.println(“Computing average.”);                      // (6)
    return sum/count;                                              // (7)
  }
}

Output of program execution:

Click here to view code image

Computing average.
Average = 100 / 20 = 5
Exit printAverage().
Exit main().

Execution of Example 7.1 is illustrated in Figure 7.1. Each method execution is shown as a box with the local variables declared in the method. The height of the box indicates how long a method is active. Before the call to the method System.out.println() at (6) in Figure 7.1, the stack trace comprises the three active methods: main(), printAverage(), and computeAverage(). The result 5 from the method computeAverage() is returned at (7) in Figure 7.1. The output from the program corresponds with the sequence of method calls in Figure 7.1. As the program terminates normally, this program behavior is called normal execution.

If the method call at (1) in Example 7.1

Click here to view code image

printAverage(100, 20);                                // (1)

is replaced with

Click here to view code image

printAverage(100, 0);                                 // (1)

and the program is run again, the output is as follows:

Click here to view code image

Computing average.
Exception in thread “main” java.lang.ArithmeticException: / by zero
        at Average1.computeAverage(Average1.java:18)
        at Average1.printAverage(Average1.java:10)
        at Average1.main(Average1.java:5)

Figure 7.2 illustrates the program execution when the method printAverage() is called with the arguments 100 and 0 at (1). All goes well until the return statement at (7) in the method computeAverage() is executed. An error event occurs in calculating the expression sum/number because integer division by 0 is an illegal operation. This event is signaled by the JVM by throwing an ArithmeticException (p. 372). This exception is propagated by the JVM through the JVM stack as explained next.

Figure 7.1 Normal Method Execution

Figure 7.2 illustrates the case where an exception is thrown and the program does not take any explicit action to deal with the exception. In Figure 7.2, execution of the computeAverage() method is suspended at the point where the exception is thrown. The execution of the return statement at (7) never gets completed. Since this method does not have any code to deal with the exception, its execution is likewise terminated abruptly and its activation frame popped. We say that the method completes abruptly. The exception is then offered to the method whose activation is now on the top of the stack (printAverage()). This method does not have any code to deal with the exception either, so its execution completes abruptly. The statements at (4) and (5) in the method printAverage() never get executed. The exception now propagates to the last active method (main()). This does not deal with the exception either. The main() method also completes abruptly. The statement at (2) in the main() method never gets executed. Since the exception is not caught by any of the active methods, it is dealt with by the main thread’s default exception handler. The default exception handler usually prints the name of the exception, with an explanatory message, followed by a printout of the stack trace at the time the exception was thrown. An uncaught exception, as in this case, results in the death of the thread in which the exception occurred.

Figure 7.2 Exception Propagation

If an exception is thrown during the evaluation of the left-hand operand of a binary expression, then the right-hand operand is not evaluated. Similarly, if an exception is thrown during the evaluation of a list of expressions (e.g., a list of actual parameters in a method call), evaluation of the rest of the list is skipped.

If the line numbers in the stack trace are not printed in the output as shown previously, use the following command to run the program:

Click here to view code image >
java -Djava.compiler=NONE Average1

Posted on Leave a comment

Using Packages – Access Control

Using Packages

The import facility in Java makes it easier to use the contents of packages. This subsection discusses importing reference types and static members of reference types from packages.

Importing Reference Types

The accessibility of types (classes, interfaces, and enums) in a package determines their access from other packages. Given a reference type that is accessible from outside a package, the reference type can be accessed in two ways. One way is to use the fully qualified name of the type. However, writing long names can become tedious. The second way is to use the import declaration that provides a shorthand notation for specifying the name of the type, often called type import.

The import declarations must be the first statement after any package declaration in a source file. The simple form of the import declaration has the following syntax:

Click here to view code image

import
fully_qualified_type_name
;

This is called single-type-import. As the name implies, such an import declaration provides a shorthand notation for a single type. The simple name of the type (i.e., its identifier) can now be used to access this particular type. Given the import declaration

Click here to view code image

import wizard.pandorasbox.Clown;

the simple name Clown can be used in the source file to refer to this class.

Alternatively, the following form of the import declaration can be used:

Click here to view code image

import
fully_qualified_package_name
.*;

This is called type-import-on-demand. It allows any type from the specified package to be accessed by its simple name. Given the import declaration

Click here to view code image

import wizard.pandorasbox.*;

the classes Clown and LovePotion and the interface Magic that are in the package wizard.pandorasbox can be accessed by their simple name in the source file.

An import declaration does not recursively import subpackages, as such nested packages are autonomous packages. The declaration also does not result in inclusion of the source code of the types; rather, it simply imports type names (i.e., it makes type names available to the code in a compilation unit).

All compilation units implicitly import the java.lang package (§8.1, p. 425). This is the reason why we can refer to the class String by its simple name, and need not use its fully qualified name java.lang.String all the time.

Import statements are not present in the compiled code, as all type names in the source code are replaced with their fully qualified names by the compiler.

Example 6.1 shows several usages of the import statement. Here we will draw attention to the class Baldness in the file Baldness.java. This class relies on two classes that have the same simple name LovePotion but are in different packages: wizard.pandorasbox and wizard.spells. To distinguish between the two classes, we can use their fully qualified names. However, since one of them is in the same package as the class Baldness, it is enough to fully qualify the class from the other package. This solution is used in Example 6.1 at (3). Note that the import of the wizard.pandorasbox package at (1) becomes redundant. Such name conflicts can usually be resolved by using variations of the import declaration together with fully qualified names.

The class Baldness extends the class Ailment, which is in the subpackage artifacts of the wizard.pandorasbox package. The import declaration at (2) is used to import the types from the subpackage artifacts.

The following example shows how a single-type-import declaration can be used to disambiguate a type name when access to the type is ambiguous by its simple name. The following import statement allows the simple name List to be used as shorthand for the java.awt.List type as expected:

Click here to view code image

import java.awt.*;           // imports all reference types from java.awt

Given the two import declarations

Click here to view code image

import java.awt.*;           // imports all type names from java.awt
import java.util.*;          // imports all type names from java.util

the simple name List is now ambiguous because both the types java.util.List and java.awt.List match.

Adding a single-type-import declaration for the java.awt.List type allows the simple name List to be used as a shorthand notation for this type:

Click here to view code image import java.awt.*;           // imports all type names from java.awt
import java.util.*;          // imports all type names from java.util
import java.awt.List;        // imports the type List from java.awt explicitly

Posted on Leave a comment

Checked and Unchecked Exceptions – Exception Handling

Checked and Unchecked Exceptions

Except for RuntimeException, Error, and their subclasses, all exceptions are checked exceptions. A checked exception represents an unexpected event that is not under the control of the program—for example, a file was not found. The compiler ensures that if a method can throw a checked exception, directly or indirectly, the method must either catch the exception and take the appropriate action, or pass the exception on to its caller (p. 388).

Exceptions defined by the Error and RuntimeException classes and their subclasses are known as unchecked exceptions, meaning that a method is not obliged to deal with these kinds of exceptions (shown with gray color in Figure 7.3). Either they are irrecoverable (exemplified by the Error class), in which case the program should not attempt to deal with them, or they are programming errors (exemplified by the RuntimeException class and its subclasses) and should usually be dealt with as such, and not as exceptions.

Defining Customized Exceptions

Customized exceptions are usually defined to provide fine-grained categorization of error situations, instead of using existing exception classes with descriptive detail messages to differentiate among the various situations. New customized exceptions are usually defined by either extending the Exception class or one of its checked subclasses, thereby making the new exceptions checked, or extending the RuntimeException subclass or one of its subclasses to create new unchecked exceptions.

Customized exceptions, as any other Java classes, can declare fields, constructors, and methods, thereby providing more information as to their cause and remedy when they are thrown and caught. The super() call can be used in a constructor to set pertinent details about the exception: a detail message or the cause of the exception (p. 405), or both. Note that the exception class must be instantiated to create an exception object that can be thrown and subsequently caught and dealt with. The following code sketches a class declaration for an exception that can include all pertinent information about the exception. Typically, the new exception class provides a constructor to set the detail message.

Click here to view code image

public class EvacuateException extends Exception {
  // Fields
  private Date date;
  private Zone zone;
  private TransportMode transport;
  // Constructor
  public EvacuateException(Date d, Zone z, TransportMode t) {
    // Call the constructor of the superclass, usually passing a detail message.
    super(“Evacuation of zone ” + z);
    // …
  }
  // Methods
  // …
}

Several examples in subsequent sections illustrate exception handling.

Posted on Leave a comment

Java Source File Structure – Access Control

6.2 Java Source File Structure

The structure of a skeletal Java source file is depicted in Figure 6.1. A Java source file can have the following elements that, if present, must be specified in the following order:

  1. An optional package declaration to specify a package name. Packages are discussed in §6.3, p. 326.
  2. Zero or more import declarations. Since import declarations introduce type or static member names in the source code, they must be placed before any type declarations. Both type and static import declarations are discussed in §6.3, p. 329.
  3. Any number of top-level type declarations. Class, enum, and interface declarations are collectively known as type declarations. Since these declarations belong to the same package, they are said to be defined at the top level, which is the package level.

The type declarations can be defined in any order. Technically, a source file need not have any such declarations, but that is hardly useful.

The JDK imposes the restriction that at most one public class declaration per source file can be defined. If a public class is defined, the file name must match this public class. For example, if the public class name is NewApp, the file name must be NewApp.java.

Classes are discussed in §3.1, p. 99; interfaces are discussed in §5.6, p. 237; and enums are discussed in §5.13, p. 287.

Modules introduce another Java source file that contains a single module declaration (§19.3, p. 1168).

Note that except for the package and the import statements, all code is encapsulated in classes, interfaces, enums, and records. No such restriction applies to comments and whitespace.

Figure 6.1 Java Source File Structure

6.3 Packages

A package in Java is an encapsulation mechanism that can be used to group related classes, interfaces, enums, and records.

Figure 6.2 shows an example of a package hierarchy comprising a package called wizard that contains two other packages: pandorasbox and spells. The package pandorasbox has a class called Clown that implements an interface called Magic, also found in the same package. In addition, the package pandorasbox has a class called LovePotion and a subpackage called artifacts containing a class called Ailment. The package spells has two classes: Baldness and LovePotion. The class Baldness is a subclass of class Ailment found in the subpackage artifacts in the package pandorasbox.

The dot (.) notation is used to uniquely identify package members in the package hierarchy. The class wizard.pandorasbox.LovePotion, for example, is different from the class wizard.spells.LovePotion. The Ailment class can be easily identified by the name wizard.pandorasbox.artifacts.Ailment, which is known as the fully qualified name of the type. Note that the fully qualified name of the type in a named package comprises the fully qualified name of the package and the simple name of the type. The simple type name Ailment and the fully qualified package name wizard.pandorasbox.artifacts together define the fully qualified type name wizard.pandorasbox.artifacts.Ailment.

Java programming environments usually map the fully qualified name of packages to the underlying (hierarchical) file system. For example, on a Unix system, the class file LovePotion.class corresponding to the fully qualified name wizard.pandorasbox.LovePotion would be found under the directory wizard/pandorasbox.

Figure 6.2 Package Structure

Conventionally, the reverse DNS (Domain Name System) notation based on the Internet domain names is used to uniquely identify packages. If the package wizard was implemented by a company called Sorcerers Limited that owns the domain sorcerersltd.com, its fully qualified name would be

com.sorcerersltd.wizard

Because domain names are unique, packages with this naming scheme are globally identifiable. It is not advisable to use the top-level package names java and sun, as these are reserved for the Java designers.

Note that each component of a package name must be a legal Java identifier. The following package would be illegal:

org.covid-19.2022.vaccine

The package name below is legal:

org.covid_19._2022.vaccine

A subpackage would be located in a subdirectory of the directory corresponding to its parent package. Apart from this locational relationship, a subpackage is an independent package with no other relation to its parent package. The subpackage wizard.pandorasbox.artifacts could easily have been placed elsewhere, as long as it was uniquely identified. Subpackages in a package do not affect the accessibility of the other package members. For all intents and purposes, subpackages are more an organizational feature than a language feature. Accessibility of members defined in type declarations is discussed in §6.5, p. 345.