3.5. Regarding Scope¶
The same basic scoping rules (i.e., what can be seen within a method when curly braces are involved) that you are used to for if-statements and loops also apply to try-blocks and catch-blocks. If you declare a variable inside a try-block or catch-block, then its scope only extends to what is inside that block.
Test Yourself
Take a few moments to review the code below. The code currently will not compile. Can you find the issue?
Hint: it’s related to scoping!
package cs1302.scope;
import java.io.File;
import java.io.PrintWriter;
import java.io.FileNotFoundException;
public class Example {
public static void main(String[] args) {
try {
String filename = args[0];
File file = new File(filename);
} catch (ArrayIndexOutOfBoundsException boundsException) {
System.err.println("first command-line argument is missing");
} // try
try {
PrintWriter output = new PrintWriter(file);
} catch (FileNotFoundException notFoundException) {
System.err.print("unable to open file for writing: ");
System.err.println(notFoundException.getMessage());
} // try
} // main
} // Example
Now, compile and run the code on Odin. Since the code is in a named package
(cs1302.scope
), make sure to create proper package directories.
Did the compiler give you the expected error message?
Test Yourself Solution and Explanation (Expand when ready)
After properly compiling the provided code example, the compiler will emit a
cannot find symbol
error similar to the following:
src/cs1302/scope/Example.java:22: error: cannot find symbol
PrintWriter output = new PrintWriter(file);
^
symbol: variable file
location: class Example
1 error
Pay close attention to the symbol the compiler is unable to find (above the
^
character). It can’t find the variable named file
even though we
declared it. However, since the variable file
is declared inside the
try-block, its scope only extends to subsequent lines within the try-block.
In other words, the variable can’t be used outside of the try-block,
as illustrated below:
try {
String filename = args[0];
File file = new File(filename);
// <---- ✓ scope of `file` extends to this line
// <---- ✓ and this line
} catch (ArrayIndexOutOfBoundsException boundsException) {
System.err.println("first command-line argument is missing");
// <---- ✗ but NOT this line
} // try
// <---- ✗ NOR this line
// <---- ✗ nor any of the lines below
Note
Issues like simple typos, missing import statements, and even an
incorrect classpath often causes the Java compiler to emit the
cannot find symbol
; however, the cause of this particular
cannot find symbol
error is related to the scope of the symbol (the
variable file
), which does not extend to a specific line of code
that attempts to use that symbol, as indicated by the error message.
Fixing the Problem
There are two high-level strategies for dealing with this kind of scoping issue. You should be aware of the first strategy but you should always try to use the second strategy as it leads to more elegant solutions that are easier to program. These two strategies are outlined below:
Strategy 1
Increase the symbol’s scope by declaring and initializing it on a line that precedes the enclosing try-block and changing the original declaration to a simple assignment – this strategy is also sometimes used to fix similar scoping issues with variables declared in if-statements and loops. If you can extend the variable’s scope to the line that uses the symbol, then the compiler will be able to find it. This strategy does come at a cost:
The code that uses the variable after the try-catch cannot and should not assume the code in the try-block is ever executed as a thrown exception may cause it to get skipped. For example, if you initialize a reference variable to
null
before a try-catch and reassign it to something that’s notnull
within the try-block, then the code after the try-catch needs to account for the possibility that the variable was never reassigned and is stillnull
. If it does not, then it runs the risk of throwing an uncheckedNullPointerException
during runtime.public static void main(String[] args) { File file = null; // <--- moving the declaration up extends the scope try { String filename = args[0]; file = new File(filename); } catch (ArrayIndexOutOfBoundsException boundsException) { System.err.println("first command-line argument is missing"); } // try try { PrintWriter output = new PrintWriter(file); // <--- risk NullPointerException } catch (FileNotFoundException notFoundException) { System.err.print("unable to open file for writing: "); System.err.println(notFoundException.getMessage()); } // try } // main
Strategy 2 (Preferred)
Place code that depends (or uses) on the symbol/variable within the try-block since it will be skipped if an exception occurs within the try-block before that line (as execution flows to a corresponding catch block).
This strategy often requires changes to multiple lines of code; however, it also often leads to a more elegant solution, as illustrated below:
public static void main(String[] args) { try { String filename = args[0]; File file = new File(filename); // moved the line below into the try since it depends on the file variable. // If an exception happens in one of the two lines above, this next line is skipped. PrintWriter output = new PrintWriter(file); } catch (ArrayIndexOutOfBoundsException boundsException) { System.err.println("first command-line argument is missing"); } catch (FileNotFoundException notFoundException) { System.err.print("unable to open file for writing: "); System.err.println(notFoundException.getMessage()); } // try } // main