package: the library unit

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

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 foo\bar\baz 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).

//: 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:\DOC\JavaT\com\bruceeckel\util

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:\JAVA\LIB;C:\DOC\JavaT

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:

//: 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 com\bruceeckel\util, 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 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( )).

//: 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



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: August 20, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT When you look at natural user interfaces as a developer, it isn't just fun and games. There are some very serious, real-world usage models of how things can help make the world a better place – things like Intel® RealSense™ technology. Check out this upcoming eSeminar and join the panel of experts, both from inside and outside of Intel, as they discuss how natural user interfaces will likely be getting adopted in a wide variety …

  • Managing your company's financials is the backbone of your business and is vital to the long-term health and viability of your company. To continue applying the necessary financial rigor to support rapid growth, the accounting department needs the right tools to most efficiently do their job. Read this white paper to understand the 10 essentials of a complete financial management system and how the right solution can help you keep up with the rapidly changing business world.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds