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: }
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. |
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:
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
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:
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. |
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.
New Term |
Variable scope determines where a variable can be used. |
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.
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. |
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.
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: |
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:
float root = Math.sqrt(453.0); System.out.print("The larger of x and y is " + Math.max(x, y));
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:
static int max(int arg1, int arg2) { ... }
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:
int count = Integer.parseInt("42", 10) // returns 42
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:
public static void main(String args[]) {...}
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.
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. |
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():
public static void main (String args[]) {...}
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.
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: }
The following is some sample input and output from this program:
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
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.
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. |
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.
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: }
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:
SumAverage.java:6: Incompatible type for +=. Can't convert java.lang.String to int. sum += args[i];
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:
sum += Integer.parseInt(args[i]);
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:
Sum is: 6 Average is: 2
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:
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).
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. | |
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. |