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

Overriding the throws Clause – Exception Handling

Overriding the throws Clause

A subclass can override a method defined in its superclass by providing a new implementation (§5.1, p. 196). What happens when a superclass method with a list of exceptions in its throws clause is overridden in a subclass? The method declaration in the subclass need not specify a throws clause if it does not throw any checked exceptions, and if it does, it can specify only checked exception classes that are already in the throws clause of the superclass method, or that are subclasses of the checked exceptions in the throws clause of the superclass method. As a consequence, an overriding method can have more number of exceptions, but it cannot allow broader checked exceptions in its throws clause than the superclass method does. Allowing broader checked exceptions in the overriding method would create problems for clients who already deal with the exceptions specified in the superclass method. Such clients would be ill prepared if an object of the subclass threw a checked exception they were not prepared for. However, there are no restrictions on specifying unchecked exceptions in the throws clause of the overriding method. The preceding discussion also applies to overriding methods from an interface that a class implements.

In the code below, the method compute() at (1) in superclass A is overridden correctly at (2) in subclass B. The throws clause of the method at (2) in subclass B specifies only one checked exception (ThirdException) from the throws clause at (1) and adds the more specific subclass exception (SubFirstException) of the superclass exception (FirstException) that is specified in the throws clause at (1). An unchecked exception (NumberFormatException) is also specified in the throws clause at (2). The checked exceptions in the throws clause at (2) are covered by the checked exceptions specified in the throws clause at (1). Unchecked exceptions are inconsequential in this regard. The subclass C does not override the compute() method from class A correctly, as the throws clause at (3) specifies an exception (FourthException) that the overridden method at (1) in class A cannot handle.

Click here to view code image

// New exception classes:
class FirstException    extends Exception { }
class SecondException   extends Exception { }
class ThirdException    extends Exception { }
class FourthException   extends Exception { }
class SubFirstException extends FirstException { }
// Superclass
class A {
  protected void compute()
      throws FirstException, SecondException, ThirdException { /* … */ }  // (1)
}
// Subclass
class B extends A {
  @Override
  protected void compute()
      throws ThirdException, SubFirstException, NumberFormatException {     // (2)
    /* … */
  }
}
//Subclass
class C extends A {
  @Override
  protected void compute()                            // Compile-time error at (3)
      throws FirstException, ThirdException, FourthException { /* … */ }  // (3)
}

Usage of checked and unchecked exceptions in different contexts is compared in Table 7.1.

Table 7.1 Comparing Checked and Unchecked Exceptions

Posted on Leave a comment

Scope Rules – Access Control

6.6 Scope Rules

Java provides explicit access modifiers to control the accessibility of members in a class by external clients, but in two areas access is governed by specific scope rules:

  • Class scope for members: how member declarations are accessed within the class
  • Block scope for local variables: how local variable declarations are accessed within a block

Class Scope for Members

Class scope concerns accessing members (including inherited ones) from code within a class. Table 6.4 gives an overview of how static and non-static code in a class can access members of the class, including those that are inherited. Table 6.4 assumes the following declarations:

Click here to view code image

class SuperClass {
  int instanceVarInSuper;
  static int staticVarInSuper;
void instanceMethodInSuper()      { /* … */ }
  static void staticMethodInSuper() { /* … */ }
  // …
}
class MyClass extends SuperClass {
  int instanceVar;
  static int staticVar;
  void instanceMethod()      { /* … */ }
  static void staticMethod() { /* … */ }
  // …
}

Table 6.4 Accessing Members within a Class

Member declarationsNon-static code in the class MyClass can refer to the member asStatic code in the class MyClass can refer to the member as
Instance variablesinstanceVar this.instanceVar instanceVarInSuper this.instanceVarInSuper super.instanceVarInSuperNot possible
Instance methodsinstanceMethod() this.instanceMethod() instanceMethodInSuper() this.instanceMethodInSuper() super.instanceMethodInSuper()Not possible
Static variablesstaticVar this.staticVar MyClass.staticVar staticVarInSuper this.staticVarInSuper super.staticVarInSuper MyClass.staticVarInSuper SuperClass.staticVarInSuperstaticVar MyClass.staticVar staticVarInSuper MyClass.staticVarInSuper SuperClass.staticVarInSuper
Static methodsstaticMethod() this.staticMethod() MyClass.staticMethod() staticMethodInSuper() this.staticMethodInSuper() super.staticMethodInSuper() MyClass.staticMethodInSuper() SuperClass.staticMethodInSuper()staticMethod() MyClass.staticMethod() staticMethodInSuper() MyClass.staticMethodInSuper() SuperClass.staticMethodInSuper()

The golden rule is that static code can only access other static members by their simple names. Static code is not executed in the context of an object, so the references this and super are not available. An object has knowledge of its class, so static members are always accessible in a non-static context.

Note that using the class name to access static members within the class is no different from how external clients access these static members.

The following factors can all influence the scope of a member declaration:

  • Shadowing of a field declaration, either by local variables (p. 354) or by declarations in the subclass (§5.1, p. 203)
  • Overriding an instance method from a superclass (§5.1, p. 196)
  • Hiding a static method declared in a superclass (§5.1, p. 203)

Within a class, references of the class can be declared and used to access all members in the class, regardless of their access modifiers. In Example 6.9, the method duplicateLight at (1) in the class Light has the parameter oldLight and the local variable newLight that are references of the class Light. Even though the fields of the class are private, they are accessible through the two references (oldLight and newLight) in the method duplicateLight(), as shown at (2), (3), and (4).

Example 6.9 Class Scope

Click here to view code image

class Light {
  // Instance variables:
  private int     noOfWatts;       // Wattage
  private boolean indicator;       // On or off
  private String  location;        // Placement
  // Instance methods:
  public void switchOn()  { indicator = true; }
  public void switchOff() { indicator = false; }
  public boolean isOn()   { return indicator; }
  public static Light duplicateLight(Light oldLight) {     // (1)
    Light newLight = new Light();
    newLight.noOfWatts = oldLight.noOfWatts;               // (2)
    newLight.indicator = oldLight.indicator;               // (3)
    newLight.location  = oldLight.location;                // (4)
    return newLight;
  }
}

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

Access Modifiers – Access Control

6.5 Access Modifiers

In this section, we discuss accessibility of top-level type declarations that can be encapsulated into packages and accessibility of members that can be encapsulated in a top-level type declaration. A top-level reference type is a reference type (class, interface, enum, record) that is not declared inside another reference type.

Access modifiers are sometimes also called visibility modifiers.

Access Modifiers for Top-Level Type Declarations

The access modifier public can be used to declare top-level reference types that are accessible from everywhere, both from inside their own package and from inside other packages. If the access modifier is omitted, the reference types can be accessed only in their own package and not in any other packages—that is, they have package access, also called package-private or default access.

The packages shown in Figure 6.2, p. 327, are implemented by the code in Example 6.7. Class files with Java bytecode for top-level type declarations are placed in designated packages using the package statement. A top-level type declaration from one package can be accessed in another packages either by using the fully qualified name of the type or by using an import statement to import the type so that it can be accessed by its simple name.

Example 6.7 Access Modifiers for Top-Level Reference Types

Click here to view code image

// File: Clown.java
package wizard.pandorasbox;                  // Package declaration
import wizard.pandorasbox.artifacts.Ailment; // Importing class Ailment
public class Clown implements Magic {        // (1)
  LovePotion tlc;                            // Class in same package
  Ailment problem;                           // Simple class name
  Clown() {
    tlc = new LovePotion(“passion”);
    problem = new Ailment(“flu”);            // Simple class name
  }
  @Override public void levitate()  {        // (2)
    System.out.println(“Levitating”);
  }
  public void mixPotion()   { System.out.println(“Mixing ” + tlc); }
  public void healAilment() { System.out.println(“Healing ” + problem); }
  public static void main(String[] args) {
    Clown joker = new Clown();
    joker.levitate();
    joker.mixPotion();
    joker.healAilment();
  }
}
interface Magic { void levitate(); }         // (3)

Click here to view code image

// File: LovePotion.java
package wizard.pandorasbox;                  // Package declaration

public class LovePotion {                    // (4) Accessible outside package
  String potionName;
  public LovePotion(String name) { potionName = name; }
  public String toString()       { return potionName; }
}

Click here to view code image

// File: Ailment.java
package wizard.pandorasbox.artifacts;        // Package declaration

