6.1. Variable Scoping, Input, Output, and Exceptions¶
6.1.1. Variable Scoping¶
Now that you have experience with fields, parameters, and variables in a variety of situations, it is time to learn more about exactly where a user-defined name can be accessed. The scope of a user-defined name is the region of source code where the name is visible and can be used.
6.1.1.1. Local Scope¶
Recall that mutator methods (or “setters” as they’re often called) are used to change the values of private fields in a class. For example:
public class Cat
{
private String color;
// ...
public void setColor(String newColor)
{
color = newColor;
}
}
This setColor()
method makes use of both a field and a parameter.
It is important to note that there is a difference in where these two types
of variables can be used. The scope of a variable or method refers to where
it can be used in a program.
A parameter’s scope is limited to the body of the method in which it is
declared. Parameters are local variables
which are declared in the parameter list of a method’s header and which
have initial values specified by the arguments in a method call. For example,
if we had an object of type Cat
called c
, we could call
the setColor()
method like so:
c.setColor("Black");
When we write the method, we declare the variable newColor
, and when we call
the method here,
we set s
equal to the string “Black”. If we wanted to call the method
again, we would need to provide a new value for the variable newColor
.
c.setColor("Black");
c.setColor("Grey");
Such values do not carry over between method calls.
The scope of a parameter is the same as the scope of a variable declared at the very beginning of the body of a method. Once the flow of execution leaves a method, its parameters and other local variables cease to exist. The scope of local variables is referred to as local scope.
Note
Local Variables
Local variables, that is, parameters and variables declared in the body of a method, have local scope which extends from the point at which they are defined to the end of the block of code in which they are defined. In particular, the scope of a parameter is the entire body of the method in which it is declared.
It would be a syntax error to refer to a method’s parameters or other local variables from outside the method.
6.1.1.2. Block-Level Scope¶
Variables that are declared in the body of a method have scope which extends from the point where they are declared to the end of the block of code in which they are declared. When a local variable is declared at the beginning of a method, it has the local scope discussed above.
However, local variables are not restricted to the beginning of a method, and their declarations can be placed elsewhere, which can affect their scope. When control structures like if-statements or loops are involved, scope can be a bit more specific.
public void exampleMethod(int x)
{
if (x % 2 == 0)
{
int value = 4;
}
value = value + 2; // This will not work!
}
Variables declared inside the curly braces ({}
) of a control structure
like a loop or conditional only exist within those curly braces. The method
above would not work as the variable value
is only declared and initialized
inside the if statement’s true branch, and that variable ceases to exist when
the corresponding closing brace marking the end of the if statement’s true
branch is reached. As a result, its name is no longer visible once execution
has left the block (the pair of braces) where it is declared. Any attempt to
use the variable outside of the braces where it is declared will result in a
compiler error, since the variable is no longer visible or accessible–no longer
“in scope”.
The same is true for looping structures:
for (int i = 0; i < 12; i++)
{
System.out.println(i);
}
i = i + 1; // This will not work!
The variable i
is defined as part of the for loop and its scope is the
body of the for loop–the braces surrounding the loop’s body. The variable i
ceases to exist after the for loop is finished.
To get around this issue, you will sometimes see code where a variable is declared before a control structure, so that it can be accessed inside the control structure and also after it.
public void exampleMethod(int x)
{
int value = 0;
if (x % 2 == 0)
{
value = 4;
}
value = value + 2;
}
We could also do something similar with a for loop:
int i = 0;
for (i = 0; i < 12; i++)
{
System.out.println(i);
}
i = i + 1; // This will work!
6.1.1.3. Class Scope¶
By contrast, fields and all methods have scope that extends throughout the entire class, that is, class scope. They can be used in the body of any method and in the expressions that assign initial values to class level variables.
Note
Class-Level Variables
Fields and methods have class scope, which extends throughout the class.
6.1.1.4. A Common Misconception¶
After declaring a variable it is tempting to use to both the variable name and the variable’s type whenever referring to it. For example:
public class Cat
{
private String color;
// ...
public void setColor(String newColor)
{
String color = newColor;
}
}
This setter will not change the value of the field color
. To Java,
whenever the type of a variable is included, you are declaring a variable.
Java will allow you to declare variables with the same name, as long as they
are in different scopes.
In the code above, there is a field called color
with class-level
scope, and a local variable called color
that only exists within
the setColor()
method.
Even though these variables have the same name and type, they are different.
Changing one will not change the other. Another common example of this can be
seen when testing. Let’s look at a hypothetical test file for our Cat
class.
public class CatTest
extends TestCase
{
private Cat testCat;
public void setUp()
{
Cat testCat = new Cat();
}
public void test1()
{
testCat.setColor("White");
}
}
This is the same issue as we saw in the previous example.
There is a class-level Cat
object declared as a field (private Cat testCat;
).
But, instead of initializing the field inside setUp()
, we also
have a local variable being declared, also called testCat
. This means
the field testCat
will not be initialized, since the object created inside
setUp()
is being used to initialize the local variable inside that method,
which will cease to exist when the method ends. When
we refer to the testCat
variable in test1()
, we refer to the field,
which was never initialized and will therefore contain null
. Thus, this
test will produce a NullPointerException
.
Fortunately, the problem is easily fixed. Once a variable has been declared, we only need to refer to it by the variable’s name.
public class CatTest
extends TestCase
{
private Cat testCat;
public void setUp()
{
testCat = new Cat();
}
public void test1()
{
testCat.setColor("White"); // this won't work!
}
}
This code would run without error. The field testCat
is still declared
outside any method, giving it a class-level scope. But this time, it is
initialized in our setUp()
method correctly, and setUp()
runs before every test. This means that in test1()
, testCat
would
refer to a Cat
object, not the value null
.
6.1.1.5. A Note on Naming¶
As we saw above, Java can handle having two variables with the same name
and type when they are declared in different scopes. This can also lead to
confusion. For example, we could
have two String variables called color
. One a field and one a parameter.
public class Cat
{
private String color;
// ...
public void setColor(String color)
{
color = color;
}
}
This code would compile but it is not advisable to use such naming conventions.
This is because it is not clear if the field color
is being set to the
parameter color
or vice-versa, or something else entirely. Let’s take a
look at what is happening here by adding a few print statements:
public class Cat
{
private String color;
public Cat()
{
this.color = "Black";
}
public void setColor(String color)
{
color = color;
System.out.println(this.color);
System.out.println(color);
}
}
In this example, whenever we make a new Cat
object, the value of the
field color
is set to “Black” at first. When we run setColor("Green")
we see an interesting result in our print statements:

The first thing to be printed out is this.color
. Which we see is “Black”.
The value of the field was not changed to “Green”! This means that when we write
color = color
we know that the field color was not on the left side of
the assignment operator.
One might assume, then, that the parameter color
is the value on the left
side of the assignment operator. This would mean that the parameter was changed
from “Green” to “Black”. But our second print statement tells us otherwise.
When we print out the parameter color
we see it is still “Green”. This
means that the field color
was not on the right side of the equals
sign either!
What happened in this code is that we set the parameter variable color
equal to itself–meaning nothing changed!
Generally, the best way to avoid such confusion is to give your variables distinct names like we did initially:
public void setColor(String newColor)
{
color = newColor;
}
Alternately, if for some reason you must use the same variable name at two
different scope levels, using the modifier this
will help clarify which
variable you are referring to:
public void setColor(String color)
{
this.color = color;
}
Now, the field color
is on the left side of the assignment operator and the
parameter color
is on the right. So, if we ran setColor("Green");
the field color
would be changed from “Black” to “Green”.
You will sometimes see this convention in setter methods or constructors,
where the programmer has intentionally used the same name for both the
parameter and the field, to communicate the intent that the parameter is
the value that will be stored in the field. When using this approach it
is mandatory to alway include this.
as a prefix when referring to the
field name, because otherwise, all uses of the name would refer to the
parameter only.
6.1.2. Summarizing Scope Concepts¶
6.1.3. Check Your Understanding: Scope¶
6.1.4. Syntax Practice: Scoping¶
6.1.5. Java Input and Output¶
We have been using System.out.println()
for a while, but you might not
have thought about what it means. System
is a class that provides methods
related to the “system” or environment where programs run. It also provides
System.out
, which is a special field that refers to an object providing
methods for displaying output, including println()
.
In fact, we can use System.out.println()
to display the value
of System.out
:
System.out.println(System.out);
The result is:
java.io.PrintStream@685d72cd
This output indicates that System.out
refers to a PrintStream
object,
which is defined in a package called java.io
. A package is a collection of
related classes; java.io
contains classes for “I/O” which stands for
input and output.
6.1.5.1. Basic Input and Output Concepts¶
Java provides an extensive library of classes for managing input and output of all forms of data. In Java, any source or destination for I/O is considered a “stream” or sequence of bytes or characters. To perform output, we insert bytes or characters into the stream. To perform input, we extract bytes or characters from the stream. Even characters entered at a keyboard (if we think about them as a sequence of keystrokes) can be represented as a stream.
Input operations are framed in terms of reading from a stream in a three-step process:
open the stream
read data items from the stream front to back in sequence
close the stream.
Output operations are framed in terms of writing to a stream in a similar three-step process:
open the stream
write data onto the end of the stream in sequence
close the stream.
To use Java’s input/output classes, make sure that in addition to importing
any other necessary packages, also import the java.io
package and
the java.util
package:
import java.io.*;
import java.util.*;
6.1.6. Output Using PrintWriters¶
6.1.6.1. Opening a Stream for Output¶
In this class, we will only deal with textual, human-readable output. The main
class we will use for generating output is Java’s PrintWriter
class, from
the java.io
package. To create a PrintWriter
, we’ll use a utility
method in the IOHelper
class from the VT student package
(import student.*;
):
PrintWriter outStream = IOHelper.createPrintWriter("output.txt");
This line declares a new variable, outStream
and creates a new PrintWriter
object that sends output to a brand new file in the file system. If a file with
the name already exists in the project directory it will be deleted before a new
empty file with the same name is created.
The PrintWriter
object provides formatting and conversion operations.
A PrintWriter
object is designed to send its output to a stream. It does
not know (or care) whether the stream is connected to a disk file or a network
connection or another device. The IOHelper
class provides a few other
methods for creating PrintWriter
objects, including methods that append to
an existing file instead of overwriting it, or streams that are connected to
the console for output.
6.1.6.2. Writing to an Output Stream¶
Three basic methods provided by PrintWriter
objects provide virtually all
of the output capabilities you will need in this course:
<stream>.print(<value>);
writes the specified <value> to the given <stream>. There are actually many versions of this method that support every possible type of <value> you might want to print.<stream>.println(<value>);
writes the specified <value> to the given <stream>, and then follows it by writing a ‘line terminator’ to mark the end of the current line (Java writes an appropriate line termination character sequence based on the current operating system’s text file format conventions). As withprint()
, you can provide any type of value toprintln()
. You can even callprintln()
without giving any argument at all, for example, to terminate the current line after several previousprint()
messages.<stream>.write(<value>);
writes a single character specified by an integer <value>. This operation is most often used when you are producing output one character at a time, rather than in larger chunks. However, if you pass an entireString
value towrite()
instead of anint
value, then the entire string will be written to thePrintWriter()
just as if you had usedprint()
.
For example:
outStream.print("This is a message, and ");
outStream.println("these words appear on the same line as those above");
outStream.println(100 / 2); // prints the value "50"
outStream.write(65); // writes the letter 'A', whose ASCII code is 65
6.1.6.3. Closing a Stream¶
Once you have completed all of the operations you intend to carry out on a given stream, the stream should be closed. Closing the stream frees up operating system resources used to connect to and communicate with the stream, and makes sure that any buffered data you have written to the stream is flushed out to the physical device involved (if any).
Closing a stream is easy:
outStream.close();
You should close both input streams and output streams this way. In many simple programs, a good rule of thumb is to make sure that the method that creates the stream should also be the one responsible for closing it.
6.1.6.4. A Complete Output Example¶
We can put all these pieces together to show how to generate output to a file,
for example. Let’s say we want to create a file called output.txt
containing
some output from our program. We can do it in one method like this (don’t
forget to import java.io.*
in your class):
public void printResultFile(int result)
{
PrintWriter out = IOHelper.createPrintWriter("output.txt");
out.println("This is the first line of output.");
out.print("The result is: ");
out.print(result);
out.println();
out.close();
}
If called with a specific argument, like printResultFile(42);
, the method
will produce a file called output.txt
in your project directory
containing these lines:
This is the first line of output.
The result is: 42
At other times, when there is a lot of output to produce, you may want to place
all the println()
calls in one or more other methods. Then you can pass a
PrintWriter
object as a parameter, as in this example:
public void printResultFile()
{
PrintWriter out = IOHelper.createPrintWriter("output.txt");
printHeader(out);
printData(out);
out.close();
}
public void printHeader(PrintWriter outStream)
{
outStream.println("This is the output for ...");
// other output commands go here.
}
public void printData(PrintWriter outStream)
{
outStream.print(/* ... */);
// more, as needed ...
}
6.1.6.5. Output with System.out¶
It turns out that printing to the terminal is such a common action that Java
provides a pre-initialized output stream just for that purpose, called
System.out
. The advantage of System.out
is that it is already declared
and always ready for use, and your program is not responsible for closing it.
As a result, you can directly call print()
, println()
, or write()
on System.out
anywhere you like.
System.out.println("beginning the code ...");
...
if (someCondition())
{
System.out.println("someCondition() is true");
x = ...;
System.out.println("x = " + x);
}
else
{
System.out.println("someCondition() is false");
y = ...;
System.out.println("y = " + y);
}
Above, notice the way the plus operator (+
) was used to combine a textual
string with another value to make a larger message. This is a nice feature of
Java–the plus operator works to “concatenate” two strings into a larger string
by placing one after the other. Further, when you concatenate a string with
any other type of value, the other value is converted into a human-readable
string representation first by calling its toString()
method.
As a result, here are some recommendations for output in this course:
When you just want to produce simple messages in the terminal window to help debug a problem with your code, use
System.out
.When you just want to interactively prompt the user for some value(s), use
System.out
.When your program is supposed to produce a series of output lines in a file, use a
PrintWriter
.When your program is supposed to produce a series of output lines that may go either to the terminal window or to a file, write one or more methods that use a
PrintWriter
provided as a parameter. You can always call such a method and provide it with aPrintWriter
produced with aSystem.out
stream in order to produce output on the screen (see theIOHelper.createConsoleWriter()
method). Alternatively, you can pass in aPrintWriter
connected to a file instead (or even one connected to an internet socket for communicating with another program on another machine!).
6.1.7. Check Your Understanding: Output¶
6.1.8. Input Using Scanners¶
6.1.8.1. Opening a Stream for Input¶
The main class we will use for reading input is Java’s Scanner
class, from
the java.io package
. Creating a Scanner
is simple:
Scanner inStream = IOHelper.createScanner("input.txt");
This line declares a new name, inStream
and creates a Scanner object that
reads characters from the named file. The createScanner()
method opens
files using path names relative to your project directory, so the file
called input.txt
should be located there.
You can provide a fully qualified path name instead of a relative path name
if you desire.
The java.io
package offers a rich inheritance hierarchy of classes for
reading from text files. The Scanner
class was created to simplify text
input and is thus preferred over the other classes.
6.1.8.2. Reading from an Input Stream¶
Several methods provided by Scanner objects provide virtually all of the input capabilities you will need in this course:
<scanner>.hasNext();
Returnstrue
if this scanner has another token in its input.<scanner>.next();
Finds and returns the next complete token (by default the next whitespace delimited string as a String object like the next line or next tab-seperated word) from this scanner. ANoSuchElementException
is thrown if no more tokens are available, (i.e., you have reached the end of input).<scanner>.hasNextLine();
Returnstrue
if this scanner has another line in its input.<scanner>.nextLine();
Finds and returns the next complete line. ANoSuchElementException
is thrown if no more tokens are available, (i.e., you have reached the end of input).<scanner>.hasNext<PrimitiveType>();
The<PrimitiveType>
can be replaced bydouble
,float
,int
, etc. Returnstrue
if this scanner has another token in its input and it can be interpreted as a value of the<PrimitiveType>
.<scanner>.next<PrimitiveType>();
he<PrimitiveType>
can be replaced bydouble
,float
,int
, etc. The method scans the next token of the input as an<PrimitiveType>
and returns back the corresponding<PrimitiveType>
value. It throws anInputMismatchException
if the next token does not match the<PrimitiveType>
, or if the value scanned is out of range. It also throws aNoSuchElementException
if no more tokens are available.<scanner>.useDelimiter(String pattern);
by default whitespace (spaces, tabs, or new line characters) are used as delimiters for separating the input into tokens to return. This method allows the user to set the delimiter characters to whatever they wish for breaking up the input. Commas are a common other delimiter to use as tables or data is often stored in what are called CSV (comma seperated value) files.<scanner>.close();
closes the scanner to release system resources being used by the scanner.
To use these methods, normally you will process the input by scanning one line at a time and then scanning the line for the desired tokens.
For example:
Scanner inStream = IOHelper.createScanner("input.txt");
// if NOT at the end of the stream, more input is available
if (inStream.hasNextLine())
{
// Get an entire line
String thisLine = inStream.nextLine();
// Create a scanner to process the line
Scanner line = new Scanner(thisLine);
// Check for the next whitespace delimited int
if (line.hasNextInt())
{
System.out.println(line.nextInt());
}
}
inStream.close();
Notice how the existence of each input is checked before it is extracted to avoid exceptions.
Also, if you have programmed in another language before, note that characters in Java are encoded using unicode, a 16-bit character code. Programmers in other languages may instead be familiar with ASCII, the American Standard Code for Information Interchange, which is a 7-bit character code. Fortunately, the first 128 codes in unicode are equivalent to the entire ASCII character set . For American users, ASCII values may thus be freely used when reading and writing character-by-character without error, although this approach does not directly extend to programs written for an international audience.
The Scanner class can be used to read from any input stream, including files, the keyboard through the terminal window, or even URLs. To read from the keyboard, for example:
Scanner keyBoard = IOHelper.createKeyboardScanner();
System.out.print("Enter your name: ");
// Prompt the user String name = keyBoard.nextLine();
System.out.println("Hello " + name); // Echo input
When performing interactive keyboard input there is no need to check for the existence of the next token. The scanner will automatically block (i.e., wait) for the user to enter input.
Scanners can also be used to read from a file that is publicly available on the Web if you know the URL:
Scanner inWebFile = IOHelper.createScannerForURL(
"http://server.subdomain.domain/dir/file.txt");
while (inWebFile.hasNextLine())
{
String line = inWebFile.nextLine();
System.out.println(line); // Echo input
}
inWebFile.close();
6.1.9. A Complete Input Example¶
We can put all these pieces together to show how to read input from a file one
character at a time, for example. Let’s say we want to read the characters
from a file called input.txt
. We can do it in one method like this (don’t
forget to import java.io.*
and java.util.*
in your class):
public void readChars()
{
Scanner in = IOHelper.createScanner("input.txt");
// while NOT at the end of the stream, more input is available
while (in.hasNextLine())
{
String thisLine = in.nextLine(); // Get an entire line
for (int index = 0; index < thisLine.length(); index++)
{
char ch = thisLine.charAt(index);
System.out.print(ch);
}
System.out.println();
}
in.close();
}
At other times, when there is a lot of output to produce, you may want to place
all the read()
calls in one or more other methods. Then you can pass a
Scanner
object as a parameter:
public void processInputFile()
{
Scanner in = IOHelper.createScanner("input.txt");
readHeader(in);
readData(in);
in.close();
}
public void readHeader(Scanner inStream)
{
String nextLine = null;
if (inStream.hasNextLine())
{
nextLine = inStream.nextLine();
// other input commands go here.
}
}
public void readData(Scanner inStream)
{
String nextLine = null;
if (inStream.hasNext() )
{
nextLine = inStream.nextLine();
// more, as needed ...
}
}
6.1.10. Check Your Understanding: Input¶
6.1.11. A Complete Input/Output Example¶
Often, it is necessary to combine the processes of reading from some source and writing to some destination. Here is a simple example that copies an input file character by character:
import cs1705.*;
import java.io.*;
import java.util.*;
// -------------------------------------------------------------------------
/**
* Shows how to read/write a file one character at a time.
* @author Dwight Barnette
* @version 2006.03.09
*/
public class CopyFileByLine
{
// ----------------------------------------------------------
/**
* Copy the source file to the specified destination file.
* @param fromFile the name of the file to copy from
* @param toFile the name of the file to copy to
*/
public void copyFile(String fromFile, String toFile)
{
Scanner source = IOHelper.createScanner(inFile);
PrintWriter dest = IOHelper.createPrintWriter(toFile);
while (source.hasNextLine())
{
String thisLine = source.nextLine();
for (int index = 0; index < thisLine.length(); index++)
{
char ch = thisLine.charAt(index);
dest.print(ch);
}
dest.println();
}
source.close();
dest.close();
}
}
6.1.12. Testing I/O-based Operations¶
When it comes to testing, remember to write one or more test cases for each method that your write in your solution. Preferably, you should write these tests before (or as) you write the method itself, rather than saving testing until your code works. As you work on larger and larger programs, it is important to build skills in convincing yourself that the parts you have already written work as you intend, even if the full solution has not been completed.
For testing programs that read input or produce output, it seems difficult when the program operates directly on the console, since it is hard to “assert” what should come out on the screen. Plus you would always need to be present to “type in” the required input sequence.
To make these tests fully automated, however, don’t write tests that use
System.out
or that read from an external source. Instead, simply create a
Scanner
to read from a fixed input string as part of your test case.
For output, create a PrintWriter
that can write to a String object instead of the console.
To make these tasks easy, the TestCase
base class from which all your test
cases inherit provides a few helper methods for you:
setIn(<contents>);
takes a string and uses it to create aScanner
for your test to use as input. The scanner gets cleared automatically before each test case, so you can call this insetUp()
if you want to use the same input sequence for all your tests.in();
returns the currentScanner
being used for input. You can use this, in combination withsetIn()
to set up an input stream for your own input-based methods inside test cases. The scanner gets cleared automatically at the start of each test case.out();
returns aPrintWriter
that you can use for output. ThisPrintWriter
captures all of its own output for later use in assertions, and its contents are reset before each test case.
As an example, consider the following test method (which assumes your text fixture
includes a doIt
object created from some DoIt class that provides a
method called processSomeInput()
that accepts a Scanner
parameter):
public void testProcessSomeInput()
{
// set up the input stream
setIn("some test input");
// run the method to get results
doIt.processSomeInput(in());
// test that the result is what was expected
assertThat( ... );
}
Suppose there was a produceOutput()
method that wrote to a PrintWriter
:
public void testProcessSomeInput()
{
// run the method to get results
doIt.produceOutput(out());
// test that the result is what was expected
assertThat("what I expect").isEqualTo(out().getHistory());
}
Finally, you can even deal with both input and output at the same time:
public void testProcessSomeInput()
{
setIn("some test input");
// run the method to get results
doIt.processSomeStuff(in(), out());
// test that the result is what was expected
assertThat("output I want").isEqualTo( out().getHistory()));
}
The TestCase
base class provides similar methods for setting System.in
or retrieving the history from System.out
. See the javadoc for
TestCase
for more details.
6.1.13. Check Your Understanding: Testing¶
6.1.14. The Main Method¶
Traditionally, the first program you write when learning a new programming language is called the hello world program. All it does is display the words “Hello, World!” on the screen. In Java, it looks like this:
public class HelloWorld
{
public static void main(String[] args)
{
// generate some simple output
System.out.println("Hello, World!");
}
}
We know from previous modules that the single line of code inside this method will print out the phrase “Hello, World!”
This program defines one method named main()
with the following signature:
public static void main(String[] args)
A method declared with this exact signature is special: it can be used as
the entry point for executing a standalone Java program.
The HelloWorld
program is an example of a Java application program, or
a Java application for short, An application program is a stand-alone program
in the sense that it does not depend on any other program to run.
In Java, any class you write can contain its own stand-alone program, if
it provides its own declaration of a main()
method with this special
signature.
Every Java application program must have a main()
method (with exactly
that method signature) which is the place where the program begins
executing when it is run.
For a program that contains many classes, it is up to the programmer which
class should contain the main()
method.
Further, when you “start” or “execute” a Java application, execution begins
with the main()
method rather than by creating an object–it is then up
to main()
to create all the objects needed by your program.
As mentioned above, the method signature for this method is very specific. This is because of the very special role this method has in the program. The method signature must be exactly:
public static void main(String[] args)
Many of the keywords we see here you’ll recognize.
public
means this method is accessible to other classes in the program.void
means this method does not return anything.main
is the name given to this method.String[] args
means that this method takes in an array of Strings calledargs
.
The String array args
is actually a feature that will allow a user to pass
String arguments to the program when it starts up. For the most part, we won’t
worry about making use of this array in this class. However, it is still part of
the method signature and can’t be considered a main method without it.
When a Java program is executed in a stand-alone way using a command line,
the arguments given on the command line are used to populate this array, so
they are accessible inside main()
.
6.1.15. Check Your Understanding: Main Methods¶
6.1.16. The Static Keyword¶
In English, when we say something is static we mean it does not move or change. This is the same in Java. By contrast, a computer program is dynamic. It changes. It does things and performs certain actions.
Once put into place, we know that anything with the keyword static will mean that it will not change. The static keyword is a modifier you can place on declarations within a class, and it means that the corresponding declaration will belong to a class itself, rather than a specific object (an instance of the class).
Lets take a look at what we mean by that:
public class Ball
{
private String color;
public Ball()
{
this.color = "Red";
}
public void setColor(String newColor)
{
this.color = newColor;
}
public String getColor()
{
return this.color;
}
}
This code declares a Ball
class. When initialized, all Ball
objects
have their color
field set to “Red”, but a setter method has been provided
so we could change that to another color. The field color
“belongs” to the
object, since each individual instance of Ball
has its own color
field,
and each instance can store a different value in its own color
field,
separate from all other Ball
s.
To access or change this field though, we need to create a Ball
object to
work with.
Ball b1 = new Ball(); // creates a red Ball
b1.setColor("Blue"); // changes the color to Blue
System.out.println(b1.getColor()); // prints out the string "Blue"
Now, lets assume that all Ball
objects we want to must have exactly the
same diameter–a diameter of 15 centimeters. This might be a good application
for using the static
keyword.
public class Ball
{
private String color;
private static int diameter = 15;
// ...
}
The main reason to use the static keyword is when you want to define a common
property of the entire class, rather than just a single object. Here, because
diameter
is declared as static
, there is just one diameter
field
stored in the class itself, and that single copy is shared by all objects
created from the class. Rather than each object storing its own diameter,
there is just one place where this value is stored–in the class itself.
The static
modifier is used when you want a single resource (or method)
to be shared by all objects in the class.
Then, when accessing a static method or field, you talk about it differently
than regular fields that are stored within the object itself. For example,
consider a toString()
method for the Ball
class:
public String toString()
{
return this.color + " ball with diameter " + Ball.diameter;
}
Here, when referring to the color
field, we use this.color
, where
this
refers to the current object, which owns the field color
.
We are talking about the color
value stored in the current object.
However, when referring to diameter
, it does not belong to the
current object, and instead belongs to the class. Therefore, we use
the class name before the dot (.) to refer to it: Ball.diameter
.
Similarly, if we had a class with a main method, we don’t need to create an
object of that class to run the main()
, since main()
is a method that
(because it is static) belongs to the class itself, rather than being invoked
on an individual object:
public class HelloWorld
{
public static void main(String[] args)
{
// generate some simple output
System.out.println("Hello, World!");
}
}
With this declaration, we can call the main()
method without creating
an object of type HelloWorld
.
HelloWorld.main(null);
In short, methods or fields declared static
belong to the class itself,
rather than to any object created from that class, and all objects created
from the class share the same copy of the static
field(s) or method(s).
Also, when referring to static fields or methods, use the class name itself
followed by a dot (.), rather than using this
, since they belong to
the class rather than any instance of the class.
6.1.16.1. A Common Pitfall¶
One thing to keep in mind is that methods that are static are in some ways
seperate from non-static methods. Let’s take a closer look at the Ball
example.
public class Ball
{
private String color;
private static int diameter = 15;
public static void printData()
{
// This line will not work!
String message = "This ball is " + color + " and has a diameter of "
+ diameter + " cm" ;
System.out.println(message);
}
}
The method printData()
is static. It can be called without creating an
object.
Because of this, we can not reference the field color
, since that field
belongs to instances of Ball
(and each ball has its own color). More
generally, we cannot reference anything that isn’t static in a static method.
It is common to make this mistake in the main()
method. Lets take a look
again at a different implementation of the “Hello World” code:
public class HelloWorld
{
public void print()
{
System.out.println("Hello, World!");
}
public static void main(String[] args)
{
// This will not work!
print();
}
}
In this situation, the method print()
is non-static–meaning you must have
an instance of the HelloWorld
class to call the method on. It cannot be
called from a static method! There are two ways to get around this. We could
add the static
keyword to the print()
method. This is the simplest
solution, but generally it is a good design practice to only use the static
keyword when it is strictly necessary.
Instead, we can create a new object of our HelloWorld
class inside our
main()
method and use that object to call the print()
method.
public class Hello
{
public void print()
{
System.out.println("Hello, World!");
}
public static void main(String[] args)
{
// This will work!
HelloWorld hObject = new HelloWorld();
hObject.print();
}
}
In fact, this pattern is quite common in main()
methods, where the whole
point of the main()
method is to create the initial object(s) needed for
the program and to “start” them by calling one or more methods on them.
However, remember that static methods cannot call non-static methods without also having an object that is an instance of the class available. In short, be aware of what you reference when writing code in a static method!
6.1.17. Check Your Understanding: The Static Keyword¶
6.1.18. Errors¶
There are two major kinds of errors that can occur in a program: compile-time errors and run-time errors. It is useful to distinguish among them in order to track them down more quickly.
6.1.18.1. Compile-Time errors¶
Compile-time errors occur when you violate the syntax rules of the Java
language. For example, parentheses and braces have to come in matching
pairs. So int x = (1 + 2);
is legal, but int x = 8);
is not. In the
latter case, the program cannot be compiled, and the compiler displays an
error.
In Eclipse, compile-time errors will show up as a red line under the offending code, as well as being listed in the Problems tab near the bottom of the main window.
Error messages from the compiler usually indicate where in the program the error occurred, and sometimes they can tell you exactly what the error is. As an example, let’s get back to the hello world program:
public class HelloWorld
{
public static void main(String[] args)
{
System.out.println("Hello, World!") // there's a missing semi-colon here!
}
}
If you forget the semicolon at the end of the print statement, you might get an error message like this:
File: Hello.java [line: 5]
Error: ';' expected
That’s pretty good: the location of the error is correct, and the error message tells you what’s wrong.
But (as you have probably seen in this class) error messages are not always easy to understand. Sometimes the compiler reports the place in the program where the error was detected, not where it actually occurred. And sometimes the description of the problem is more confusing than helpful.
public class HelloWorld
{
public static void main(String[] args)
{
System.out.println("Hello, World!");
// there's a missing curly brace here!
}
You might get a message like this:
File: Hello.java [line: 7]
Error: reached end of file while parsing
There are two problems here. First, the error message is written from the compiler’s point of view, not yours. Parsing is the process of reading a program before translating; if the compiler gets to the end of the file while still parsing, that means something was omitted. But the compiler doesn’t know what. It also doesn’t know where. The compiler discovers the error at the end of the program (line 7), but the missing brace should be on the previous line.
Error messages contain useful information, so you should make an effort to read and understand them. But it can be important to know that they do not tell the whole story.
6.1.18.2. Run-Time Errors¶
During the first few weeks of your programming career, you will probably spend a lot of time tracking down compile-time errors (sometimes called syntax errors). But as you gain experience, you will make fewer mistakes and find them more quickly.
The second type of error is a run-time error, so-called because it does not appear until after the program has started running. In Java, these errors occur while the interpreter is executing byte code and something goes wrong. These errors are also called exceptions because they usually indicate that something exceptional (and bad) has happened.
When a run-time error occurs, the interpreter displays an error message that explains what happened and where. For example, if you accidentally divide by zero you will get a message like this:
Exception in thread "main" java.lang.ArithmeticException: / by zero at HelloWorld.main(HelloWorld.java:5)
Some parts of this output are useful for debugging. The first line includes
the name of the exception, java.lang.ArithmeticException
, and a message
that indicates more specifically what happened, / by zero
.
The next line shows the method where the error occurred;
HelloWorld.main
indicates the method
main()
in the class HelloWorld
. It also reports the file where the
method is defined, HelloWorld.java, and the line number where the error
occurred, 5.
6.1.19. Throwing Exceptions¶
In Java, errors and other abnormal conditions are handled by throwing and catching exceptions. When an error or an exceptional condition is detected, you can throw an exception. This is like pulling the fire alarm.
Lets assume we had a method that divided one parameter by the other:
public double div(double num, double denom)
{
return num / denom;
}
However, what if we couldn’t guarantee that the variable denom
was going
to be non-zero? We could add an if statement that will throw an exception
if denom
is equal to 0.
public double div(double num, double denom)
throws Exception
{
if (denom == 0.0)
{
throw new Exception("Tried to divide by 0!");
}
return num / denom;
}
Running div(1.0, 0.0)
would cause the following text to be printed to
the console:
java.lang.Exception: Tried to divide by 0!
Our message is what gets printed out.
Again, throwing an exception is like pulling a fire alarm. It brings
everything to a halt. Just like a return statement, when an Exception
is
thrown, the code will stop running at that line.
We can see the syntax for creating a new Exception
above. throw
is a
keyword in java for throwing exceptions.
You’ll notice the syntax after the word throw
looks a lot like how we
create objects–because that is exactly what it is doing. An Exception is a
kind of object baked into Java. In fact, Exception
is a parent class with
many different subclasses. In this case, it is perhaps more accurate to use
a child class called IllegalArgumentException
.
public double div(double num, double denom)
throws IllegalArgumentException
{
if (denom == 0.0)
{
throw new IllegalArgumentException("Tried to divide by 0!");
}
return num / denom;
}
This would produce the error message:
java.lang.IllegalArgumentException: Tried to divide by 0!
You’ll also notice a change to the method signature. There’s additional text after the parameter list but before the opening curly brace. If there’s a path where an exception will be intentionally thrown, we need to add that to the method signature. You won’t be able to compile code without adding these lines to the method signature, which indicate to the caller that an exception might be thrown.
6.1.20. Check Your Understanding: Throwing Exceptions¶
6.1.21. Syntax Practice: Throwing Exceptions¶
6.1.22. Try/Catch Blocks¶
So far, we’ve seen how to throw errors intentionally. However, what if we would like code to fall back to some other case if an error is thrown? Just as we can throw an exception, we can catch one as well. Let us revisit our example:
public double div(double num, double denom)
throws IllegalArgumentException
{
if (denom == 0.0)
{
throw new IllegalArgumentException("Tried to divide by 0!");
}
return num / denom;
}
/**
* divides two randomly generated numbers
*/
public double divideRandom()
{
Random rand = Random.generator();
double numerator = 5.5 * rand.nextInt(5);
double denominator = 5.5 * rand.nextInt(5);
double quotient = div(numerator, denominator);
return quotient;
}
Here, we generate two random doubles then run the div
method. However,
recall what we know about the code: rand.nextInt(5)
. This code will
generate a random integer between 0 and 4. This means there is a chance
either variable could end up being set to the result of 5.5 * 0
or 0.
Setting numerator
to 0 is no big deal, but if denominator
is 0, we’ll
see that exception get thrown.
What if we want to gracefully get out of this potential problem without an error getting thrown? We can employ a “try/catch” block.
/**
* divides two randomly generated numbers
*/
public double divideRandom()
{
Random r = Random.generator();
double numerator = 5.5 * r.nextInt(5);
double denominator = 5.5 * r.nextInt(5);
double quotient = 0.0;
try
{
quotient = div(numerator, denominator);
}
catch (IllegalArgumentException err)
{
quotient = div(numerator, 2.0);
}
return quotient;
}
The handling of exceptions in Java takes place in two parts. First we try to
execute some statements that may or may not lead to an exception. These are
enclosed within the try
clause.
try
{
quotient = div(numerator, denominator);
}
Second, we provide one or more catch
clauses to handle particular types
of exceptions that we expect may occur.
In this case, we are only handling one IllegalArgumentException
, so we
just provide one catch
(although you could provide multiple if needed,
each for a different type of exception).
catch (IllegalArgumentException err)
{
quotient = div(numerator, 2.0);
}
The way that this code works is that the code in the try
block is run.
If it works fine, great–there is nothing extra to do. However, if
an IllegalArgumentException
is thrown inside the try
part, the catch
block catches the exception without causing the whole program to grind to a
halt. Instead, if that exception is thrown, the variable quotient
is
assigned to the result of running div(numerator, 2.0)
.
As we said earlier, throwing an exception is like pulling a fire alarm.
The throw
occurs somewhere within the scope of the try
block. The
“fire department” in this case is the code contained in the catch
clause
that immediately follows the try
.
6.1.22.1. Try/Catch/Finally Syntax¶
A try block begins with the keyword try
followed by a block of code
enclosed within curly braces. A catch clause or catch block consists
of the keyword catch
followed by a parameter declaration that identifies
the type of Exception
being caught, followed by a collection of statements
enclosed within curly braces. These are statements that handle the exception by
taking appropriate actions. Because of this, catch
clauses are also often
called exception handlers.
Once an exception is thrown, control is transferred outisde of the try
block to an appropriate catch block. Control does not return to the try
block.
try
{
// block of statements at least one of which may throw an exception
}
catch (ExceptionClassName parameterName)
{
// block of statements to be executed if ExceptionClassName is thrown
}
catch (DifferentExceptionClassName otherParameterName)
{
// block of statements to be executed if DifferentExceptionClassName is thrown
}
We can also add another optional clause that will run whether or not an
exception is thrown using the keyword finally
.
try
{
// block of statements at least one of which may throw an exception
}
catch (ExceptionClassName parameterName)
{
// block of statements to be executed if ExceptionClassName is thrown
}
catch (DifferentExceptionClassName otherParameterName)
{
// block of statements to be executed if DifferentExceptionClassName is thrown
}
finally
{
// optional block of statements that is executed
// whether or not an exception is thrown
// this code is always run
}
After the code in the try
is executed, and after any matching catch
block is executed, the code inside the finally
block will always be
run at the end. Even if an exception is thrown inside the try
that does
not match any catch
clause, the finally
block will still be executed.
Note that there can be multiple catch
clauses associated with a
given try
block, and the order in which they are arranged is important.
A thrown exception will be caught by the first catch that matches the type of
exception thrown. Therefore, it is better to arrange catch clauses from the
most specific to most general.
try
{
// block of statements at least one of which may throw an exception
}
catch (Exception exc)
{
// This will catch any possible exception thrown as Exception
// is the general parent class.
}
catch (IllegalArgumentException illegal)
{
// This code is not reachable in this format.
}
It is also important to remember that a try
block must be immediately
followed by one or my catch clauses and a catch clause can only follow a try block.