by Laura Lemay
In just about every lesson up to this point you've been creating Java applications-writing classes, creating instance variables and methods, and running those applications to perform simple tasks. Also up to this point, you've focused either on the very broad (general object-oriented theory) or the very minute (arithmetic and other expressions). Today you'll pull it all together and learn how and why to create classes by using the following basics:
Defining classes is pretty easy; you've seen how to do it a bunch of times in previous lessons. To define a class, use the class keyword and the name of the class:
class MyClassName { ... }
By default, classes inherit from the Object class. If this class is a subclass of another specific class (that is, inherits from another class), use extends to indicate the superclass of this class:
class myClassName extends mySuperClassName { ... }
Note |
Java 1.1 will give you the ability to nest a class definition inside other classes-a useful construction when you're defining "adapter classes" that implement an interface. The flow of control from the inner class then moves automatically to the outer class. For more details (beyond this sketchy description), see the information at the 1.1 Preview Page at http://java.sun.com/products/JDK/1.1/designspecs/. |
A class definition with nothing in it is pretty dull; usually, when you create a class, you have something you want to add to make that class different from its superclasses. Inside each class definition are declarations and definitions for variables or methods or both-for the class and for each instance. In this section, you'll learn all about instance and class variables; the next section talks about methods.
On Day 3, "Java Basics," you learned how to declare and initialize local variables-that is, variables inside method definitions. Instance variables, fortunately, are declared and defined in almost exactly the same way as local variables; the main difference is their location in the class definition. Variables are considered instance variables if they are declared outside a method definition. Customarily, however, most instance variables are defined just after the first line of the class definition. For example, Listing 6.1 shows a simple class definition for the class Bicycle, which inherits from the class PersonPoweredVehicle. This class definition contains five instance variables:
Listing 6.1. The Bicycle class.
1: class Bicycle extends PersonPoweredVehicle { 2: String bikeType; 3: int chainGear; 4: int rearCogs; 5: int currentGearFront; 6: int currentGearRear; 7: }
A constant variable or constant is a variable whose
value never changes (which may seem strange given the meaning
of the word variable). Constants are useful for defining
shared values for all the methods of an object-for giving meaningful
names to objectwide values that will never change. In Java, you
can create constants only for instance or class variables, not
for local variables.
New Term |
A constant is a variable whose value never changes. |
To declare a constant, use the final keyword before the variable declaration and include an initial value for that variable:
final float pi = 3.141592; final boolean debug = false; final int maxsize = 40000;
Technical Note |
The only way to define constants in Java is by using the final keyword. Neither the C and C++ constructs for #define nor const are available in Java, although the const keyword is reserved to prevent you from accidentally using it. |
Constants can be useful for naming various states of an object and then testing for those states. For example, suppose you have a test label that can be aligned left, right, or center. You can define those values as constant integers:
final int LEFT = 0; final int RIGHT = 1; final int CENTER = 2;
The variable alignment is then also declared as an int:
int alignment;
Then, later in the body of a method definition, you can either set the alignment:
this.alignment = CENTER;
or test for a given alignment:
switch (this.alignment) { case LEFT: // deal with left alignment ... break; case RIGHT: // deal with right alignment ... break; case CENTER: // deal with center alignment ... break; }
As you have learned in previous lessons, class variables are global to a class and to all that class's instances. You can think of class variables as being even more global than instance variables. Class variables are good for communicating between different objects with the same class, or for keeping track of global states among a set of objects.
To declare a class variable, use the static keyword in the class declaration:
static int sum; static final int maxObjects = 10;
Methods, as you learned on Day 2, "Object-Oriented Programming and Java," define an object's behavior-what happens when that object is created and the various operations that object can perform during its lifetime. In this section, you'll get a basic introduction to method definition and how methods work; tomorrow, you'll go into more detail about advanced things you can do with methods.
Method definitions have four basic parts:
Note |
To keep things simple today, I've left off two optional parts of the method definition: a modifier such as public or private, and the throws keyword, which indicates the exceptions a method can throw. You'll learn about these parts of a method definition in Week 3. |
The first three parts of the method definition form what's called the method's signature and indicate the most important information about the method itself.
In other languages, the name of the method (or function, subroutine,
or procedure) is enough to distinguish it from other methods in
the program. In Java, you can have different methods that have
the same name but a different return type or argument list, so
all these parts of the method definition are important. This is
called method overloading, and you'll learn more about
it tomorrow.
New Term |
A method's signature is a combination of the name of the method, the type of object or primitive data type this method returns, and a list of parameters. |
Here's what a basic method definition looks like:
returntype methodname(type1 arg1, type2 arg2, type3 arg3..) { ... }
The returntype is the type of value this method returns. It can be one of the primitive types, a class name, or void if the method does not return a value at all.
Note that if this method returns an array object, the array brackets can go either after the return type or after the parameter list; because the former way is considerably easier to read, it is used in the examples today (and throughout this book):
int[] makeRange(int lower, int upper) {...}
The method's parameter list is a set of variable declarations, separated by commas, inside parentheses. These parameters become local variables in the body of the method, whose values are the objects or values of primitives passed in when the method is called.
Inside the body of the method you can have statements, expressions, method calls to other objects, conditionals, loops, and so on-everything you've learned about in the previous lessons.
If your method has a real return type (that is, it has not been declared to return void), somewhere inside the body of the method you need to explicitly return a value. Use the return keyword to do this. Listing 6.2 shows an example of a class that defines a makeRange() method. makeRange() takes two integers-a lower bound and an upper bound-and creates an array that contains all the integers between those two boundaries (inclusive).
Listing 6.2. The RangeClass class.
1: class RangeClass { 2: int[] makeRange(int lower, int upper) { 3: int arr[] = new int[ (upper - lower) + 1 ]; 4: 5: for (int i = 0; i < arr.length; i++) { 6: arr[i] = lower++; 7: } 8: return arr; 9: } 10: 11: public static void main(String arg[]) { 12: int theArray[]; 13: RangeClass theRange = new RangeClass(); 14: 15: theArray = theRange.makeRange(1, 10); 16: System.out.print("The array: [ "); 17: for (int i = 0; i < theArray.length; i++) { 18: System.out.print(theArray[i] + " "); 19: } 20: System.out.println("]"); 21: } 22: 23: }
In the body of a method definition, you may want to refer to the
current object-the object in which the method is contained in
the first place-to refer to that object's instance variables or
to pass the current object as an argument to another method. To
refer to the current object in these cases, you can use the this
keyword. this can be used
anywhere the current object might appear-in dot notation to refer
to the object's instance variables, as an argument to a method,
as the return value for the current method, and so on. Here's
an example:
In many cases you may be able to omit the this
keyword entirely. You can refer to both instance variables and
method calls defined in the current class simply by name; the
this is implicit in those
references. So the first two examples could be written like this:
Keep in mind that because this
is a reference to the current instance of a class, you
should only use it inside the body of an instance method definition.
Class methods-that is, methods declared with the static keyword-cannot
use this.
When you declare a variable, that variable always has a limited
scope. Variable scope determines where that variable can be used.
Variables with a local scope, for example, can only be used inside
the block in which they were defined. Instance variables have
a scope that extends to the entire class so they can be used by
any of the methods within that class.
When you refer to a variable within your method definitions, Java
checks for a definition of that variable first in the current
scope (which may be a block, for example, inside a loop), then
in the outer scopes up to the current method definition. If that
variable is not a local variable, Java then checks for a definition
of that variable as an instance or class variable in the current
class, and then, finally, in each superclass in turn.
Because of the way Java checks for the scope of a given variable,
it is possible for you to create a variable in a lower scope such
that a definition of that same variable "hides" the
original value of that variable. This can introduce subtle and
confusing bugs into your code.
For example, note the small Java program in Listing 6.3.
The easiest way to get around this problem is to make sure you
don't use the same names for local variables as you do for instance
variables. Another way to get around this particular problem,
however, is to use this.test
to refer to the instance variable, and just test
to refer to the local variable. By referring explicitly to the
instance variable by its object scope you avoid the conflict.
A more insidious example of this variable naming problem occurs
when you redefine a variable in a subclass that already occurs
in a superclass. This can create very subtle bugs in your code-for
example, you may call methods that are intended to change the
value of an instance variable, but that change the wrong one.
Another bug might occur when you cast an object from one class
to another-the value of your instance variable may mysteriously
change (because it was getting that value from the superclass
instead of from your class). The best way to avoid this behavior
is to make sure that when you define variables in a subclass you're
aware of the variables in each of that class's superclasses and
you don't duplicate what is already there.
When you call a method with object parameters, the variables you
pass into the body of the method are passed by reference, which
means that whatever you do to those objects inside the method
affects the original objects as well. This includes arrays and
all the objects that arrays contain; when you pass an array into
a method and modify its contents, the original array is affected.
(Note that primitive types are passed by value.)
Listing 6.4 is an example to demonstrate how this works.
The main() method in the
PassByReference class tests
the use of the onetoZero()
method. Let's go over the main()
method line by line so that you can see what is going on and why
the output shows what it does.
Lines 14 through 16 set up the initial variables for this example.
The first one is an array of integers; the second one is an instance
of the class PassByReference,
which is stored in the variable test. The third is a simple integer
to hold the number of ones in the array.
Lines 18 through 22 print out the initial values of the array;
you can see the output of these lines in the first line of the
output.
Line 24 is where the real work takes place; this is where you
call the onetoZero() method,
defined in the object test, and pass it the array stored in arr.
This method returns the number of ones in the array, which you'll
then assign to the variable numOnes.
Got it so far? Line 25 prints out the number of 1s
(that is, the value you got back from the onetoZero()
method). It returns 3, as
you would expect.
The last bunch of lines print out the array values. Because a
reference to the array object is passed to the method, changing
the array inside that method changes that original copy of the
array. Printing out the values in lines 27 through 30 proves this-that
last line of output shows that all the 1s
in the array have been changed to 0s.
Just as you have class and instance variables, you also have class
and instance methods, and the differences between the two types
of methods are analogous. Class methods are available to any instance
of the class itself and can be made available to other classes.
Therefore, some class methods can be used anywhere, regardless
of whether an instance of the class exists.
For example, the Java class libraries include a class called Math.
The Math class defines a
whole set of math operations that can be used in any program or
the various number types:
To define class methods, use the static
keyword in front of the method definition, just as you would create
a class variable. For example, that max
class method might have a signature like this:
Java supplies "wrapper" classes for each of the primitive
data types-for example, classes for Integer,
Float, and boolean.
Using class methods defined in those classes, you can convert
to and from objects and primitive types. For example, the parseInt()
class method in the Integer
class takes a string and a radix (base) and returns the value
of that string as an integer:
Most methods that operate on a particular object, or that affect
that object, should be defined as instance methods. Methods that
provide some general utility but do not directly affect an instance
of that class are better declared as class methods.
Now that you know how to create classes, objects, and class and
instance variables and methods, all that's left is to put it together
into something that can actually run-in other words, to create
a Java application.
Applications, to refresh your memory, are Java programs that run
on their own. Applications are different from applets, which require
a Java-enabled browser to view them. Much of what you've been
creating up to this point have been Java applications; next week
you'll dive into how to create applets. (Applets require a bit
more background in order to get them to interact with the browser
and draw and update with the graphics system. You'll learn all
of this next week.)
A Java application consists of one or more classes and can be
as large or as small as you want it to be. While all the Java
applications you've created up to this point do nothing but output
some characters to the screen or to a window, you can also create
Java applications that use windows, graphics, and user interface
elements, just as applets do (you'll learn how to do this next
week). The only thing you need to make a Java application run,
however, is one class that serves as the "jumping-off"
point for the rest of your Java program. If your program is small
enough, it may need only the one class.
The jumping-off class for your application needs only one thing:
a main() method. When you
run your compiled Java class (using the Java interpreter), the
main() method is the first
thing that gets called. None of this should be much of a surprise
to you at this point; you've been creating Java applications with
main() methods all along.
The signature for the main()
method always looks like this:
Here's a run-down of the parts of the main()
method:
The body of the main() method
contains any code you need to get your application started: initializing
variables or creating instances of any classes you may have declared.
When Java executes the main()
method, keep in mind that main()
is a class method-the class that holds it is not automatically
instantiated when your program runs. If you want to treat that
class as an object, you have to instantiate it in the main()
method yourself (all the examples up to this point have done this).
Your Java application can have only one class, or, in the case
of most larger programs, it may be made up of several classes,
where different instances of each class are created and used while
the application is running. You can create as many classes as
you want for your program, and as long as they are in the same
directory or listed in your CLASSPATH,
Java will be able to find them when your program runs. Note, however,
that only the one jumping-off class, only the class you use with
the Java bytecode interpreter needs a
main() method. Remember, main()
is used only so that Java can start up the program and create
an initial object; after that, the methods inside the various
classes and objects take over. While you can include main()
methods in helper classes, they will be ignored when the program
actually runs.
Because Java applications are standalone programs, it's useful
to be able to pass arguments or options to a program to determine
how the program is going to run, or to enable a generic program
to operate on many different kinds of input. Command-line arguments
can be used for many different purposes-for example, to turn on
debugging input, to indicate a filename to read or write from,
or for any other information that you might want your Java program
to know.
How you pass arguments to a Java application varies based on the
platform you're running Java on. On Windows and UNIX, you can
pass arguments to the Java program via the command line; in the
Macintosh, the Java Runner gives you a special window to type
those arguments in.
Figure 6.1: Java Runner arguments.
Enter your arguments, separated by spaces, into this box.
In these examples, you've passed three arguments to your program:
argumentOne, the number 2,
and three. Note that a space
separates arguments, so if you use the phrase Java
is cool as your arguments, you'll get three of them.
To group arguments, surround them with double-quotes. So, for
example, the argument "Java is cool"
produces one argument for your program to deal with. The double-quotes
are stripped off before the argument gets to your Java program.
How does Java handle arguments? It stores them in an array of
strings, which is passed to the main()
method in your Java program. Remember the signature for main():
Here, args is the name of
the array of strings that contains the list of arguments. You
can actually call it anything you want.
Inside your main() method,
you can then handle the arguments your program was given by iterating
over the array of arguments and handling those arguments any way
you want. For example, Listing 6.5 is a really simple class that
prints out the arguments it gets, one per line.
The following is some sample input and output from this program:
Note how the arguments are grouped in the second input example;
putting quotes around foo bar
causes that argument to be treated as one unit inside the argument
array.
An important thing to note about the arguments you pass into a
Java program is that those arguments will be stored in an array
of strings. This means that any arguments you pass to your Java
program are strings stored in the argument array. To treat them
as non-strings, you'll have to convert them to whatever type you
want them to be.
For example, suppose you have a very simple Java program called
SumAverage that takes any
number of numeric arguments and returns the sum and the average
of those arguments. Listing 6.6 shows a first pass at this program.
Don't try compiling this one; just look at the code and see if
you can figure out what it does.
At first glance, this program seems rather straightforward-a for
loop iterates over the array of arguments, summing them, and then
the sum and the average are printed out as the last step.
What happens when you try and compile this? You get an error similar
to this one:
You get this error because the argument array is an array of strings.
Even though you passed integers into the program from the command
line, those integers were converted to strings before they were
stored in the array. To be able to sum those integers, you have
to convert them back from strings to integers. There's a class
method for the Integer class,
called parseInt, that does
just this. If you change line 6 to use that method, everything
works just fine:
Now, compiling the program produces no errors and running it with
various arguments returns the expected results. For example, java
SumAverage 1 2 3 returns the following output:
Today you put together everything you've come across in the preceding
days of this week about how to create Java classes and use them
in Java applications. This includes the following:
final, however, is new. final is used in a more general way for classes and methods to indicate that those things cannot be subclassed or overridden. Using the final keyword for variables is consistent with that behavior.
final variables are not quite the same as constant variables in C++, which is why the const keyword is not used.
The array: [ 1 2 3 4 5 6 7 8 9 10 ]
Analysis
The main() method in this class tests the makeRange() method by creating a range where the lower and upper boundaries of the range are 1 and 10, respectively (see line 6), and then uses a for loop to print the
values of the new array.
The this
Keyword
t = this.x; // the x instance variable for this object
this.myMethod(this); // call the myMethod method, defined in
// this class, and pass it the current
// object
return this; // return the current object
t = x // the x instance variable for this object
myMethod(this) // call the myMethod method, defined in this
// class
Note
Omitting the this keyword for instance variables depends on whether there are no variables of the same name declared in the local scope. See the next section for more details on variable scope.
Variable Scope and Method Definitions
New Term
Variable scope determines where a variable can be used.
Listing 6.3. A variable scope example.
1: class ScopeTest {
2: int test = 10;
3:
4: void printTest () {
5: int test = 20;
6: System.out.println("test = " + test);
7: }
8:
9: public static void main (String args[]) {
10: ScopeTest st = new ScopeTest();
11: st.printTest();
12: }
13: }
Analysis
In this class, you have two variables with the same name and definition: The first, an instance variable, has the name test and is initialized to the value 10. The second is a local variable with the same name, but with the value
20. Because the local variable hides the instance variable, the println() method will print that test is 20.
Passing Arguments to Methods
Listing 6.4. The PassByReference
class.
1: class PassByReference {
2: int onetoZero(int arg[]) {
3: int count = 0;
4:
5: for (int i = 0; i < arg.length; i++) {
6: if (arg[i] == 1) {
7: count++;
8: arg[i] = 0;
9: }
10: }
11: return count;
12: }
13: public static void main (String arg[]) {
14 int arr[] = { 1, 3, 4, 5, 1, 1, 7 };
15: PassByReference test = new PassByReference();
16: int numOnes;
17:
18: System.out.print("Values of the array: [ ");
19: for (int i = 0; i < arr.length; i++) {
20: System.out.print(arr[i] + " ");
21: }
22: System.out.println("]");
23:
24 numOnes = test.onetoZero(arr);
25: System.out.println("Number of Ones = " + numOnes);
26: System.out.print("New values of the array: [ ");
27: for (int i = 0; i < arr.length; i++) {
28: System.out.print(arr[i] + " ");
29: }
30: System.out.println("]");
31: }
32:}
Values of the array: [ 1 3 4 5 1 1 7 ]
Number of Ones = 3
New values of the array: [ 0 3 4 5 0 0 7 ]
Analysis
Note the method definition for the onetoZero() method in lines 2 to 12, which takes a single array as an argument. The onetoZero() method does two things:
Class Methods
float root = Math.sqrt(453.0);
System.out.print("The larger of x and y is " + Math.max(x, y));
static int max(int arg1, int arg2) { ... }
int count = Integer.parseInt("42", 10) // returns 42
Creating Java Applications
public static void main(String args[]) {...}
Helper Classes
Java Applications and Command-Line Arguments
Passing Arguments to Java Programs
Windows or Solaris
To pass arguments to a Java program on Windows or Solaris, append them to the command line when you run your Java program:
java Myprogram argumentOne 2 three
Macintosh
To pass arguments to a Java program on the Macintosh, double-click the compiled Java class file. The Java Runner will start up, and you'll get the dialog box shown in Figure 6.1.
Handling Arguments in Your Java Program
public static void main (String args[]) {...}
Listing 6.5. The EchoArgs
class.
1: class EchoArgs {
2: public static void main(String args[]) {
3: for (int i = 0; i < args.length; i++) {
4: System.out.println("Argument " + i + ": " + args[i]);
5: }
6: }
7: }
java EchoArgs 1 2 3 jump
Argument 0: 1
Argument 1: 2
Argument 2: 3
Argument 3: jump
java EchoArgs "foo bar" zap twaddle 5
Argument 0: foo bar
Argument 1: zap
Argument 2: twaddle
Argument 3: 5
Technical Note
The array of arguments in Java is not analogous to argv in C and UNIX. In particular, arg[0], the first element in the array of arguments, is the first command-line argument after the name of the class-not the name of the program
as it would be in C. Be careful of this as you write your Java programs.
Listing 6.6. A first try at the SumAverage
class.
1: class SumAverage {
2: public static void main (String args[]) {
3: int sum = 0;
4:
5: for (int i = 0; i < args.length; i++) {
6: sum += args[i];
7: }
8:
9: System.out.println("Sum is: " + sum);
10: System.out.println("Average is: " +
11: (float)sum / args.length);
12: }
13: }
SumAverage.java:6: Incompatible type for +=.
Can't convert java.lang.String to int.
sum += args[i];
sum += Integer.parseInt(args[i]);
Sum is: 6
Average is: 2
Summary
Q&A
I tried creating a constant variable inside a method, and I got a compiler error when I tried it. What was I doing wrong?
You can create only constant (final) class or instance variables; local variables cannot be constant.
static and final are not exactly the most descriptive words for creating class variables, class methods, and constants. Why not use class and
const?
static comes from Java's C++ heritage; C++ uses the static keyword to retain memory for class variables and methods (and, in fact, they aren't called class methods and
variables in C++: static member functions and variables are more common terms).
In my class, I have an instance variable called origin. I also have a local variable called origin in a method, which, because of variable scope, gets hidden
by the local variable. Is there any way to get hold of the instance variable's value?
The easiest way is not to name your local variables the same names as your instance variables. If you feel you must, you can use this.origin to refer to the instance variable and
origin to refer to the local variable.
I want to pass command-line arguments to an applet. How do I do this?
You're writing applets already? Been skipping ahead, have you? The answer is that you use HTML attributes to pass arguments to an applet, not the command line (you don't have a command line for
applets). You'll learn how to do this next week.
I wrote a program to take four arguments, but if I give it too few arguments, it crashes with a runtime error.
Testing for the number and type of arguments your program expects is up to you in your Java program; Java won't do it for you. If your program requires four arguments, test that you have indeed
been given four arguments, and return an error message if you haven't.