In just about every lesson up to this point you've been creating Java applicationswriting classes, creating data members and methods, and running those applications to perform simple tasks. You've also focused either on the very broad (general
object-oriented theory) or the very minute (arithmetic and other expressions). Today, you 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 numerous times in previous lessons. To define a class, use the class keyword and the name of the class:
class MyClassName { ... }
If this class is a subclass of another class (remember a subclass is a class that inherits the properties and methods of another class), use extends to indicate the superclass of this class:
class myClassName extends mySuperClassName { ... }
If this class implements a specific interface, use implements to refer to that interface:
class MyRunnableClassName implements Runnable { ... }
Both extends and implements are optional. You'll learn about using and defining interfaces on Day 18.
Take special note of the keyword Runnable in the preceding code. Runnable is an interface you use to declare that you want the class to run in a separate thread. The importance of Runnable will become apparent when threads are discussed on Day 11.
A class definition with nothing in it is pretty dull. Typically, when you create a class, you have something you want to add to that class to make it different from its superclasses (if the class has a superclass). Inside each class definition
are declarations and definitions for variables and/or methods or both for the class. These include class properties and methods, as well as instance properties and methods. In this section, you'll learn all about data members and class variables
(properties); the next section talks about methods.
On Day 3, you learned how to declare and initialize local variablesthat is, variables inside method definitions. Data members, fortunately, are declared and defined in almost exactly the same way as local variables; the main difference is where
they're located in the class definition. Variables are considered data members if they are declared outside a method definition.
Although you can declare a data member anywhere in your class, except within a method, most data members are defined just after the first line of the class definition. This is customary among most programmers because it makes the code easier to
read and easier to maintain. For example, Listing 7.1 shows a simple class definition for the class Bicycle, which inherits from the class PersonPoweredVehicle. This class definition contains four data members:
Listing 7.1. The bicycle class.
1: class Bicycle extends PersonPoweredVehicle 2: { 3: String m_sBikeType; 4: int m_iChainGear; 5: int m_iRearCogs; 6: int m_iCurrentGearFront; 7: int m_iCcurrentGearRear; 8: }
The preceding m_ (member) data member prefix is part of the naming convention. . .remember back on Day 5 when m_ was first introduced? Keep this in mind. Later today, when the this keyword is described, the use of this naming convention becomes quite apparent and handy.
Constants are useful for defining shared values for all the methods of an objectfor giving meaningful names to object-wide values that will never change. In Java, you can create constants only for data members or class variables, not for local
variables.
A constant variable or constant is a variable whose value never changes while your program is running, which might seem strange given the meaning of the word variable.
To declare a constant you use the final keyword before the variable declaration and include an initial value for that variable.
The only way to define constants in Java is by using the final keyword. Neither the C nor C++ constructs for #define and const are available in Java, although const is reserved to help catch its accidental use.
final float PI = 3.141592; final boolean DEBUG = false; final int MAXSIZE = 40000;
Notice the preceding lines of code. It would appear that we broke the naming convention for data members. After all, we have stressed that an int variable begins with an i and that a data member begins with an m_. Why then are the preceding variables
in all capitals with no identifiers? The answer is simple: it is common practice in programming (specifically C & C++) to define constants in all capitals. The purpose of this is to make it very easy to tell which variables are constants and cannot be
changed.
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 iAlignment;
Then, later in the body of a method definition, you can either set the alignment:
this.iAlignment = CENTER;
or test for a given alignment:
switch (this.iAlignment) { case LEFT: // deal with left alignment ... break; case RIGHT: // deal with right alignment ... break; case CENTER: // deal with center alignment ... break; }
As you learned in previous lessons, class variables are global to a class and to all that class' instances. Remember, that in VJ++ there is no concept of a Global variable: class variables are as close as VJ++ gets to Globals. Class variables
are good for communicating between different objects within 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 c_iSum; static final int i_iMaxObjects = 10;
Notice the c_ before the variables in the preceding example. As per the naming conventions, this is an easily recognizable identifier you can use when defining a class variable.
Class variables can be used in two different ways: they can be accessed via the class itself or they can be accessed via an instance of the class. Take, for example, the class SomeClass. Assume the class SomeClass has a class variable called c_iCount.
Examine the code fragment below:
SomeClass myInstance; myInstance.c_iCount = 14; SomeClass.c_iCount = 14;
The last two lines are equivalent: they both set the value of the class variable c_iCount in the class SomeClass to 14.
Methods, as you learned on Day 4, 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.
Score points at your next water cooler conference: when someone mentions the word method, fire back "Oh, you mean a member function." Both these terms are used interchangeably within the programming industry.
Method definitions have four basic parts:
The method's signature is a combination of the name of the method, the type of object or base type this method returns, and a list of parameters.
To keep things simple today, we'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 on Day 20.
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. This is called method overloading, and you'll learn more about it tomorrow.
Here's what a basic method definition looks like:
returntype methodname(type1 arg1, type2 arg2, type3 arg3..) { ... }
The returntype is the primitive type or class of the 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 after either the return type or after the parameter list; because the return type 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 methods within this class or to other objects, conditionals, loops, and so oneverything 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 return a value. Use the return keyword to do this. Listing 7.2 shows an example of a class that defines a
makeRange() method. makeRange() takes two integersa lower bound and an upper boundand creates an array that contains all the integers between those two boundaries (inclusive).
Listing 7.2. The RangeClass class.
1: class RangeClass 2: { 3: int[] makeRange(int iLower, int iUpper) 4: { 5: int iArr[] = new int[ (iUpper - iLower) + 1 ]; 6: 7: for (int i = 0; i < iArr.length; i++) 8: { 9: iArr[i] = iLower++; 10: } 11: return iArr; 12: } 13: 14: public static void main(String iArg[]) 15: { 16: int iTheArray[]; 17: RangeClass theRange = new RangeClass(); 18: theArray = theRange.makeRange(1, 10); 19: System.out.print("The array: [ "); 20: for (int i = 0; i < iTheArray.length; i++) 21: { 22: System.out.print(iTheArray[i] + " "); 23: } 24: System.out.println("]"); 25: } 26: }
Here's the output of this program:
The array: [ 1 2 3 4 5 6 7 8 9 10 ]
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 9), and then uses a for loop to print the values of
the new array.
In the body of a method definition, you might want to refer to the current objectthe object the method was called onto refer to that object's data members 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 refers to the current object, and you can use it anywhere that object might appear: in dot notation to refer to the object's data members, 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 data member 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, however, you might be able to omit the this keyword. You can refer to both data members 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 data member for this object myMethod(this); // call the myMethod method, defined in this // class
Omitting the this keyword for data members depends on whether there are no variables of the same name declared in the local scope. See the next section for details.
Omitting the this keyword when referring to data members and methods is accepted and often encouraged, however it is not always the prudent thing to do. When you include the this keyword when referring to the local object, it makes your code more
readable and easier to maintain. It also forces you, the programmer, to better understand your own logic. However, because a naming convention prefix for variables has been developed, which immediately identifies their scope, you rarely need to use the
this keyword. So, for the purposes of this book, we've only used the this keyword when it's necessary.
Keep in mind that this is a reference to the current instance of a class, therefore you should only use it inside the body of an instance method definition. Class methods, or methods declared with the static keyword, cannot use this. Class methods will
be explained in greater detail later today.
When you refer to a variable within your method definitions, Java checks for a definition of that variable first in the current scope (which might be a block), 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 a data member 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 this small Java program:
class ScopeTest { int iTest = 10; void printTest () { int iTest = 20; System.out.println("test = " + iTest); } }
In this class, you have two variables with the same name and definition: the first, a data member, has the name iTest 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 data member, the println() method will print that iTest as 20.
You can get around this particular problem by using this.test to refer to the data member, and just test to refer to the local variable.
When you looked at the preceding lines of code maybe you immediately thought, "Those guys really messed up; they didn't follow their own naming convention and, boy, did it get them in trouble." Well, this was done on purpose in order to demonstrate the this keyword. If the preceding code had been written with the naming convention, the first iTest would have actually been named m_iTest and the second iTest would never have hidden the first; not demonstrating the point at all.
A more insidious example of this 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 might call methods that are intended to change the value of a data
member, but they change the wrong one. Another bug might occur when you cast an object from one class to another; the value of your data member might 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' superclasses so you don't duplicate what's 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 of the objects that the 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.)
Please reread the preceding paragraph! Commit it to memory. Put a sticker on your monitor. It is very important to understand how VJ++ passes arguments. Because if you don't, your programs will behave in strange and mysterious ways.
Here's an example to demonstrate how this works. First, you have a simple class definition, which includes a single method called OneToZero(). (See Listing 7.3.)
Listing 7.3. The PassByReference class.
1: class PassByReference 2: { 3: int OneToZero(int iArg[]) 4: { 5: int iCount = 0; 6: for (int i = 0; i < iArg.length; i++) 7: { 8: if (iArg[i] == 1) 9: { 10: iCount++; 11: iArg[i] = 0; 12: } 13: } 14: return iCount; 15: } 16: }
The OneToZero() method does two things:
Listing 7.4 shows the main() method for the PassByReference class, which tests the OneToZero() method:
Listing 7.4. The main() method in PassByReference.
1: public static void main (String arg[]) 2: { 3: int iArr[] = { 1, 3, 4, 5, 1, 1, 7 }; 4: PassByReference test = new PassByReference(); 5: int iNumOnes; 6: System.out.print("Values of the array: [ "); 7: for (int i = 0; i < iArr.length; i++) 8: { 9: System.out.print(iArr[i] + " "); 10: } 11: System.out.println("]"); 12: iNumOnes = test.OneToZero(arr); 13: System.out.println("Number of Ones = " + iNumOnes); 14: System.out.print("New values of the array: [ "); 15: for (int i = 0; i < iArr.length; i++) 16: { 17: System.out.print(iArr[i] + " "); 18: } 19: System.out.println("]"); 20: }
Here is the output of this program:
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 ]
Go over the main() method line by line so that you can see what is going on.
Lines 3 through 5 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 6 through 11 print out the initial values of the array; you can see the output of these lines in the first line of the output.
Line 12 is where the real work takes place; this is where you call the OneToZero() method, defined in the object test, and pass it to the array stored in iArr. This method returns the number of ones in the array, which you'll then assign to the
variable iNumOnes.
Got it so far? Line 13 prints out the number of ones; that is, the value you got back from the OneToZero() method. It returns three, 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 the original copy of the array. Printing out the values in lines 15 through 18 proves
this; see the last line of output, all the 1s in the array have been changed to 0s.
Just as you have class variables and data members, you also have class and instance methods. The difference between the two types of methods are analogous. Class methods are available to any instance of the class, and to 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 or not.
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 fRoot = Math.sqrt(453.0); System.out.print("The larger of x and y is " + Math.max(x, y));
To define a class method, use the static keyword in front of the method definition, just as you would create a class variable. For example, the preceding max class method might have a signature like this:
static int max(int arg1, int arg2) { ... }
Java supplies wrapper classes for each of the base 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 iCount = Integer.parseInt("42", 10); // returns 42
Most methods that operate on a particular object, utilize the object's data members, 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
best declared as class methods.
Now that you know how to create classes, objects, data members, class variables and instance methods, all that's left is to put it all together into something that can actually runin other words, to create a VJ++ application.
Applications, to refresh your memory, are Java programs that run on their own. Applications are different from applets, which require Explorer, Netscape, or another Java-capable browser to view them. Much of what you've been creating up to this point
have been Java applications; next week you'll investigate more on 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. (Sun's browser, HotJava, is one example of a Java application.) The main thing that you need to make a Java application run is one class that
serves as the jumping-off point for the rest of your Java program. If your program is small enough, it might need only the one class.
The jumping-off class for your program needs one thing in particular: a main() method. When you run your compiled Java class (using the Java interpreter jview), 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 in a VJ++ application 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: generally, the initialization of the class containing main and initializing variables or creating instances of any classes you might 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. (See Listing 7.4 line 4 for an example of instantiation.)
Instantiated means to create an instance of itself.
Because Java applications are stand-alone programs, it's useful to be able to pass arguments or options to that 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: to turn on debugging input, to indicate a filename to read or write from, or for any other information that your VJ++ program depends upon.
To pass arguments to a Java program, you merely append them to the command line when you run your Java program:
jview Myprogram argumentOne 2 three
On the preceding command line are three arguments: argumentOne, the number 2, and three. Note that a space separates arguments, so the following command line produces three arguments:
jview myprogram Java is cool
To group arguments, surround them with double-quotes. This command line produces one argument:
jview myprogram "Java is cool"
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. Although you can call the args variable anything you want, it is a convention to leave it as the default of args. Doing so makes it easier for someone else to maintain
your code, because they will more than likely expect the command-line array to be in args.
Inside your main() method, you can handle the arguments from which your program started by iterating over the array of arguments, and then handling each of those arguments as the program needs. For example, Listing 7.5 is a really simple class that
prints out the arguments it gets: one per line.
Listing 7.5. The EchoArgs class.
1: class EchoArgs 2: { 3: public static void main(String args[]) 4: { 5: for (int i = 0; i < args.length; i++) 6: { 7: System.out.println("Argument " + i + ": " + args[i]); 8: } 9: } 10: }
The following is some sample input and output from this program:
jview EchoArgs 1 2 3 jump Argument 0: 1 Argument 1: 2 Argument 2: 3 Argument 3: jump
jview EchoArgs "Black and Gold" New Mexico 6 Argument 0: Black and Gold Argument 1: New Argument 2: Mexico Argument 3: 6
Note how the arguments are grouped in the listing; putting quotes around Black and Gold causes that argument to be treated as one unit inside the argument array.
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 VJ++ program is that those arguments will be stored in an array of strings (args). 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 7.6 shows a first pass at this program.
Listing 7.6. First try at the SumAverage class.
1: class SumAverage 2: { 3: public static void main (String args[]) 4: { 5: int iSum = 0; 6: for (int i = 0; i < args.length; i++) 7: { 8: iSum += args[i]; 9: } 10: System.out.println("Sum is: " + iSum); 11: System.out.println("Average is: " + 12: (float)iSum / args.length); 13: } 14: }
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 the following error:
SumAverage.java:9: 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 from strings to integers. There's a class method for the Integer class, called parseInt, that does just this. If you change line 8 to use that method, everything works just fine:
iSum += Integer.parseInt(args[i]);
Now, compiling the program produces no errors, and running it with various arguments returns the expected results. For example, jview 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 included the following:
Q: I tried creating a constant variable inside a method, and I got a compiler error when I tried it. What was I doing wrong?
A: You can create only constant (final) class variables or data members; local variables cannot be constant.
Q: static and final are not exactly the most descriptive words for creating class variables, class methods, and constants. Why not use class and const?
A: 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.
Q: In my class, I have a data member called name. I also have a local variable called name in a method, which, because of variable scope, gets hidden by the local variable. Is there any way to get hold of the data member's value?
A: The easiest way is not to name your local variables the same names as your data members. If you feel you must, you can use this.name to refer to the data member and name to refer to the local variable.
Q: I want to pass command-line arguments to an applet. How do I do this?
A: 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.
Q: I wrote a program to take four arguments, but if I give it too few arguments, it crashes with a run-time error.
A: It is your responsibility to test for the number of arguments and the data type of those arguments your program expects; Java won't do it for you. If your program requires four arguments, you need to test that you have indeed been given four
arguments, and return an error message if you haven't.