public class Ailment {                       // Accessible outside package
  String ailmentName;
  public Ailment(String name) { ailmentName = name; }
  public String toString() { return ailmentName; }
}

Click here to view code image

// File: Baldness.java
package wizard.spells;                       // Package declaration

import wizard.pandorasbox.*;                 // Redundant
import wizard.pandorasbox.artifacts.*;       // Import of subpackage

public class Baldness extends Ailment {      // Simple name for Ailment
  wizard.pandorasbox.LovePotion tlcOne;      // Fully qualified name
  LovePotion tlcTwo;                         // Class in same package
  Baldness(String name) {
    super(name);
    tlcOne = new wizard.pandorasbox.         // Fully qualified name
                 LovePotion(“romance”);
    tlcTwo = new LovePotion();               // Class in same package
  }
}
class LovePotion /* implements Magic */ {    // (5) Magic is not accessible
  // @Override public void levitate() {}     // (6) Cannot override method
}

Compiling and running the program from the current directory gives the following results:

Click here to view code image

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

Levitating
Mixing passion
Healing flu

In Example 6.7, the class Clown at (1) and the interface Magic at (3) are placed in a package called wizard.pandorasbox. The public class Clown is accessible from everywhere. The Magic interface has package accessibility, and can only be accessed within the package wizard.pandorasbox. It is not accessible from other packages, not even from subpackages.

The class LovePotion at (4) is also placed in the package called wizard.pandorasbox. The class has public accessibility, and is therefore accessible from other packages. The two files Clown.java and LovePotion.java demonstrate how several compilation units can be used to group classes in the same package, as the type declarations in these two source files are placed in the package wizard.pandorasbox.

In the file Clown.java, the class Clown at (1) implements the interface Magic at (3) from the same package. We have used the annotation @Override in front of the declaration of the levitate() method at (2) so that the compiler can aid in checking that this method is declared correctly as required by the interface Magic.

In the file Baldness.java, the class LovePotion at (5) wishes to implement the interface Magic at (3) from the package wizard.pandorasbox, but this is not possible, although the source file imports from this package. The reason is that the interface Magic has package accessibility, and can therefore only be accessed within the package wizard.pandorasbox. The method levitate() of the Magic interface therefore cannot be overridden in class LovePotion at (6).

Table 6.2 summarizes accessibility of top-level reference types in a package. Just because a reference type is accessible does not necessarily mean that members of the type are also accessible. Accessibility of members is governed separately from type accessibility, as explained in the next subsection.

Table 6.2 Access Modifiers for Top-Level Reference Types (Non-Modular)

ModifiersTop-level types
No modifierAccessible in its own package (package accessibility)
publicAccessible anywhere
Posted on Leave a comment

Access Modifiers for Class Members – Access Control

Access Modifiers for Class Members

By specifying member access modifiers, a class can control which information is accessible to clients (i.e., other classes). These modifiers help a class define a contract so that clients know exactly which services are offered by the class.

The accessibility of a member in a class can be any one of the following:

  • public
  • protected
  • package access (also known as package-private and default access), when no access modifier is specified
  • private

In the following discussion of access modifiers for members of a class, keep in mind that the member access modifier has meaning only if the class (or one of its subclasses) is accessible to the client. Also, note that only one access modifier can be specified for a member.

The discussion in this subsection applies to both instance and static members of top-level classes.

In UML notation, when applied to member names the prefixes +, #, and – indicate public, protected, and private member access, respectively. No access modifier indicates package access for class members.

The package hierarchy shown in Figure 6.5 is implemented by the code in Example 6.8. The class Superclass1 at (1) in pkg1 has two subclasses: Subclass1 at (3) in pkg1 and Subclass2 at (5) in pkg2. The class Superclass1 in pkg1 is used by the other classes (designated as Client 1 to Client 4) in Figure 6.5.

  

Figure 6.5 Accessibility of Class Members

Accessibility of a member is illustrated in Example 6.8 by the four instance fields defined in the class Superclass1 in pkg1, where each instance field has a different accessibility. These four instance fields are accessed in a Superclass1 object created in the static method printState1() declared in five different contexts:

  • Defining class: The class in which the member is declared—that is, pkg1.Superclass1 in which the four instance fields being accessed are declared
  • Client 1: From a subclass in the same package—that is, pkg1.Subclass1
  • Client 2: From a non-subclass in the same package—that is, pkg1.NonSubclass1
  • Client 3: From a subclass in another package—that is, pkg2.Subclass2
  • Client 4: From a non-subclass in another package—that is, pkg2.NonSubclass2

Example 6.8 Accessibility of Class Members

Click here to view code image

// File: Superclass1.java
package pkg1;

import static java.lang.System.out;
public class Superclass1 {                       // (1)
  // Instance fields with different accessibility:
  public    int     pubInt   = 2017;
  protected String  proStr   = “SuperDude”;
            boolean pgkBool  = true;
  private   long    privLong = 0x7777;
  public static void printState1() {             // (2)
    Superclass1 obj1 = new Superclass1();
    out.println(obj1.pubInt);
    out.println(obj1.proStr);
    out.println(obj1.pgkBool);
    out.println(obj1.privLong);
  }
}
// Client 1
class Subclass1 extends Superclass1 {            // (3)
  public static void printState1() {

    Superclass1 obj1 = new Superclass1();
    out.println(obj1.pubInt);
    out.println(obj1.proStr);
    out.println(obj1.pgkBool);
    out.println(obj1.privLong);  // Compile-time error! Private access.
  }
}
// Client 2
class NonSubclass1 {                             // (4)
  public static void printState1() {

    Superclass1 obj1 = new Superclass1();
    out.println(obj1.pubInt);
    out.println(obj1.proStr);
    out.println(obj1.pgkBool);
    out.println(obj1.privLong);  // Compile-time error! Private access.
  }
}

Click here to view code image

// File: Subclass2.java
package pkg2;
import pkg1.Superclass1;
import static java.lang.System.out;
// Client 3
public class Subclass2 extends Superclass1 {     // (5)
  public static void printState1() {             // (6)
    Superclass1 obj1 = new Superclass1();        // Object of Superclass1
    out.println(obj1.pubInt);
    out.println(obj1.proStr);   // (7) Compile-time error! Protected access.
    out.println(obj1.pgkBool);  // Compile-time error! Package access.
    out.println(obj1.privLong); // Compile-time error! Private access.
  }
  public static void printState2() {             // (8)
    Subclass2 obj2 = new Subclass2();            // (9) Object of Subclass2
    out.println(obj2.pubInt);
    out.println(obj2.proStr);   // (10) OK! Protected access.
    out.println(obj2.pgkBool);  // Compile-time error! Package access.
    out.println(obj2.privLong); // Compile-time error! Private access.
  }
}
// Client 4
class NonSubclass2 {                             // (11)
  public static void printState1() {
    Superclass1 obj1 = new Superclass1();
    out.println(obj1.pubInt);
    out.println(obj1.proStr);   // Compile-time error! Protected access.
    out.println(obj1.pgkBool);  // Compile-time error! Package access.
    out.println(obj1.privLong); // Compile-time error! Private access.
  }
}

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

Design Principle: Encapsulation – Access Control

6.1 Design Principle: Encapsulation

An object has properties and behaviors that are encapsulated inside the object. The services that the object offers to its clients make up its contract, or public interface. Only the contract defined by the object is available to the clients. The implementation of the object’s properties and behavior is not a concern of the clients. Encapsulation helps to make clear the distinction between an object’s contract and its implementation. This demarcation has major consequences for program development, as the implementation of an object can change without affecting the clients. Encapsulation also reduces complexity, as the internals of an object are hidden from the clients, which cannot alter its implementation.

Encapsulation is achieved through information hiding, by making judicious use of language features provided for this purpose. Information hiding in Java can be achieved at the following levels of granularity:

  • Method or block declaration

Localizing information in a method or a block hides it from the outside. Local variables can only be accessed in the method or the block according to their scope.

  • Reference type declaration

The accessibility of members declared in a reference type declaration (class, enum type, interface) can be controlled through access modifiers (p. 347). One much-advocated information-hiding practice is to prevent clients from having direct access to data maintained by an object. The fields of the object are private, and the object’s contract defines public methods for the services provided by the object. Such tight encapsulation helps to separate the use from the implementation of a reference type.

  • Package declaration

