package: the library unit

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

import
java.util.*;

If
you want to bring in a single class, you can name that class in the
import
statement

import
java.util.Vector;

Now
you can use
Vector
with no qualification. However, none of the other classes in
java.util
are available.

This
potential clashing of names is why it’s important to have complete
control over the name spaces in Java, and to be able to create a completely
unique name regardless of the constraints of the Internet.

So
far, most of the examples in this book have existed in a single file and have
been designed for local use, and haven’t bothered with package names. (In
this case the class name is placed in the “default package.”) This
is certainly an option, and for simplicity’s sake this approach will be
used whenever possible throughout the rest of the book. If you’re
planning to create a program that is “Internet friendly,” however,
you must think about preventing class name clashes.

A
library is also a bunch of these class files. Each file has one class that is
public
(you’re not forced to have a
public
class, but it’s typical), so there’s one component for each file.
If you want to say that all these components (that are in their own separate
.java
and
.class
files)
belong together, that’s where the
package
keyword comes in.

When
you say:

package
mypackage;

at
the beginning of a file, where the
package
statement
must
appear
as the first non-comment in the file, you’re stating that this
compilation unit is part of a library named
mypackage.
Or, put another way, you’re saying that the
public
class name within this compilation unit is under the umbrella of the name
mypackage,
and if anyone wants to use the name they must either fully specify the name or
use the
import
keyword in combination with
mypackage
(using the choices given previously). Note that the convention for Java
packages is to use all lowercase letters, even for intermediate words.

For
example, suppose the name of the file is
MyClass.java.
This means there can be one and only one
public
class in that file, and the name of that class must be
MyClass
(including the capitalization):

