2. Java Programming Basics
Java has stirred up a lot of excitement in the programming world. So let's get ready to join the excitement. By the end of this chapter, you will have written your first Java programs. Our aim is to provide an overview of the basic structure of a Java program while walking you through the process of writing, compiling, and running your code. Hopefully, through this process, you will start to see the similarities and differences between Java and any other programming languages you have used.
First, we get the Java compiler and runtime environment installed on your computer. We test the installation the best way we know how-by writing a Java program. We next look at Java program structure and at the way Java programs run. Finally, we introduce applet basics and demonstrate how to put your applets up on the Web for all to see.
We attack Java pretty furiously this chapter, but don't be too concerned if all your questions about Java are not answered in the next few pages. By Chapter 5, "How Applets Work," we will have elaborated on all of the material we cover here. In the next few pages, you will gain a basis to start your exploration of Java-and you will have written several simple Java programs to boot!
Your first step to becoming a Java programmer is installing the Java compiler and the Java runtime environment on your system. These are contained in the Java Developers Kit (JDK), which Sun Microsystems has made freely available. The latest version of the JDK you can freely download from http://java.sun.com. See the Web site for Java installation details.
We would recommend set the PATH environment variable to the "C:\JAVA\BIN" or where you installed the Java.
With the environment variables set, you are ready to start programming. Of course, this means picking a text editor. About the only requirement for the text editor is that it will save your files as plain text and that you can give the files a .java extension. It is preferable that your chosen text editor also be fast, since you can't format your text with different fonts. For example, using Microsoft Word 7.0 would be a poor choice. The ideal text editor will also do basic syntax checking as you program. If you have a C++ programming environment already installed, try using its text editor.
Now let's see if your installation was successful. First, when you are not familar enough with Java, you are free copy all examples of Java code of this on-lime Tutorial. If you're not on a Mac, just type it in this time, so you can start deciding whether or not you are happy with your text editor.
Macintosh users should wait until later in this chapter, when we
are writing applets, before trying out your chosen text editor.
Example 2-1: Simple Sveika Java program - OurPrimaryClass.java.
import java.util.*; public class OurPrimaryClass { public final static void main(String S[]) { System.out.println("Sveika, Java!"); Date d=new Date(); System.out.println("Data: "+d.toString()); } }
After you type this in, save the file as OurPrimaryClass.java. Any time you write a public class, you need to save it in a file of the same name. We explain exactly what a public class is later on in the chapter. First, let's compile the code using the Java compiler, javac. UNIX and Windows users, you can do this from your command line as follows:
javac OurPrimaryClass.java
Mac users should just click on the Java compiler icon, and then open the OurPrimaryClass.java file for compiling.
If you get any errors, make sure that you made no typing errors, or simply copy it from this Web page. If you still have problems, make sure that:
Assuming that all went well, you are ready to run our program. UNIX and Windows users can do this by typing:
java OurPrimaryClass
This invokes the java runtime environment, which takes OurPrimaryClass and executes the method main. The output should be:
Sveika, Java!
followed by "Data:" and today's date. If you get an error instead of this output, then the runtime environment is probably having trouble finding the file OurPrimaryClass.class-the file javac produced. UNIX and Windows users should make sure that CLASSPATH includes the current directory.
Macintosh users should run this example as an applet.
Passing Command Line Parameters
Those of us not using a Macintosh for development can also pass command line parameters to our simple program. That is where String S[] comes in. Let's rewrite our main method so that it will print out everything that is passed to it from the command line:
Example 2-2: Main method that deals with parameters.
public class PrintCommandLineParameters { public final static void main(String S[] ) { System.out.println("Sveika, Java!"); System.out.println("Buvo perduoti tokie parametrai:"); for(int i=0;i<S.length;i++) System.out.println(S[i]); } }
Our program will now print out any command line parameters we pass to it. If we invoke our program as follows:
java PrintCommandLineParameters parameterl parameter2 parameter3 parameter4
it will produce the following output:
Sveika, Java! Buvo perduoti tokie parametrai: parameterl parameter2 parameter3 parameter4
TIP The main method is analogous to the main function required in C
and C++ programs.
You probably figured out by now that System.out.println is the key to writing output back to the screen. For simple programs, System.out.println lets us generate output very easily. When we start writing applets, we're going to see how to produce graphical, rather than text only, output. For now, let's look at the basics of using System.out.println.
As we have already seen, if we pass System.out.println characters enclosed in quotation marks, it will print the characters, followed by a new line. We can also have it print out the value of a variable, either alone or with a string enclosed in quotation marks:
Example 2-3: Using System.out.println.
public class PrintlnExample { public static void main(String ARGV[]) { System.out.println("This example demonstrates the use"); System.out.println("of System.out.println"); System.out.println("\nYou can output variables values"); System.out.println("like the value of an integer:\n"); int i=4; System.out.println("variable i="+i); System.out.println(i); myOut("or use renamed System.out.println\n"); myOut("The new name myOut is much shorter\nthan previous one\n"); } public static void myOut(String S) { System.out.println(S); } }
You have written a simple Java program. Now let's take a look at what is actually happening. We're going to expand on our primaryClass program to include the basic building blocks that all Java programs have in common.
Again, we won't promise that you will understand all of Java in the next few pages. We cover Java's object-oriented features in the next chapter and discuss in detail the syntax of the language in Chapter 4. In this chapter, we want to develop a basic understanding of what a Java program is. It is important to note that the programs we are going to develop in this section aren't applets. (We write a "Hello, Applet!" applet towards the end of this chapter.) However, our discussion here is applicable to applets, which are the fundamental elements of all Java programs, whether we run them from the command line or within a Web page.
All Java programs have four basic building blocks: classes, methods, variables, and packages. Regardless of your programming background, you are most likely familiar with methods, which are subroutines, and variables, which are data. Classes, on the other hand, are the basis of object orientation. To keep things simple for now, we just say that a class is a unit that contains variables and methods. Packages contain classes and aid the compiler in locating the classes that are needed to compile our code. We will also see in Chapter 3, "Object Orientation in Java," that classes contained in a package have a special relationship with one another. For now, though, let's just think of a package as a set of classes. The program we wrote to check the JDK installation has all of the parts we describe here.
These are the parts that are apparent in every Java program. However, Java programs can have other entities that we aren't going to describe in depth yet. But, in order to give you an overview, Table 2-1 outlines the other building blocks. Although these aren't required for every Java program, they are necessary for many that we will be writing.
Entity | Purpose | Where Covered |
---|---|---|
Interfaces | Allow "polymorpism." | Polymorfism and Interfaces are discussed in Chapter 3/ |
Exceptions | Allow for easier error handling | Chapter 4. |
Threads | Used to concurrently execute different blocks of code. | Introduced in Chapter 5; discussed in depth in Chapter 10. |
Table 2-1: Java entities not covered in this chapter.
A Java program can have any number of classes, but there is always one class that has a special relationship with the runtime environment. It is always the first of our classes that the Java runtime recognizes. We call this class the primary class. In Example 2-1, it was called OurPrimaryClass. The driving characteristic of a primary class is that it has one or more preset methods that must be defined in the class.
When we run a program from the command line, as we did with Example 2-1, there is only one special method that we need to define-main. When we write our simple applets later on in this chapter, we will see that there are several methods that we need to define in the primary class of an applet.
Now let's look at each of the essential building blocks-variables, methods, classes, and packages-in more depth.
You have seen variables before. They are just boxes in which data is kept. Java's variables, like variables in most programming languages, are of specific types. The type of a variable determines what kind of information can be stored in it. For instance, a variable of type int is used for storing integers. Let's use a variable of this type in an example: Example 2-4: Using a variable.
public class UsesInt { public static void main(String S[]) { int i=4; System.out.println("Value of i="+i); } }
Here we have used the assignment operator = to give i the value of 4, and then we output it via System.out.println. The type we are using, int, belongs to one of the two major groupings of types found in Java-the primitive types. The other major grouping is called reference types, which includes programmer-defined types and array types. The primitive types are your standard, run-of the-mill types that represent numeric values, single characters, and boolean (true/false) values. Reference types, on the other hand, are dynamic. We outline the major differences between the two groupings in Table 2-2.
Characteristic | Primitive Types | Reference Types |
---|---|---|
Defined by Java Language? | Yes | No |
Predefined Memory Requirment? | Yes | No |
Memory Must Be Allocated At Runtime For The Variable | No | Yes |
Table 2-2: Primitive vs. reference types.
Primitive and reference types also differ in the way they are passed
as parameters to methods. Primitive variables are passed by value,
while reference variables are passed by reference. If this description
doesn't brighten any light bulbs for you, don't worry -we cover
this in the "Methods" section later in this chapter.
In day-to-day programming, the most important difference between primitive types and reference types is shown in the last column of Table 2-2: memory must be allocated at runtime for a reference variable. With reference types, we need to explicitly request memory for the variable before we put anything in it because the runtime environment doesn't know how much memory is necessary. Let's consider an example that illustrates this, remembering that all arrays are reference types-notice the commented lines that start with //:
Example 2-5: Primitive and reference variables.
public class Variables { public static void main(String ARGV[]) { int myPrimitive; //a variable of a primitive type int myReference[]; //a variable of a reference type myPrimitive=1; //we can go ahead and put something in our primitive //variable... myReference=new int[3]; //but we need to allocate memory for our reference variable //first... myReference[0]=0; myReference[1]=1; myReference[2]=2; //now we can put stuff into it. } }
Since ints are primitive types, the runtime environment knows exactly how much space they require-in the case of ints, 4 bytes. But when we declare that we want an array of ints, the runtime environment doesn't know how much space we want. Before we can put anything in myReference, we have to request some memory space for the variable. We do this with the new operator; it requests the appropriate memory from the runtime environment.
Note that variables of array types and programmer-defined types "point" to a space in memory where the real meat of the variable actually resides, while variables of the primitive types are contained entirely in presized boxes. As you can see, reference types are quite similar to pointers in C/C++. There are a couple of key differences. First, using reference types, you can't access and manipulate the memory address directly. And second, since you can't get at the memory address, there is no such thing as pointer arithmetic in Java.
Primitive Types
First, let's look at primitive types such as the int type we used in our earlier code example. This is one of the eight primitive types of the Java language. A primitive type is a type that is defined by the Java language itself. Table 2-3 lists the primitive types of Java.
Type | Size | Inclusie Range | Example Values |
---|---|---|---|
int | 4 bytes | -2147483648 to 2147483648 | 2003, -2003 |
short | 2 bytes | -32768 to 32767 | 1999, -1999 |
byte | 1 byte | -128 to 127 | 100,-100 |
long | 8 bytes | -922372036854775808 to 922372036854775807 | 1000000000, -1000000000 |
float | 4 bytes | dependent on precision | 3.142 |
double | 8 bytes | dependent on precision | 3.141592654 |
boolean | 1 bit | true, false | true, false |
char | 2 bytes | all unicode characters | |
Table 2-3: Primitive types of the Java language.
The first six types in our table are the numeric primitives. You can use the +, -, *, and / operators with them for addition, subtraction, multiplication, and division. We provide a complete discussion of the syntax of dealing with the numeric types in Chapter 4. By and large, you will find it identical to the C programming language. Let's go on to the boolean type, a type that many programming languages do not explicitly have. Here's how we assign to a boolean variable:
Example 2-6: Assign to a boolean.
If we put this code fragment inside the main method from Example 2-1, we get the following output:boolean truth=true; System.out.println(truth); boolean fallacy=false; System.out.println(fallacy); truth=(1==1); fallacy=(1==0); System.out.println(truth); System.out.println(fallacy);
true false true false
As you can see, boolean variables can be assigned the result of a comparison operation. In Java, the !, !=, and == operators play the same role with boolean variables that they play with ints in C. As with the rest of the primitive types, we discuss the syntax and semantics of the boolean type fully in Chapter 4.
Often throughout the book, we give example code fragments like the
one above. They won't actually compile by themselves, but i f we
wrote a whole program to demonstrate the concept, it would be hard
to discern what we were actually trying to prove. However, we have
put all of our code fragments within compilable programs on the CD.
Reference Types
As we've already said, reference types differ from primitive types in that they aren't defined explicitly by the Java language and thus their memory requirements are unknown. We have already looked at one grouping of reference types, Java's arrays. Array types exist for every other type in Java, including programmer-defined types, which are used much more often in Java than any other type.
Before we look at reference types, we need to know a little of the pertinent vocabulary. When we allocate memory for a reference type with the new operator, we are instantiating the reference type. Thereafter, the variable is an instantiation, or instance, of the particular reference type.
"Exactly what hair are you trying to split here," you may be wondering. The problem is that we can't just declare a reference variable and start putting data into it. We have to ask the runtime environment for some memory, and the runtime environment has to record that we have activated a variable of that particular reference type. That's a mouthful, so we just call the process instantiation. Then, once we instantiate the type, the target variable is qualitatively different-now we can put data into it. To indicate that the reference variable is now available for stuffing, we say that it is an instance.
Now let's look at our programmer-defined types. Then, we will examine arrays a little more closely
Programmer-Defined Types
Most languages support type definition-in C, you can define types with a struct, while in Pascal you do it with records. In Java, we can define types with classes, which we touch on here, or interfaces, which we save for Chapter 3, "Object Orientation in Java."
At the simplest level classes are similar to structs and records in that you can use them to store and access a collection of data. But classes can include methods as well as data. We can define a type called "MyType" as follows: the public keyword that precedes the declarations is an access modifier-it means that our members are accessible from outside the class. We examine the access modifiers a little later in this chapter.
Example 2-7a: Defining a type.
class MyType { public int myDataMember=4; public void myMethodMember() { System.out.println("I'm a member!"); System.out.println("myData="+myDataMember);} }
You probably noticed that this example is very much like the Java programs we have been writing. Actually, classes play double duty in the Java language. In the programs that we have been writing, they play an organizational role.
Classes also can be used to define types. Variables of types defined by classes are called objects, instantiations, or instances, of a particular class. We create, or instantiate, an object with the new operator and access its members with the dot ( .) operator:
Example 2-7b: Instantiating an object.
public class RunMe { public static void main(String ARGII[]) { MyType Mine=new MyType(); int i=Mine.myDataMember; Mine.myMethodMember(); } }
Example 2-7 shows us the three things we can do with an object; create it, access one of its data members, and access one of its functional members. Here, the third line of code calls the myMethodMember method, which prints out:
I'm a member! myData=4
Since myDataType is a reference type, we use the new operator. It allocates some memory for our object. We can also define some other stuff to happen when our class is instantiated by defining a constructor. Here is a constructor for myDataType that just lets us know the class has been instantiated:
Example 2-8a: A constructor that verifies instantiation.
We can also use constructors to initialize the values of data members. This constructor will set the value of myDataMember to the integer that is passed:public class MyType { int myDataMember=0; public MyType() { System.out.println("Instantiation in process!");} }
Example 2-8b: A constructor that initializes values of data members.
public MyType(int val) { System.out.println("setting myDataMember="+val); myDataMember=val;}
Assuming that we have both of these constructors defined in our myDataType class, let's look at another short program that uses both of them:
Example 2-8c: A program that uses both constructors.
public class RunMe { public static void main(String ARGV[]) { MyType instancel=new MyType(); MyType instance2=new MyType(100); } }
Now, our output will be:
Instantiation in progress! I'm a member! myDataType=4 setting myDataType=100 I'm a member! myDataType=100
The StringType
We've discussed primitive types and programmer-defined types. Now we need to look at a special type that is a hybrid of the two-the String type. T'he String is primarily a programmer-defined type: it is defined by the String class, and it contains methods and variables. The hybridization happens when you assign to String variables:
String myString="Hello!";
"What's so strange about that," you may ask. Indeed, it's the most intuitive way possible to create strings. We also have the convenience of using the + operator for concatenation:
int myInt=4; String anotherString=myString+"myInt is "+myInt;
The value of anotherString will be "Hello! myInt is 4". But because anotherString is an object, we can access the method members of the String class. For instance, it is easy to extract the first five characters of the anotherString:
String helloString=anotherString.substring(5);
The problem here is that we are instantiating our Strings without using the new operator. From a day-to-day programming standpoint, this isn't problematic. Since we use strings so much, it is very convenient. But as you start programming in Java, you need to understand that Strings are special-the String is the only programmer-defined type that can be instantiated without using the new operator.
Array Types
Array types define an array-an ordered set of like variables. Array types exist for all of the types in Java, including programmer-defined types. We can also have arrays of arrays, or multidimensional arrays-we leave that discussion for Chapter 4, "Syntax & Semantics." Basically, if we can create a variable of some type, we can create an array of variables of that type. However, creation of Java arrays is a little strange in that it requires the new operator:
Example 2-9a: Allocating space for arrays
int myIntArray[]; myIntArray=new int[3]; myType my0bjectArray[]; my0bjectArray=new myType[3];
The new operator tells the runtime environment to allocate space for the array As you can see, you don't have to declare the size of the array when you create the array variable. Once you create the array with the new operator, the array works the same as the arrays of C or Pascal:
Example 2-9b: Assigning to arrays
myIntArray[0]=0; myIntArray[1]=1; myIntArray[2]=2; myObjectArray[0]=new myType(); myObjectArray[1]=new myType(); myObjectArray[2]=new myType(); myObjectArray[0].myDataMember=0; myObjectArray[1].myDataMember=1; myObjectArray[2].myDataMember=2;
Java's arrays are beneficial for three reasons. First, as we have already seen, we don't have to set the size of the array as we declare it. Second, every Java array is a variable, so we can pass it as a parameter to a method and return it. We will look at this advantage of arrays when we look at methods in the next section. And third, we can easily find out how big an array is. For instance, the following code fragment will display the size of the array we defined above:
Example 2-9c: Getting the length of an array.
int len=myIntArray.length; System.out.println("Length of myIntArray="+len);
A method in Java is a subroutine-similar to the functions of C and Pascal. Methods have a return type and can take parameters.
For ease of demonstration, we are going to declare all of our methods static. The static modifier declares that the method will have a particular type of behavior within our object-oriented program (more on this later).
Our first concern is syntactical: the modifiers precede the return type, which precedes the method name and parameter list. The method body is enclosed in brackets:
<method modifiers> return type method name (<parameters>) { method body }
The method body can contain variable declarations and statements. We aren't restrained to declaring all of our variables before any statements, as we would be in C.
With the syntax out of the way, we first look at how methods return data. Then, we examine how we can pass data-or parameters-to our methods. Lastly, we look at a feature of Java's methods called method overloading, which allows us to give the same name to several different methods that differ in the parameters that they take.
Return types
All methods must specify a return type. The void return type of our main method in earlier examples is a special return type that means the method does not return a type. Methods that specify void as their return type are like Pascal's procedures. For methods that have another return type, we need to have a return statement. Our return type can be any of the types we described in the Variables section-any of the primitive types or any type we define with a class. Here we give examples of void methods and methods that have a return type:
Example 2-10: Calling methods.
public class MethodExamples{ static void voidMethod() { System.out.println("I am a void method"); } static int returnInt() { int i=4; System.out.println("returning 4"); return i;} static public final void main(String S[]) { System.out.println("Hello, methods!"); System.out.println("Calling a void method"); voidMethod(); int ans=returnInt(); System.out.print("method says -"); System.out.println(ans); } }
Note that we are calling our methods in much the same way that we would call them using a non-object-oriented language. This is true because a static method is calling other static methods within the same class. It's also true when nonstatic, or dynamic, methods call other dynamic methods. Things change when dynamic methods call static methods, or vice-versa, and when we are calling methods from another class. We explain all of this in the next section.
Parameter Passing
Let's move on to parameter passing. We can pass variables of any type-including types we define with classes-and arrays of any type. However, variables of primitive types act differently than variables of reference types do when passed to a method. We examine primitive variables first.
All primitive variables are passed to methods by value. This means that a copy of the variable is made when it is passed to the method. If we manipulate the variable inside the method, the original is not affected-only the copy. Let's illustrate this with an example:
Example 2-11: Primitive parameter passing.
class ParameterExample { static int addFour(int i) { i=i+4; System.out.println("local copy of i="+i); return i;} public final static void main(String S[]) { System.out.println("Hello, parameter passing!"); int i=10; System.out.print("Original value of i="+i); int j=addFour(i); System.out.println("value of j="+j); System.out.println("Current value of i="+i); } }
When we run our program, we get the following output:
Hello, parameter passing! Original value of i=10 value of j=14 Current value of i=10
The value i does not change, though we added 4 to i in the addFour method. However, reference variables are changed if they are manipulated within a method. Consider an example with an array of integers:
Example 2-12: Passing reference variable as a parameter
public class ReferenceParameterExample { static void changeArray(int referencellariable[]) { referencellariable[2]=100;} public static void main(String ARGV[]){ int anArray[]=new int[3]; anArray[2]=10; System.out.println("anArray[2]="); System.out.println(anArray[2]); changeArray(anArray); System.out.println(anArray[2]);} }
The output of our program will be:
anArray[2]= 10 100
When we pass a reference variable to a method, we are directly altering what that variable refers to-in this case, an array of ints.
Method Overloading
Have you ever had to write two subroutines that perform essentially the same function, but take different sets of parameters? Java allows you to give the same name to several methods that differ in the types of parameters they take. For instance, let's say we have a method that compares two integers:
Example 2-13a: Comparing two numbers.
public static String compareNums(int i, int j) { if (i==j) { return "Numbers "+i+" and "+j+" are equal";} if (i>j) { return "Number "+i+" greater than "+j;} return "Number "+j+" greater than "+i; }
Then we decide that instead of just comparing two numbers, we would like to compare three. It would be clumsy to define a new method with a name like compareThreeNums-luckily, we don't have to:
Example 2-13b: Overloading a method with additional parameters.
public static String compareNums(int i, int j, int k){ String S=compareNums(i,j); S=S+"\n", S=S+compareNums(i,k); return S;}
As long as the list of parameters is different, we can overload the method compareNums as many times as we would like. This is especially convenient when we want to perform the same action on different types of variables. As we will see in Chapter 4, we can not pass double variables into methods expecting ints. However, we can overload our method so that it accepts doubles or any other type:
Example 2-13c: Overloading a method with different types of parameters.
public static String compareNums(double i, double j) { if (i==j) { return "Numbers "+i+" and "+j+" are equal";} if (i>j) { return "Number "+i+" greater than "+j;} return "Number "+j+" greater than "+i; }
Method overloading is very convenient when we call our method. Instead of having to remember several different method names, we just have to remember one name. The compiler figures out which method should actually execute:
Example 2-13d: Calling overloaded methods.
public static void main(String ARGV[]) { int a=3; int b=4; int c=5; double d=3.3; double e=4.4; String S=compareNums(a,b); System.out.println(S); S=compareNums(a,b,c); System.out.println(S); S=compareNums(d,e,f); System.out.println(S); }
Now, we can finally start filling in some of the gaps in our discussion by explaining classes. When we first introduced classes, we said that classes contain variables and methods. This is true enough, as you probably noticed in the primary classes that we have been writing. But classes also form the basis of Java's object orientation, and we look at them in that light now.
Static vs. Dynamic Members
When we were looking at variables, we saw how classes could define types. Now let's demystify the static modifier we've been using for our methods. So far, we have just been using it on methods, so we explain what it means as a method modifier first. The static modifier can also be used with variables, but then it has a different meaning.
You may have noticed the absence of the static modifier in the methods that we have been writing here. This is because they are dynamic methods, which is the default. Dynamic methods and variables are members of objects-we can access them through an object variable. Static methods, on the other hand, can't be part of objects. Table 2-4 lists the syntax for calling dynamic methods versus static methods.
Method type | Modifier | Syntax |
---|---|---|
Dynamic | none (the default) | <object>.<method name> (<parameter list>) |
Static | static | <class name>.<method name> (<parameter list>) |
Table 2-4: Dynamic vs. static syntax.
We illustrate this with an example:
Example 2-14a: Static vs. dynamic methods.
public class StaticVsDynamic { int i=0; public static void staticMethod(int j) { System.out.println("A static method"); System.out.println("j="+j); } //dynamic methods public void setInt(int k) { i=k; System.out.println("setting i to "+k); } public int returnInt() { return i;} }
This example class has a static method and a dynamic method. The static method doesn't know about the dynamic members setInt, returnInt, and i. Here is a primary class that illustrates the differing syntax for calling static and dynamic methods:
Example 2-14b: Calling static and dynamic methods.
public class RunMe { public static void main(String S[]) { int i=0; StaticVsDynamic.staticMethod(10); //don't need to construct an object to call a static method StaticVsDynamic A=new StaticVsDynamic(); //must instantiate before calling dynamic method A.setInt(20); System.out.println("A.i = "+A.returnInt()); } }
We can also use the static modifier in conjunction with variables. The syntax for accessing the variable is basically the same:
<class name>.<variable name>
Since all methods and variables must be contained in a class, the static modifier is used to describe methods and variables that don't function as part of an object. They are more or less equivalent to the subroutines and global variables of a non-object-oriented language, except that we need to know the class that contains them in order to call them.
MemberAccess
We can control how our methods and variables are accessed in Java. So far, all of our class members have been public. The public modifier specifies that we can change our variables from any point in our program. We can limit the access of methods and variables with the access modifiers listed in Table 2-5.
Modifier | Description |
---|---|
Public | Member is accessible from outside the class. |
Private | Member is accessible only from inside the class. |
Protected | Public inside a package; private outside |
Table 2-5: Access modifiers.
In addition to these three, there is one more access modifier we discuss in Chapter 3, named private protected. The purpose of the access modifiers is to keep objects from interacting with class members they shouldn't mess with. It may seem that we are personifying our code. After all, the programmer has the ultimate control over what a program is doing. This is true, but the modifiers give us a way to guarantee that an object is going to behave the way we've programmed it to. If someone else uses our code, or if we use it at some later date when we have forgotten its nuances, we know that it will still work. We ensure this by allowing only certain parts of a class to be used by other classes.
This concept is called "data hiding," and it plays an important role in our discussion of object orientation in Chapter 3. For now, we illustrate this concept with a simple example. Suppose we are writing a class that keeps track of how much money a store is taking in and how many customers it has had. There are any number of other things we might want to keep track of, such as what we sold and when we sold it, but let's keep it simple for now. Here's our code:
Example 2-15: Private vs. public members.
public class SaleProcessor { private int Revenue=0; private int numSales=0; public void recordSale(int newRevenue){ Revenue=Revenue+newRevenue; numSales=numSales+1;} public int getRevenue() { return Revenue;} public int getNumSales() { return numSales;} }
Every time a customer buys something, the payment is processed with the ringUpCustomer method, which guarantees that the revenue is increased appropriately and that the customer tally is incremented. By not allowing direct access to these variables, we can better ensure their accuracy.
Class Inheritance
We have seen how modifiers make our classes more dependable because we can ensure that only certain methods and variables can be accessed from outside the class. Inheritance makes our classes reusable by allowing us to extend classes that have already been written by adding new functionality. Our new class will have all of the members of the original class, plus any we want to add.
Let's consider the saleProcessor class we developed in Example 2-15. Our boss says, "This is great! But I need a cash register class that tracks the bills in the register." We already have a start with the saleProcessor class-object-oriented programming allows us to expand upon it. To keep things simple, we ignore coins, bills over $10, and making change:
Example 2-16: Inheritance.
class CashRegister extends SaleProcessor{ private int Ones=0; private int Fives=0; private int Tens=0; CashRegister(int startOnes, int startFives, int startTens) { Ones=startOnes; Fives=startFives; Tens=startTens;} public void sellToCustomer(int newOnes, int newFives, int newTens) { int thisSum=0; Ones=Ones+newOnes; thisSum=newOnes; Fives=Fives+newFives; thisSum=thisSum+(newFives*5); Tens=Tens+newTens; thisSum=thisSum+(newTens*10); recordSale(thisSum); } public int numOnes() {return Ones;} public int numFives() {return Fives;} public int numTens() {return Tens;} }
Because we extend the saleProcessor class, we can build on that code instead of rewriting it. The ease of code reuse like this is one of the key advantages of object-oriented programming languages.
We have now covered the core of the Java language. You've seen that classes are the key building block of any Java program. Compared to classes, packages are very utilitarian. They simply contain classes and two of the other entities of Java we haven't discussed yet-exceptions and interfaces. Beyond this, they allow us to define members that are protected; they are public to classes within the same package, while private to those classes outside of the package.
Let's look at packages as containers first. In this sense, they play a very simple, though fundamental, role: they give the compiler a way to find the classes we need to compile our code. You know the System.out.println method we've been using to write output so far? Well, System is actually a class contained in the package java.lang, along with String. We use the import statement to access these classes. In our first example, we used this import statement to access the Date class.
import java.util.*;
The wildcard character at the end tells the compiler to import all of the classes in the java.date package. This is one of several packages included in the API, which we discuss in detail in
Chapter 6, "Discovering the Application Programming Interface." The java compiler implicitly defines a package for classes in the current directory and implicitly imports it. This is why we haven't had to explicitly place the classes that we've written into a package. If we want to explicitly place them into a package, the mechanics are simple:
Example 2-17: Package example.
package simplePackage; class SimpleClass1 { public void pubMethod() { System.out.println("This is a public method");} protected void protectedMethod() { System.out.println("This is a protected method");} }
This puts our class simpleClassl into simplePackage. We can put other classes into this package by putting the statement "package simplePackage;" at the top of the file. Other classes that we put into simplePackage will be able to access the method protectedMethod, while classes that aren't part of simplePackage will not be able to.
We still have a few chapters to go before you will understand all of the nuances of the Java language, but hopefully you have a good understanding of the basic structure. Before we begin to write our first applet, let's look at the runtime environment closely. As you may remember from Chapter 1, "The World Wide Web & Java," Java programs are run inside a virtual machine. What the program does know about is the runtime environment supplied to it by the virtual machine. As a language, Java has quite a few features that make it a delight for the programmer, including object orientation, built-in error handling, and threading. But what really sets Java apart is its platform independence, and this is entirely due to the structure of a runtime environment. Let's look at how the runtime environment affects our lives as programmers.
The Compilation & Execution Process
Java is a semi-compiled language. Unlike compiled languages, the Java compiler does not create a file that is ready to execute on our system. Instead, it creates a file that the Java runtime environment can run. This means that you can write and compile a Java program on one platform, move it to another platform, and it will work the same.
The .class file that the Java file creates contains bytecodes. Bytecodes are instructions for the Java runtime environmentsimilar to the machine instructions that a compiled C program contains. But instead of the operating system executing machine instructions, the Java runtime environment translates the bytecodes. When a program needs more memory, or access to an input/output device (the keyboard or monitor, for example), the runtime environment will service the request. The program itself, however, never has direct access to the system. In this way, the Java runtime environment acts as a firewall between a Java program and the system. This buffer is especially important in conjunction with applets-you would not want to be reading a Web page while an applet embedded in the page deletes your hard drive!
You may be wondering, "Well, if the Java runtime environment is translating those bytecodes, shouldn't we be calling Java a translated language, like Perl or BASIC?" True, the .class file is translated, like Perl or BASIC source code. But Java is much faster than these languages because it is easier for a computer to translate bytecodes than human-readable perl and BASIC source code.
In a sense, a .class file is a compressed version of a .java file; compressed in such a way that it is easy for the Java runtime environment to run it. However, the .class file is not optimally compressed. If we were guaranteed that every .class file was generated by javac, or some other Java compiler that followed all of the rules, then we would find that our .class files have more information than is strictly necessary This information is used to ensure that the bytecodes aren't trying to "trick" the Java runtime environment.
What kind of trick might this be? It would be something that would violate the runtime environment's role as protector of the local system. The Java compiler forces us to write code that doesn't try to trick the runtime environment, but a malicious hacker could write a program directly in bytecodes. The runtime environment requires the .class files it runs to have enough extra information so that a maliciously written file couldn't get around the restrictions of the Java language itself. As we shall see, this is one of three ways that the runtime environment that runs applets guarantees that applets don't harm the computer they are running on.
Even with the extra information contained within the bytecodes, our Java programs still run faster than those written in strictly translated languages such as Perl or BASIC. But the speed still lags behind programs that are compiled for the local machine, such as C and C++ programs. Luckily, help is on the way in the form of Just In Time (JIT) compiling. What JIT compiling does is compile-instead of translate-the bytecodes into machine language as the code is running. Once a code segment has run once, it is compiled to machine language. Subsequent executions will be as fast as if the code segment were compiled. Our Java programs will take an initial performance hit while the bytecodes are being compiled, but afterward, our code will run as fast as if it were compiled for the local platform.
Some times you will like turn off the JIT compilation.
For example, if you like to compare JIT run-time vs.
no-JIT one, or when you are working with
Java's exception tracing and JIT compilation hides numbers of lines where
were bugs, suppression of the JIT compilation would be good idea.
For applications using JDK 1.1.5 and previous (under Windows)
use the -nojit option when running the java command.
Example: java -nojit manoKlase 
For applications using JDK 1.1.6 and later (under Windows)
set the JAVA_COMPILER environment variable
to point to a non-existent JIT compiler, e.g. "nojit".
To turn off the JIT in the existing DOS prompt only (Windows):
set JAVA_COMPILER=nojit.
If you are using applets in Microsoft Internet Explorer 4.x (Windows),
Un-check the "Java JIT compiler enabled" setting located in the
"Java VM" section of the Internet Explorer preferences.
The preferences can be found by following
the pull-down menus: Tools -> Internet Options -> Advanced
Though the days of JIT compiling are just around the bend, you needn't worry that you'll have to change your code. Since JIT compiling is implemented as part of the runtime environment, it will simply result in faster executing code.
If you want to speak intelligently about Java development, you need to understand the architectural aspects of the Java runtime environment discussed in the previous section. But the structure of the runtime environment isn't something we need to concern ourselves with every time we sit down to program. The fact that our .class file is being run inside a virtual machine doesn't affect how we write our code-as long as it does what we want, we don't care whether it is compiled, semi-compiled, translated, or conjured into reality by magic.
The runtime environment performs one essential function that does affect how we write our code: garbage collection. Indeed, garbage collection makes our lives as programmers much easier. You probably guessed that the garbage collection the Java runtime environment performs has nothing at all to do with those candy wrappers, soda cans, and coffee cups that gather about any worthwhile coder's workstation. No, the garbage that is collected is made up of the variables within our program that are no longer needed.
Have you ever experienced a "memory leak"? Memory leaks occur when a program asks for a lot of memory from the operating system but never gives it back. After a while (sometimes a very short while) the amount of memory that is requested exceeds what the operating system has available, and your program crashes. Often, it takes the whole computer with it!
Memory leaks clearly result from a programmer's error, but they can be devilishly hard to find. All you have to do is request that memory be allocated and then forget to give it back. To find the cause of the leak, you must look at every place in your code where you allocate memory and make sure you are throwing the memory away. In programs that are thousands of lines long, this can be very, very time consuming.
In Java, we never have to worry about this. First, the only time our programs can ask for memory while running is when we make an assignment to an object variable or create an array This is done implicitly-we weren't leaving some vital step out of all of those examples we have had you type in this chapter! Also, garbage collection keeps us from freeing memory twice or writing to memory we have already freed.
When we make an assignment, the runtime environment puts a marker on the memory block that has been allocated. If we have created an object or array for local use inside a code block, a method body, for example, then the memory it was taking up will be given back to the operating system when the code block concludes. This process is called "collecting the garbage." But if we pass that variable on for use by the rest of our program (by either returning it from the method or making it part of an object or array passed in as a parameter), then it won't deallocate the memory.
"But," you may say, "you promised that garbage collection would have some relevance to me, the programmer." Well, it does. First, you don't have to worry about memory leaks, because the runtime environment keeps track of how your program is using memory. So garbage collection kills off memory you don't need anymore.
While garbage collection clearly identifies garbage, it also does not throw away memory that is not garbage. Let's consider an example that has cost many beginning C programmers hours of debugging time and handfuls of hair:
public char * thisWontWork() { char localArray[6]; strcpy(localArray,"hello"); return localArray;}
Supposedly, this function creates a character array, fills it with "hello", and returns it. Unfortunately, this isn't quite what happens. Since we declared localArray within this function, the memory it takes is killed off at the conclusion of the function, despite the fact that we return localArray! But the story gets worse. If we try to print out localArray as soon as the function returns, it's usually still there because the memory hasn't been overwritten yet. But eventually, the memory will get overwritten, and it will take just long enough so that we think an entirely different part of our program is causing the error!
Because Java's garbage collection tracks our variables dynamically, we don't need to worry about encountering this problem. Let's consider the equivalent Java code:
public char[] thisWillWork(){ char localArray[6]; localArray=('h','e','l','l','o'); return localArray;}
The garbage collector notices that localArray is being returned and is, thus, still being used. It will only deallocate localArray when there is no variable assigned to it or when the program ends.
So now you have trudged through the basics of the Java language and the Java runtime environment. It's time for the fun stuffapplets!
Because applets are embedded in Web pages, applet development has a few more twists and turns than the usual program development cycle. Hopefully, by the end of this section, you will have adapted to the process of coding and running your own applets. You will then be prepared to attack Chapter 5, "How Applets Work," and join the scores of applet writers who are waking up the Web.
Let's dive right in and write our first applet. Crack open your favorite text editor and type along with me:
Example 2-18a: Your first applet.
import java.applet.*; import java.awt.*; public class FirstApplet extends Applet { public void paint(Graphics g) { g.drawString("Hello, Applets!",50,50);} }
You'll notice that this program is quite different from the programs we have been writing. We will dissect it in a moment-first, let's get it running. Just follow this recipe:
Esample 2-18b: Web page for firstllpplet.
<APPLET CODE=FirstApplet.class WIDTH=200 height=200> You aren't using a Java capable web browser. </APPLET>
You can save it under any name you choose as long as it ends with .html. The text between the <APPLET...> and </APPLET> tags is displayed to Web browsers that don't know how to run applets.
Figure 2-1: Your first applet.
Note that we aren't using a Java-capable Web browser to look at our applet at this stage. Neither the appletviewer nor Netscape Navigator +2.0 or Internet Explorer +3.0 is smart enough to recognize that we have recompiled our code. In either case, each time we change our code, we have to quit the program that is running our applet and start it up again. The appletviewer is a much smaller program and tends to be quicker to start up. Therefore, you'll probably find it preferable to use the appletviewer during the development cycle.
Now that you have written your first applet, let's look at its anatomy. The class we just wrote is still a primary class, though it is quite different from the primary classes we wrote at the beginning of the chapter. Previously, we had defined only one method-main. Here, we have defined two. As we will see in Chapter 5, "How Applets Work," there are several more methods we need to define to create interesting effects like animation.
The key to understanding applet programming is understanding the basics of our primary class. Let's note a couple of differences between this primary class and the ones we were writing earlier:
Within this last observation is the key to understanding applet anatomy. Previously, the runtime environment called the main method, from which our entire program ran. When the browser's runtime environment starts our program, it first calls the init method, but as we observed, the entire program is not run out of the init method. How does our paint method get called? Whenever the screen needs to get painted, the paint method is called. If you cover up the Web browser with another window and then uncover it, the paint method will be called again.
The Applet class has a large number of methods that are called in response to user actions, such as moving the mouse or touching certain keys on the keyboard. We describe all of these in greater detail in Chapter 5. For now we demonstrate the mouseDown method, which is called every time the first mouse button is clicked within the applet's space. We use it to write "Hello, Applet!" at the position where the mouse is clicked:
Example 2-19: An applet that interacts with the mouse.
import java.applet.*; import java.awt.*; public class SecondApplet extends Applet { int curX=50; int curY=50; public boolean mouseDown(Event e, int x, int y) { curX=x; curY=y; repaint(); return true;} public void paint(Graphics g) { g.drawString("Hello, Applets!",curX,curY);} }
Notice that we are now making a call to repaint in mouseDown. The repaint method tells the runtime environment to update the screen. It does this by passing the screen, in the form of a Graphics object, to our paint method. After we click the mouse in the lower right-hand corner, our applet will look like Figure 2-2.
Figure 2-2: Interactive applet.
Besides the fact that the runtime environment calls any method of a variety of methods when necessary, the primary class for applets behaves the same as the primary classes we built earlier. We can define new methods-rather than just override the ones defined in the Applet class-and we can instantiate new classes.
So far, we have been using the appletviewer to run our applets locally. Now we are going to talk about making your applets available to the world via the World Wide Web.
Probably the most important ingredient is access to a Web server. We need to move our .class files and the .html files in which the applet is embedded into the space from which your web documents are served. If you program on the same machine from which you'll be serving your applets, you probably just need to move your files from one directory to another. If your Web server resides on a different machine than the one you work on, you will need to move them onto that machine.
You should probably talk to your webmaster for details about moving the files into Web space. Regarding the two examples we wrote earlier, a total of four files, you just need to make sure that the .class file is in the same directory as the .html file that references it. Then, just point your Web browser to the .html file. The appletviewer can also load applets from the Internet-instead of telling it to open a file, tell it to open a URL.
Our .class file for the SecondApplet.html page we wrote needed to be in the same directory as the secondApplet.html file. If we want, though, our .class file can reside elsewhere within the Web space of the server. We can control this with the CODEBASE parameter of the Applet tag. Here is an example .html file that contains the CODEBASE attribute.
Example 2-20: Using the CODEBASE attribute.
<APPLET CODE=ProgramRunnerApplet.class CODEBASE="class.dir" WIDTH=300 HEIGHT=150> You aren't using a Java capable web browser. </APPLET>
In this case, SecondApplet.class needs to be in the directory, class.dir, below the directory that contains our example .html file. The value for CODEBASE can also be an absolute path, but it will be absolute to the top of the Web space. In addition to the CODEBASE attribute, there are several others that you can use inside the APPLET tag to customize the appearance and behavior of your applet. These are summarized in Table 2-6.
Attribute | Meaning | required? |
---|---|---|
CODE | The compilled applet should be a .class file | Yes |
WIDTH | The width in pixels that applet will take up on the Web page | Yes |
HEIGHT | The height in pixels that applet will take up on the Web page | Yes |
CODEBASE | A directory on the Web server in which to look for the .class file specified CODE | No |
ALT | Specifies alternate text to display in case the browser understands the APPLET tag but does not support Java. | No |
NAME | Gives a name to the applet - other applets on the page can look it up by name. | No |
ALIGN | Determines how applet will be aligned on the page. | No |
VSPACE | Sets a vertical margin, described in pixels, around the applet. | No |
HSPACE | Sets a horizontal margin, described in pixels, around the applet. | No |
Table 2-6: Attributes of the APPLET tag.
In addition to these attributes, we can also pass information into an applet using the <PARAM...> tag. Any number of PARAM tags can reside between the <APPLET...> and </APPLET> tags. Inside the PARAM tag you can specify a NAME and a VALUE. The applet is able to look up these pairs while it's running. In Chapter 5, we develop several applets that make use of the PARAM tag. Here is the how the Web page looks for one of these examples:
Example 2-27: Using the PARAM tag.
<APPLET CODE="AnimatedCursorApplet.class" HEIGHT=250 WIDTH=250> <PARAM NAME="CURSORFILEO" VALUE="images/anim0.gif"> <PARAM NAME="CURSORFILE1" VALUE="images/animl.gif"> <PARAM NAME="CURSORFILE2" VALUE="images/anim2.gif"> <PARAM NAME="CURSORFILE3" VALUE="images/anim3.gif"> <PARAM NAME="CURSORFILE4" VALUE="images/anim4.gif"> <PARAM NAME="CURSORFILE5" VALUE="images/anim5.gif"> </APPLET>
This particular applet uses the name-value pairs to get images for an animation, but it is entirely up to the individual applets how to interpret them. Of course, applets don't have to look at them at all.
Automatically Documenting Your Code
Documenting your code is always very important. A tool included in the JDK, javadoc, creates Web pages based on your documented source code. It creates the Web page by looking for special comments within your source code. Here is an example:
Example 2-22: Automatic documentation.
/** I wrote a class. I even documented it! * I documented it using javadoc. */ class documentedClass { /** Now I am going to document this variable. * It's an int! * I love documenting! */ public int documentedVariable; /** How about a documented method! * This is a method that takes a String as a parameter. *It doesn't do anything with it-I'm just documenting for fun. * But if it did-oh boy-I would tell you all about it!*/ public void documentedMethod(String x) ({ System.out.println("Documented method");} }
Then, we can run javadoc on our code by typing:
javadoc -d <API directory> documentedClass.java
Where <API directory> is the directory in which the rest of the documentation for the Application Programming Interface resides.
No javadoc tool exists in the Macintosh JDK at the time of this
writing.
Javadoc will output an html file, documentClass.html. In Figure 2-2, we pointed the Web browser at the file.
Figure 2-3: Documentation with Javadoc.
As you can see, our comments are placed in a Web page. Notice that our String parameter is hyperlinked; the link goes to documentation about the String class. All class parameters are hyperlinked, and all comments that start with /** are written into the Web page. If you want to place a link to some class, you can do this with @see tag, followed by the name of the class. This will set a link to that particular class. You can also include regular HTML tags within the comments.
Sets a vertical margin, described in pixels, around the applet. Now you have a feel for the basics of the Java language and the programming environment. In Chapter 3, we explore the principles of object-oriented programming, and how they are expressed in Java, in more depth.