Top-level reference types that belong together can be grouped into relevant packages by using the package statement. An import statement can be used to access types in other packages by their simple name. Inter-package accessibility of types can be controlled through public or package accessibility (p. 345).

  • Module declaration

Packages that are related can be grouped into a module by using a module declaration. How modules are created, accessed, compiled, and deployed is discussed in detail in Chapter 19, p. 1161.

Ample examples throughout the book illustrate how encapsulation can be achieved at different levels by using features provided by the Java programming language.

Posted on Leave a comment

Deriving Permitted Direct Subtypes of a Sealed Supertype – Object-Oriented Programming

Deriving Permitted Direct Subtypes of a Sealed Supertype

The permits clause of a sealed class or interface can be omitted, if all its permitted direct subtypes are declared in the same compilation unit—that is, are in the same source file. Since only one reference type can be declared public in a compilation unit, the accessibility of the other type declarations cannot be public.

The compiler can infer the permitted direct subtypes of any sealed class or interface in a compilation unit by checking whether a type declaration fulfills the contract for declaring a permitted direct subtype; that is, it extends/implements a sealed supertype and is declared either (implicitly) sealed, non-sealed, or (implicitly) final.

Click here to view code image

// File: Book.java (compilation unit)
public abstract sealed class Book {}        // Permitted subclasses are derived.
non-sealed class PrintedBook extends Book {}
sealed interface Subscribable {}            // Permitted subtypes are derived.
final class Ebook extends Book implements Subscribable {}
final class Audiobook extends Book implements Subscribable {}
non-sealed interface VIPSubcribable extends Subscribable {}

Given the type declarations in the compilation unit Book.java, the compiler is able to infer that PrintedBook, Ebook, and Audiobook are permitted direct subclasses of the sealed superclass Book, whereas Ebook, Audiobook, and VIPSubcribable are permitted direct subtypes of the sealed superinterface Subscribable.

Using Sealed Classes and Interfaces

As we have seen, sealed classes and interfaces restrict which other classes or interfaces can extend or implement them. Apart from providing the programmer with a language feature to develop improved domain models for libraries, the compiler can also leverage the sealed types to provide better analysis of potential problems in the code at compile time.

Example 5.33 is a client that uses the sealed classes and their permitted direct subclasses defined in Example 5.32. Typically, a reference of the sealed superclass is used to process a collection of objects of its permitted subtypes. The array books is such a collection created at (2). The objects of the permitted subtypes are processed by the enhanced for(:) loop at (3). Typically, a cascading if-else statement is used in the loop body to distinguish each object in the collection and process it accordingly. Any object of a permitted subclass can be processed by the code as long as the cascading if-else statement guarantees that the checks are exhaustive—that is, all permitted subtypes are covered. The compiler cannot help to ensure that this is the case, as it cannot extract the necessary information from the cascading if-else statement for this analysis. Note that the conditionals in the cascading if-else statement use the instanceof pattern match operator to make the code at least less verbose.

Example 5.33 Using Sealed Classes

Click here to view code image

package signedandsealed;
public class BookAdmin {
  public static void main(String[] args) {
    // Create some books:                                                  // (1)
    PrintedBook pBook = new PrintedBook(“888-222”, 340);
    Ebook eBook = new Ebook(“999-777”, “epub”);
    Audiobook aBook = new Audiobook(“333-555”, “Fry”, 300.0);
    // Create a book array:                                                // (2)
    Book[] books = {pBook, eBook, aBook};
    // Process the books:                                                  // (3)
    for (Book book : books) {
      if (book instanceof PrintedBook pb) {                                // (4)
        System.out.printf(“Printed book: %s, %d%n”,
                           pb.getIsbn(), pb.getPageCount());
      } else if (book instanceof Ebook eb) {
        System.out.printf(“Ebook: %s, %s%n”, eb.getIsbn(), eb.getFormat());
      } else if (book instanceof Audiobook ab) {
        System.out.printf(“Audiobook: %s, %s, %.1f%n”,
                           ab.getIsbn(), ab.getNarrator(), ab.getLength());
      }
    }
  }
}

Output from the program:

Click here to view code image Printed book: 888-222, 340
Ebook: 999-777, epub
Audiobook: 333-555, Fry, 300.0

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.