Bruce Eckel's Thinking in Java Contents | Prev | Next

Building a Java program

There are several other issues you must understand before seeing your first Java program.

Name visibility

A problem in any programming language is the control of names. If you use a name in one module of the program, and another programmer uses the same name in another module, how do you distinguish one name from another and prevent the two names from “clashing”? In C this is a particular problem because a program is often an unmanageable sea of names. C++ classes (on which Java classes are based) nest functions within classes so they cannot clash with function names nested within other classes. However, C++ still allowed global data and global functions, so clashing was still possible. To solve this problem, C++ introduced namespaces using additional keywords.

Java was able to avoid all of this by taking a fresh approach. To produce an unambiguous name for a library, the specifier used is not unlike an Internet domain name. In fact, the Java creators want you to use your Internet domain name in reverse since those are guaranteed to be unique. Since my domain name is BruceEckel.com, my utility library of foibles would be named com.bruceeckel.utility.foibles. After your reversed domain name, the dots are intended to represent subdirectories.

In Java 1.0 and Java 1.1 the domain extension com, edu, org, net, etc., was capitalized by convention, so the library would appear: COM.bruceeckel.utility.foibles. Partway through the development of Java 1.2, however, it was discovered that this caused problems and so now the entire package name is lowercase.

This mechanism in Java means that all of your files automatically live in their own namespaces, and each class within a file automatically has a unique identifier. (Class names within a file must be unique, of course.) So you do not need to learn special language features to solve this problem – the language takes care of it for you.

Using other components

Whenever you want to use a predefined class in your program, the compiler must know how to locate it. Of course, the class might already exist in the same source code file that it’s being called from. In that case, you simply use the class – even if the class doesn’t get defined until later in the file. Java eliminates the “forward referencing” problem so you don’t need to think about it.

What about a class that exists in some other file? You might think that the compiler should be smart enough to simply go and find it, but there is a problem. Imagine that you want to use a class of a particular name, but the definition for that class exists in more than one file. Or worse, imagine that you’re writing a program, and as you’re building it you add a new class to your library that conflicts with the name of an existing class.

To solve this problem, you must eliminate all potential ambiguities. This is accomplished by telling the Java compiler exactly what classes you want using the import keyword. import tells the compiler to bring in a package, which is a library of classes. (In other languages, a library could consist of functions and data as well as classes, but remember that all code in Java must be written inside a class.)

Most of the time you’ll be using components from the standard Java libraries that come with your compiler. With these, you don’t need to worry about long, reversed domain names; you just say, for example:

import java.util.Vector;

to tell the compiler that you want to use Java’s Vector class. However, util contains a number of classes and you might want to use several of them without declaring them all explicitly. This is easily accomplished by using ‘ *’ to indicate a wildcard:

import java.util.*;

It is more common to import a collection of classes in this manner than to import classes individually.

The static keyword

Ordinarily, when you create a class you are describing how objects of that class look and how they will behave. You don’t actually get anything until you create an object of that class with new, and at that point data storage is created and methods become available.

But there are two situations in which this approach is not sufficient. One is if you want to have only one piece of storage for a particular piece of data, regardless of how many objects are created, or even if no objects are created. The other is if you need a method that isn’t associated with any particular object of this class. That is, you need a method that you can call even if no objects are created. You can achieve both of these effects with the static keyword. When you say something is static, it means that data or method is not tied to any particular object instance of that class. So even if you’ve never created an object of that class you can call a static method or access a piece of static data. With ordinary, non- static data and methods you must create an object and use that object to access the data or method, since non- static data and methods must know the particular object they are working with. Of course, since static methods don’t need any objects to be created before they are used, they cannot directly access non- static members or methods by simply calling those other members without referring to a named object (since non- static members and methods must be tied to a particular object).

Some object-oriented languages use the terms class data and class methods , meaning that the data and methods exist only for the class as a whole, and not for any particular objects of the class. Sometimes the Java literature uses these terms too.

To make a data member or method static, you simply place the keyword before the definition. For example, this produces a static data member and initializes it:

class StaticTest {
    static int i = 47;
}

Now even if you make two StaticTest objects, there will still be only one piece of storage for StaticTest.i. Both objects will share the same i. Consider:

StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest(); 

At this point, both st1.i and st2.i have the same value of 47 since they refer to the same piece of memory.

There are two ways to refer to a static variable. As indicated above, you can name it via an object, by saying, for example, st2.i. You can also refer to it directly through its class name, something you cannot do with a non-static member. (This is the preferred way to refer to a static variable since it emphasizes that variable’s static nature.)

StaticTest.i++;

The ++ operator increments the variable. At this point, both st1.i and st2.i will have the value 48.

Similar logic applies to static methods. You can refer to a static method either through an object as you can with any method, or with the special additional syntax classname.method( ). You define a static method in a similar way:

class StaticFun {
  static void incr() { StaticTest.i++; }
}

You can see that the StaticFun method incr( ) increments the static data i. You can call incr( ) in the typical way, through an object:

StaticFun sf = new StaticFun();
sf.incr();

Or, because incr( ) is a static method, you can call it directly through its class:

StaticFun.incr();

While static, when applied to a data member, definitely changes the way the data is created (one for each class vs. the non- static one for each object), when applied to a method it’s not so dramatic. An important use of static for methods is to allow you to call that method without creating an object. This is essential, as we will see, in defining the main( ) method that is the entry point for running an application.

Like any method, a static method can create or use named objects of its type, so a static method is often used as a “shepherd” for a flock of instances of its own type.

Contents | Prev | Next