package mypackage;
public class MyClass { <p><tt>  // . . . </tt></p>

Now,
if someone wants to use
MyClass
or, for that matter, any of the other
public
classes in
mypackage,
they must use the
import
keyword to make the name or names in
mypackage
available. The alternative is to give the fully-qualified name:

mypackage.MyClass
m = new mypackage.MyClass();

The
import
keyword can make this much cleaner:

import mypackage.*;
// . . . 
MyClass m = new MyClass();

It’s
worth keeping in mind that what the
package
and
import
keywords allow you to do, as a library designer, is to divide up the single
global name space so you won’t have clashing names, no matter how many
people get on the Internet and start writing classes in Java.

Creating
unique package names

It
also solves two other problems: creating unique package names and finding those
classes that might be buried in a directory structure someplace. This is
accomplished, as was introduced in Chapter 2, by encoding the path of the
location of the
.class
file into the name of the
package.
The compiler enforces this, but by convention, the first part of the
package
name is the Internet domain name of the creator of the class, reversed. Since
Internet domain names are guaranteed to be unique (by InterNIC,
[24]
who controls their assignment)
if
you follow this convention it’s guaranteed that your
package
name will be unique and thus you’ll never have a name clash. (That is,
until you lose the domain name to someone else who starts writing Java code
with the same path names as you did.) Of course, if you don’t have your
own domain name then you must fabricate an unlikely combination (such as your
first and last name) to create unique package names. If you’ve decided to
start publishing Java code it’s worth the relatively small effort to get
a domain name.

The
Java interpreter proceeds as follows. First, it finds the environment variable
CLASSPATH
(set via the operating system when Java, or a tool like a Java-enabled browser,
is installed on a machine). CLASSPATH contains one or more directories that are
used as roots for a search for
.class
files. Starting at that root, the interpreter will take the package name and
replace each dot with a slash to generate a path name from the CLASSPATH root
(so
package
foo.bar.baz

becomes
foobarbaz
or
foo/bar/baz
depending
on your operating system). This is then concatenated to the various entries in
the CLASSPATH. That’s where it looks for the
.class
file with the name corresponding to the class you’re trying to create.
(It also searches some standard directories relative to where the Java
interpreter resides).

package
com.bruceeckel.util;

Now
this package name can be used as an umbrella name space for the following two
files:

//: Vector.java
// Creating a package
package com.bruceeckel.util;
 
public class Vector {
  public Vector() {
    System.out.println(
      "com.bruceeckel.util.Vector");
  }
} ///:~ 

When
you create your own packages, you’ll discover that the
package
statement must be the first non-comment code in the file. The second file looks
much the same:

//: List.java
// Creating a package 
package com.bruceeckel.util;
 
public class List {
  public List() {
    System.out.println(
      "com.bruceeckel.util.List");
  }
} ///:~ 

Both
of these files are placed in the subdirectory on my system:

C:DOCJavaTcombruceeckelutil

If
you walk back through this, you can see the package name
com.bruceeckel.util,
but what about the first portion of the path? That’s taken care of in the
CLASSPATH environment variable, which is, on my machine:

CLASSPATH=.;D:JAVALIB;C:DOCJavaT

You
can see that the CLASSPATH can contain a number of alternative search paths.
There’s a variation when using JAR files, however. You must put the name
of the JAR file in the classpath, not just the path where it’s located.
So for a JAR
named
grape.jar
your classpath would include:

CLASSPATH=.;D:JAVALIB;C:flavorsgrape.jar

//: LibTest.java
// Uses the library
package c05;
import com.bruceeckel.util.*;
 
public class LibTest {
  public static void main(String[] args) {
    Vector v = new Vector();
    List l = new List();
  }
} ///:~ 

When
the compiler encounters the
import
statement, it begins searching at the directories specified by CLASSPATH,
looking for subdirectory combruceeckelutil, then seeking the compiled files
of the appropriate names (
Vector.class
for
Vector
and
List.class
for
List).
Note that both the classes and the desired methods in
Vector
and
List
must be
public.


Automatic
compilation

If
a class is not in a
.java
file of the same name as that class, this behavior will not occur for that class.


Collisions

import com.bruceeckel.util.*;
import java.util.*; 

Since
java.util.*
also contains a
Vector
class, this causes a potential collision. However, as long as the collision
doesn’t actually occur, everything is OK – this is good because
otherwise you might end up doing a lot of typing to prevent collisions that
would never happen.

The
collision
does
occur if you now try to make a
Vector:

Vector
v = new Vector();

Which
Vector
class does this refer to? The compiler can’t know, and the reader
can’t know either. So the compiler complains and forces you to be
explicit. If I want the standard Java
Vector,
for example, I must say:

java.util.Vector
v = new java.util.Vector();

A
custom tool library

With
this knowledge, you can now create your own libraries of tools to reduce or
eliminate duplicate code. Consider, for example, creating an alias for
System.out.println( )
to reduce typing. This can be part of a package called
tools:

//: P.java
// The P.rint &amp; P.rintln shorthand
package com.bruceeckel.tools;
 
public class P {
  public static void rint(Object obj) {
    System.out.print(obj);
  }
  public static void rint(String s) {
    System.out.print(s);
  }
  public static void rint(char[] s) {
    System.out.print(s);
  }
  public static void rint(char c) {
    System.out.print(c);
  }
  public static void rint(int i) {
    System.out.print(i);
  }
  public static void rint(long l) {
    System.out.print(l);
  }
  public static void rint(float f) {
    System.out.print(f);
  }
  public static void rint(double d) {
    System.out.print(d);
  }
  public static void rint(boolean b) {
    System.out.print(b);
  }
  public static void rintln() {
    System.out.println();
  }
  public static void rintln(Object obj) {
    System.out.println(obj);
  }
  public static void rintln(String s) {
    System.out.println(s);
  }
  public static void rintln(char[] s) {
    System.out.println(s);
  }
  public static void rintln(char c) {
    System.out.println(c);
  }
  public static void rintln(int i) {
    System.out.println(i);
  }
  public static void rintln(long l) {
    System.out.println(l);
  }
  public static void rintln(float f) {
    System.out.println(f);
  }
  public static void rintln(double d) {
    System.out.println(d);
  }
  public static void rintln(boolean b) {
    System.out.println(b);
  }
} ///:~ 

All
the different data types can now be printed out either with a newline (
P.rintln( ))
or without a newline (
P.rint( )).

You
can guess that the location of this file must be in a directory that starts at
one of the CLASSPATH locations, then continues
com/bruceeckel/tools.
After compiling, the
P.class
file can be used anywhere on your system with an
import
statement:

//: ToolTest.java
// Uses the tools library
import com.bruceeckel.tools.*;
 
public class ToolTest {
  public static void main(String[] args) {
    P.rintln("Available from now on!");
  }
} ///:~ 

So
from now on, whenever you come up with a useful new utility, you can add it to
the
tools
directory. (Or to your own personal
util
or
tools
directory.)


Classpath
pitfall

The
P.java
file brought up an interesting pitfall. Especially with early implementations
of Java, setting the classpath correctly is generally quite a headache. During
the development of this book, the
P.java
file was introduced and seemed to work fine, but at some point it began
breaking. For a long time I was certain that this was the fault of one
implementation of Java or another, but finally I discovered that at one point I
had introduced a program (
CodePackager.java,
shown in Chapter 17)

that
used a different class
P.
Because it was used as a tool, it was
sometimes
placed in the classpath, and other times it wasn’t. When it was, the
P
in
CodePackager.java
was
found first by Java when executing a program in which it was looking for the
class in
com.bruceeckel.tools,
and the compiler would say that a particular method couldn’t be found.
This was frustrating because you can see the method in the above class
P
and no further diagnostics were reported to give you a clue that it was finding
a completely different class. (That wasn’t even
public.)

If
you’re having an experience like this, check to make sure that
there’s only one class of each name anywhere in your classpath.

Using
imports to change behavior

A
feature that is missing from Java is C’s
conditional
compilation
,
which allows you to change a switch and get different behavior without changing
any other code. The reason such a feature was left out of Java is probably
because it is most often used in C to solve cross-platform issues: different
portions of the code are compiled depending on the platform that the code is
being compiled for. Since Java is intended to be automatically cross-platform,
such a feature should not be necessary.

However,
there are other valuable uses for conditional compilation. A very common use is
for debugging code. The debugging features are enabled during development, and
disabled for a shipping product. Allen Holub (www.holub.com) came up with the
idea of using packages to mimic conditional compilation. He used this to create
a Java version of C’s very useful
assertion
mechanism
,
whereby you can say “this should be true” or “this should be
false” and if the statement doesn’t agree with your assertion
you’ll find out about it. Such a tool is quite helpful during debugging.

Here
is the class that you’ll use for debugging:

//: Assert.java
// Assertion tool for debugging
package com.bruceeckel.tools.debug;
 
public class Assert {
  private static void perr(String msg) {
    System.err.println(msg);
  }
  public final static void is_true(boolean exp) {
    if(!exp) perr("Assertion failed");
  }
  public final static void is_false(boolean exp){
    if(exp) perr("Assertion failed");
  }
  public final static void
  is_true(boolean exp, String msg) {
    if(!exp) perr("Assertion failed: " + msg);
  }
  public final static void
  is_false(boolean exp, String msg) {
    if(exp) perr("Assertion failed: " + msg);
  }
} ///:~ 

This
class simply encapsulates boolean tests, which print error messages if they
fail. In Chapter 9, you’ll learn about a more sophisticated tool for
dealing with errors called
exception
handling
,
but the
perr( )
method will work fine in the meantime.

When
you want to use this class, you add a line in your program:

import
com.bruceeckel.tools.debug.*;

To
remove the assertions so you can ship the code, a second
Assert
class is created, but in a different package:

//: Assert.java
// Turning off the assertion output 
// so you can ship the program.
package com.bruceeckel.tools;
 
public class Assert {
  public final static void is_true(boolean exp){}
  public final static void is_false(boolean exp){}
  public final static void
  is_true(boolean exp, String msg) {}
  public final static void
  is_false(boolean exp, String msg) {}
} ///:~ 

Now
if you change the previous
import
statement to:

import
com.bruceeckel.tools.*;

The
program will no longer print out assertions. Here’s an example:

//: TestAssert.java
// Demonstrating the assertion tool
package c05;
// Comment the following, and uncomment the
// subsequent line to change assertion behavior:
import com.bruceeckel.tools.debug.*;
// import com.bruceeckel.tools.*;
 
public class TestAssert {
  public static void main(String[] args) {
    Assert.is_true((2 + 2) == 5);
    Assert.is_false((1 + 1) == 2);
    Assert.is_true((2 + 2) == 5, "2 + 2 == 5");
    Assert.is_false((1 + 1) == 2, "1 +1 != 2");
  }
} ///:~ 

By
changing the
package
that’s imported, you change your code from the debug version to the
production version. This technique can be used for any kind of conditional code.

Package
caveat


[23]
There’s nothing in Java that forces the use of an interpreter. There
exist native-code Java compilers that generate a single executable file.

[24]
ftp://ftp.internic.net

More by Author

Must Read