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.
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.
A package hierarchy represents an organization of the Java classes and interfaces. It does not represent the source code organization of the classes and interfaces. The source code is of no consequence in this regard. Each Java source file (also called compilation unit) can contain zero or more type declarations, but the compiler produces a separate class file containing the Java bytecode for each of them. A type declaration can indicate that its Java bytecode should be placed in a particular package, using a package declaration.
At most, one package declaration can appear in a source file, and it must be the first statement in the source file. The package name is saved in the Java bytecode of the types contained in the package. Java naming conventions recommend writing package names in lowercase letters.
Note that this scheme has two consequences. First, all the classes and interfaces in a source file will be placed in the same package. Second, several source files can be used to specify the contents of a package.
If a package declaration is omitted in a compilation unit, the Java bytecode for the declarations in the compilation unit will belong to an unnamed package (also called the default package), which is typically synonymous with the current working directory on the host system.
Example 6.1 illustrates how the packages in Figure 6.2 can be defined using the package declaration. There are four compilation units. Each compilation unit has a package declaration, ensuring that the type declarations are compiled into the correct package. The complete code can be found in Example 6.7, p. 345.
Example 6.1 Defining Packages and Using Type Import
// File name: Baldness.java // This file has 2 type declarations package wizard.spells; // Package declaration import wizard.pandorasbox.*; // (1) Type-import-on-demand import wizard.pandorasbox.artifacts.*; // (2) Import from subpackage public class Baldness extends Ailment { // Simple name for Ailment wizard.pandorasbox.LovePotion tlcOne; // (3) Fully qualified class name LovePotion tlcTwo; // Class in same package // … } class LovePotion { /* … */ }
Shared resources are typically implemented using synchronized code in order to guarantee thread safety of the shared resource (§22.4, p. 1387). However, if the shared resource is an immutable object, thread safety comes for free.
An object is immutable if its state cannot be changed once it has been constructed. Since its state can only be read, there can be no thread interference and the state is always consistent.
Some examples of immutable classes from the Java SE Platform API are listed in Table 6.5. Any method that seemingly modifies the state of an immutable object is in fact returning a new immutable object with the state modified appropriately based on the original object. Primitive values are of course always immutable.
Appointments(week 45):[5, 3, 8, 10, 7, 8, 9] Exception in thread “main” java.lang.IllegalArgumentException: Stats not for whole week: [10, 5, 20, 7] at WeeklyStats.<init>(WeeklyStats.java:14) at StatsClient.main(StatsClient.java:7)
There are certain guidelines that can help to avoid common pitfalls when implementing immutable classes. We will illustrate implementing an immutable class called WeeklyStats in Example 6.10, whose instances, once created, cannot be modified. The class WeeklyStats creates an object with weekly statistics of a specified entity.
It should not be possible to extend the class.
Caution should be exercised in extending an immutable class to prevent any subclass from subverting the immutable nature of the superclass.
A straightforward approach is to declare the class as final, as was done in Example 6.10 at (1) for the class WeeklyStats. Another approach is to declare the constructor as private and provide static factory methods to construct instances (discussed below). A static factory method is a static method whose sole purpose is to construct and return a new instance of the class—an alternative to calling the constructor directly.
All fields should be declared final and private.
Declaring the fields as private makes them accessible only inside the class, and other clients cannot access and modify them. This is the case for the fields in the WeeklyStats class at (2), (3), and (4).
Declaring a field as final means the value stored in the field cannot be changed once initialized. However, if the final field is a reference to an object, the state of this object can be changed by other clients who might be sharing this object, unless the object is also immutable. See the last guideline on how to safeguard the state of an mutable object referenced by a field.
Check the consistency of the object state at the time the object is created.
Since it is not possible to change the state of an immutable object, the state should be checked for consistency when the object is created. If all relevant information to initialize the object is available when it is created, the state can be checked for consistency and any necessary measures taken. For example, a suitable exception can be thrown to signal illegal arguments.
In the class WeeklyStats, the constructor at (5) is passed all the necessary values to initialize the object, and it checks whether they will result in a legal and consistent state for the object.
No set methods (a.k.a. setter or mutator methods) should be provided.
Set methods that change values in fields or objects referenced by fields should not be permitted. The class WeeklyStats does not have any set methods, and only provides get methods (a.k.a. getter or assessor methods).
If a setter method is necessary, then the method should create a new instance of the class based on the modified state, and return that to the client, leaving the original instance unmodified. This approach has to be weighed against the cost of creating new instances, but is usually offset by other advantages associated with using immutable classes, like thread safety without synchronized code. Caching frequently used objects can alleviate some overhead of creating new objects, as exemplified by the immutable wrapper classes for primitive types. For example, the Boolean class has a static factory method valueOf() that always returns one of two objects, Boolean.TRUE or Boolean.FALSE, depending on whether its boolean argument was true or false, respectively. The Integer class interns values between –128 and 127 for efficiency so that there is only one Integer object to represent each int value in this range.
A client should not be able to access mutable objects referred to by any fields in the class.
The class should not provide any methods that can modify its mutable objects. The class WeeklyStats complies with this requirement.
A class should also not share references to its mutable objects. The field at (4) has the type array of int that is mutable. An int array is passed as a parameter to the constructor at (5). The constructor in this case makes its own copy of this int array, so as not to share the array passed as an argument by the client. The getWeeklyStats() method at (8) does not return the reference value of the int array stored in the field stats. It creates and returns a new int array with values copied from its private int array. This technique is known as defensive copying. This way, the class avoids sharing references of its mutable objects with clients.
The class declaration below illustrates another approach to prevent a class from being extended. The class WeeklyStats is no longer declared final at (1), but now has a private constructor. This constructor at (5a) cannot be called by any client of the class to create an object. Instead, the class provides a static factory method at (5b) that creates an object by calling the private constructor. No subclass can be instantiated, as the superclass private constructor cannot be called, neither directly nor implicitly, in a subclass constructor.
public class WeeklyStatsV2 { // (1) Class is not final. … private WeeklyStatsV2(String description, int weekNumber, int[] stats) { // (5a) Private constructor this.description = description; this.weekNumber = weekNumber; this.stats = Arrays.copyOf(stats, stats.length); // Create a private copy. } // (5b) Static factory method to construct objects. public static WeeklyStatsV2 getNewWeeklyStats(String description, int weekNumber, int[] stats) { if (weekNumber <= 0 || weekNumber > 52) { throw new IllegalArgumentException(“Invalid week number: ” + weekNumber); } if (stats.length != 7) { throw new IllegalArgumentException(“Stats not for whole week: ” + Arrays.toString(stats)); } return new WeeklyStatsV2(description, weekNumber, stats); } … }
A class having just static methods is referred to as a utility class. Such a class cannot be instantiated and has no state, and is thus immutable. Examples of such classes in the Java API include the following: the java.lang.Math class, the java.util.Collections class, the java.util.Arrays class, and the java.util.concurrent.Executors class.
Apart from being thread safe, immutable objects have many other advantages. Once created, their state is guaranteed to be consistent throughout their lifetime. That makes them easy to reason about. Immutable classes are relatively simple to construct, amenable to testing, and easy to use compared to mutable classes. There is hardly any need to make or provide provisions for making copies of such objects. Their hash code value, once computed, can be cached for later use, as it will never change. Because of their immutable state, they are ideal candidates for keys in maps, and as elements in sets. They also are ideal building blocks for new and more complex objects.
A downside of using an immutable object is that if a value must be changed in its state, then a new object must be created, which can be costly if object construction is expensive.
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
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:
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); } }
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):
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).
// 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.
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”); } }
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.
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
package mypkg; public class Auxiliary { public static int binarySearch(int[] a, int key) { // Same in java.util.Arrays // Implementation is omitted. return -1; } }
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:
Declarations and statements can be grouped into a block using curly brackets, {}. Blocks can be nested, and scope rules apply to local variable declarations in such blocks. A local declaration can appear anywhere in a block. The general rule is that a variable declared in a block is in scope in the block in which it is declared, but it is not accessible outside this block. It is not possible to redeclare a variable if a local variable of the same name is already declared in the current scope.
Local variables of a method include the formal parameters of the method and variables that are declared in the method body. The local variables in a method are created each time the method is invoked, and are therefore distinct from local variables in other invocations of the same method that might be executing (§7.1, p. 365).
Figure 6.6 illustrates block scope (also known as lexical scope) for local variables. It shows four blocks: Block 1 is the body of the method main(), Block 2 is the body of the for(;;) loop, Block 3 is the body of a switch statement, and Block 4 is the body of an if statement.
Parameters cannot be redeclared in the method body, as shown at (1) in Block 1.
A local variable—already declared in an enclosing block, and therefore visible in a nested block—cannot be redeclared in the nested block. These cases are shown at (3), (5), and (6).
A local variable in a block can be redeclared in another block if the blocks are disjoint—that is, they do not overlap. This is the case for variable i at (2) in Block 3 and at (4) in Block 4, as these two blocks are disjoint.
The scope of a local variable declaration begins from where it is declared in the block and ends where this block terminates. The scope of the loop variable index is the entire Block 2. Even though Block 2 is nested in Block 1, the declaration of the variable index at (7) in Block 1 is valid. The scope of the variable index at (7) spans from its declaration to the end of Block 1, and it does not overlap with that of the loop variable index in Block 2.
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).
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.
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:
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.
If the try block executes, then the finally clause is guaranteed to be executed, regardless of whether any catch clause was executed, barring the two special cases (JVM crashes or the System.exit() method is called). Since the finally clause is always executed before control transfers to its final destination, the finally clause can be used to specify any clean-up code (e.g., to free resources such as files and network connections). However, the try-with-resources statement provides a better solution for handling resources, and eliminates the use of the finally clause in many cases (p. 407).
A try-finally construct can be used to control the interplay between two actions that must be executed in the correct order, possibly with other intervening actions. In the code below, the operation in the calculateAverage() method (called at (2)) is dependent on the success of the sumNumbers() method (called at (1)). The if statement at (2) checks the value of the sum variable before calling the calculateAverage() method:
int sum = 0; try { sum = sumNumbers(); // (1) // other actions } finally { if (sum > 0) calculateAverage(); // (2) }
This code guarantees that if the try block is entered, the sumNumbers() method will be executed first, and later the calculateAverage() method will be executed in the finally clause, regardless of how execution proceeds in the try block. We can, if desired, include any catch clauses to handle any exceptions.
If the finally clause neither throws an exception nor executes a control transfer statement like a return or a labeled break, the execution of the try block or any catch clause determines how execution proceeds after the finally clause (Figure 7.4, p. 376).
If no exception is thrown during execution of the try block or the exception has been handled in a catch clause, normal execution continues after the finally clause.
If there is any uncaught exception (either because no matching catch clause was found or because the catch clause threw an exception), the method completes abruptly and the exception is propagated after the execution of the finally clause.
The output of Example 7.4 shows that the finally clause at (4) is executed, regardless of whether an exception is thrown in the try block at (2). If an Arithmetic-Exception is thrown, it is caught and handled by the catch clause at (3). After the execution of the finally clause at (4), normal execution continues at (5).
Computing average. java.lang.ArithmeticException: / by zero at Average4.computeAverage(Average4.java:24) at Average4.printAverage(Average4.java:10) at Average4.main(Average4.java:4) Exception handled in printAverage(). Finally done. Exit printAverage(). Exit main().
On exiting from the finally clause, if there is any uncaught exception, the method completes abruptly and the exception is propagated as explained earlier. This is illustrated in Example 7.5. The method printAverage() is aborted after the finally clause at (3) has been executed, as the ArithmeticException thrown at (4) is not caught by any method. In this case, the exception is handled by the default exception handler. Notice the difference in the output from Example 7.4 and Example 7.5.
Computing average. Finally done. Exception in thread “main” java.lang.ArithmeticException: / by zero at Average5.computeAverage(Average5.java:21) at Average5.printAverage(Average5.java:10) at Average5.main(Average5.java:4)
If the finally clause executes a control transfer statement, such as a return or a labeled break, this control transfer statement determines how the execution will proceed—regardless of how the try block or any catch clause was executed. In particular, a value returned by a return statement in the finally clause will supercede any value returned by a return statement in the try block or a catch clause.
Example 7.6 shows how the execution of a control transfer statement such as a return in the finally clause affects the program execution. The first output from the program shows that the average is computed but the value returned is from the return statement at (3) in the finally clause, not from the return statement at (2) in the try block. The second output shows that the ArithmeticException thrown in the computeAverage() method and propagated to the printAverage() method is suppressed by the return statement in the finally clause. Normal execution continues after the return statement at (3), with the value 0 being returned from the printAverage() method.
If the finally clause throws an exception, this exception is propagated with all its ramifications—regardless of how the try block or any catch clause was executed. In particular, the new exception overrules any previously uncaught exception (p. 415).
Example 7.6 The finally Clause and the return Statement
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:
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
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:
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