Unwrap the package statement's potential

Minimize project complexity and maximize code reuse

1 2 Page 2
Page 2 of 2

Dynamic package hierarchy

As a positive side effect of embracing code refactoring, you should also prepare to embrace a constantly evolving package hierarchy. Imagine a growing and maturing tree: as your number of Java types (classes and interfaces) grows, so does the leaf-to-branch ratio in your packages. Whenever this ratio reaches a threshold, your instinct is to relieve the weight on the branch, and create subbranches and redistribute the classes and interfaces in these new subbranches. I try to keep my types-per-package ratio very low, in the range of 7 to 10. (Compare this guideline metric with java.lang's range of 30 or more and java.util's 40 or more, depending on the API's exact version. Ever felt overwhelmed by java.util's long list of reusable types? Low types-per-package ratios prevent programmers from losing themselves in your APIs.)

The same need for package branching due to growth applies to the ratio of subpackages to parent package. If this ratio reaches a threshold, then you should instinctually reorganize the subpackages so that fewer weigh down the parent package. Keep the package tree aesthetically balanced (i.e., maintain over time its fractal-like tree structure, recalling that software is part art, part science).

By now I hear loud voices shouting, "How does a dynamic package hierarchy fit in with the need for backward-compatible library classes?" Clearly there's a conflict of interest here: libraries need to grow in a library user-friendly way. Luckily, the main invariant a library user relies on is the detailed APIs a library exports (i.e., the classes' shorthand names and the precise method signatures). Java's import language feature makes changing the source package from which a type hails less of an obstacle than it could be otherwise.

In practice, every time a class or interface moves from one package to another as a result of package hierarchy growing pains, you need to bump your library's version number and include in your release notes the incompatible name changes that were made (e.g., how Sun handled Swing's package name change some years ago). In my experience, library users gladly accept the minor short-term pain involved in occasionally changing a few import statements with the reassuring knowledge that the company's library organization is not allowed to deteriorate to the point where it becomes a liability instead of a key asset. (Nowadays, many Java tools support this view of a dynamic package hierarchy and make package name and structure changes as painless as possible).

Package-scope declarations

Tightly linked to package declarations is another Java language feature rarely properly taught by Java teachers (including in text books) or correctly assimilated by new Java programmers: the correct scoping of class members (i.e., fields, methods, constructors, and nested classes (since Java 1.1)).

Nobody has a problem with public and private scope. These scopes are fairly well understood because they are unambiguous and therefore usually appropriately used. Protected scope, on the other hand, is often thoroughly misunderstood; and when we come to package-scope, we're in a real cognitive disaster zone.

Java's designers mistakenly designed package-scope as the default scope, and consequently it is a keyword-less scope declaration (all scopes should have a keyword required, and no scope should have been the default). It's the lack of a manifest keyword that lies at the root of the problem: the majority of introductory Java books and courses leave scoping semantics and rules out of the first few chapters because correct scoping is completely unnecessary when tackling many of the classic teaching programming examples.

So we were all taught to use package-scope by default, because it conveniently lacks a keyword and therefore lets us use it without thinking about the implications (that old trademark laziness again...).

Of course, as your Java skills mature, you slowly realize that correctly scoping your class members, especially fields, methods, and constructors, is very important indeed.

Package-scope declaration for fields: almost always an error

For fields, the only scope appropriate in 90 percent of cases is private. This is a direct result of the encapsulation principle, one of the pillars of object orientation. Secondly, the consensus in object-orientation circles is that object composition is far more appropriate in most cases than subclassing, so the protected keyword should be used more infrequently than first thought. The only justifiable instance where you need to declare fields public is when you export public constants, as in:

public static final XXXX THE_CONSTANT;

The use of package-scope for fields should be viewed as equally unacceptable as declaring nonconstant fields public, because both approaches shatter the encapsulation of objects by exposing their implementation guts. And yet package-scoped fields can be seen in Java source code everywhere you look: in books, articles, newsgroup postings, and last but not least, production code. Here's just one example from the core libraries (Sun's implementation): class javax.swing.ImageIcon declares the following field:

transient int loadStatus = 0;

Notice how this declaration includes an implicit package-scope declaration, yet no other classes in package javax.swing access this field in any ImageIcon objects. Indeed, since ImageIcon also declares a public accessor method for this object state,

public int getImageLoadStatus() {
   return loadStatus;
}

it is clear that the field declaration should have specified private as the correct scope.

Package-scope declaration for methods and constructors

Compared to scoping fields, scoping methods and constructors is somewhat more complex. Both methods and constructors can legitimately be scoped using four possible scopes:

  • Public: when the whole world needs to see and access the method/constructor
  • Protected: when only subclasses need to see and access the method/constructor
  • Private: when only the declaring class needs access to the method/constructor
  • Package-scope: when other classes in the same package (this excludes subpackages) need to access the method/constructor

Clearly, if a subsystem or module lives in its own package, then package-scope declaration of methods and constructors is often a perfectly legitimate and indeed common necessity.

Good things come in small packages

The package statement is normally the first line of noncomment code in any Java source code file, yet, nearly seven years after Java's birth, many programmers still fail to exploit the substantial potential benefits of correct package keyword use. This diminutive keyword allows you to tackle overall project complexity head-on by subdividing and modularizing your system's architecture and lets you create a working long-term software process framework for code reuse. Such potential is no mean feat. So, next time you create a new package, consider the package keyword's bigger issues more thoroughly. It will be a wise, long-term investment.

Laurence Vanhelsuwé is a senior software engineer living in Scotland. With more than 20 years of programming experience—seven exclusively on Java—Laurence currently juggles his time between software development, authoring Java-related articles and books, technical editing, and Java training.

Learn more about this topic

This story, "Unwrap the package statement's potential" was originally published by JavaWorld.

Related:

Copyright © 2003 IDG Communications, Inc.

1 2 Page 2
Page 2 of 2