Chapter 18. Packages

 

For some reason a glaze passes over people's faces when you say “Canada”. Maybe we should invade South Dakota or something.

 
 --Sandra Gotlieb, wife of Canadian ambassador to U.S. (1981–1989)

Packages define units of software that can be distributed independently and combined with other packages to form applications. Packages have members that are related classes, interfaces, and subpackages, and may contain additional resource files (such as images) used by the classes in the package. Packages are useful for several reasons:

  • Packages create groupings for related interfaces and classes. For example, a set of library classes for performing statistical analysis could be grouped together in a stats package. The package can be placed in an archive file, together with a manifest describing the package, and shipped to customers for use in their applications.

  • Packages create namespaces that help avoid naming conflicts between types. Interfaces and classes in a package can use popular public names (such as List and Constants) that make sense in one context but might conflict with the same name in another package.

  • Packages provide a protection domain for developing application frameworks. Code within a package can cooperate using access to identifiers that are unavailable to external code.

Let us look at a package for the attribute classes you saw in previous chapters. We will name the package attr. Each source file whose classes and interfaces belong in the attr package states its membership with its package declaration:

package attr;

This statement declares that all classes and interfaces defined in this source file are part of the attr package. A package declaration must appear first in your source file, before any class or interface declarations. Only one package declaration can appear in a source file. The package name is implicitly prefixed to each type name contained within the package.

If a type is not declared as being part of an explicit package, it is placed in an unnamed package. Each system must support at least one unnamed package, but they can support more—typically one per class loader. The existence of the unnamed package makes it simple for you to write small programs without being encumbered by the organizational requirements imposed on the members of named packages. Any non-trivial program or set of library classes should always be part of a named package.

Package Naming

A package name should prevent collisions with other packages, so choosing a name that's both meaningful and unique is an important aspect of package design. But with programmers around the globe developing packages, there is no way to find out who is using what package names. So choosing unique package names is a problem. If you are certain a package will be used only inside your organization, you can use an internal arbiter to ensure that no projects pick clashing names.

But in the world at large, this approach is not practical. Package identifiers are simple names. A good way to ensure unique package names is to use an Internet domain name. If you worked at a company named Magic, Inc., that owned the domain name magic.com, the attribute package declaration should be

package com.magic.attr;

Notice that the components of the domain name are reversed from the normal domain name convention.

If you use this convention, your package names should not conflict with those of anyone else, except possibly within your organization. If such conflicts arise (likely in a large organization), you can further qualify using a more specific domain. Many large companies have internal subdomains with names such as east and europe. You could further qualify the package name with such a subdomain name:

package com.magic.japan.attr;

Package names can become quite long under this scheme, but it is relatively safe. No one else using this technique will choose the same package name, and programmers not using the technique are unlikely to pick your names.

Type Imports

Type imports are a generalization of the static imports that you learned about in Chapter 2 on page 71. They allow you to refer to types by their simple names instead of having to write their fully qualified name.

When you write code outside a package that needs types declared within that package you have two options. One is to use the fully qualified name of the type. This option is reasonable if you use only a few items from a package, but given the long names that packages tend to acquire (or even the shorter ones in the standard libraries) it can be quite tedious to use fully qualified names.

The other way to use types from a package is to import part or all of the package. A programmer who wants to use the attr package could put the following line near the top of a source file (after any package declaration but before anything else):

import attr.*;

Now the types in that package can be referred to simply by name, such as Attributed. An import that uses a * is called an import on demand declaration. You can also perform a single type import:

import attr.Attributed;

The name used with an import statement must be the canonical name of a package (for import on demand) or type (for single type import). As discussed in “Naming Classes” on page 411, the canonical name and fully qualified name can differ only for nested types. Note that generic types are imported using their raw type name—that name can then be used in a parameterized type without further qualification.

Code in a package imports the rest of its own package implicitly, so everything defined in a package is available to all types in the same package. The package java.lang is implicitly imported in all code.

The import mechanism is passive in that it does not cause information about the named packages and types to be read in at compile time—unless a type is used. The import statement simply tells the compiler how it can determine the fully qualified name for a type that is used in your program if it can't find that type defined locally. For example, as described on page 181, if your class declares a reference of type Attributed the compiler will search for the type in the following order:

  1. The current type including inherited types.

  2. A nested type of the current type.

  3. Explicitly named imported types (single type import).

  4. Other types declared in the same package.

  5. Implicitly named imported types (import on demand).

If, after that, the type is still not found it is an error. It is also an error if any package named in an import statement cannot be located—even if there is no actual need to obtain a type from that package.

Resolving which type is being referred to requires some work by the compiler, and use of import on demand may involve more work than resolving a single type import. However, this is rarely significant in the compilation process. The choice between a single type import and import on demand is usually a matter of personal preference or local style rules. Naturally, to resolve conflicts or to ensure that a specific type is resolved correctly you may need to use a single type import.[1]

Type imports can also be used with nested class names. For example, in Chapter 5 we defined the BankAcount class and its nested Permissions class. If these classes were in the package bank and you imported bank.BankAccount, you would still need to use BankAccount.Permissions to name the Permissions class. However, you could instead import bank.BankAccount.Permissions and then use the simple name Permissions. You don't have to import BankAccount to import the class BankAccount.Permissions. Importing nested type names in this way should generally be avoided because it loses the important information that the type is actually a nested type.

Static nested types can be imported using either the type import mechanism or the static import mechanism. If you insist on importing nested types, then you should use the type import mechanism for consistency.

The package and import mechanisms give programmers control over potentially conflicting names. If a package used for another purpose, such as linguistics, has a class called Attributed for language attributes, programmers who want to use both types in the same source file have several options:

  • Refer to all types by their fully qualified names, such as attr.Attributed and lingua.Attributed.

  • Import only attr.Attributed or attr.*, use the simple name Attributed for attr.Attributed, and use the full name of lingua.Attributed.

  • Do the converse: import lingua.Attributed or lingua.*, use the simple name Attributed for lingua.Attributed, and use the full name of attr.Attributed.

  • Import all of both packages—attr.* and lingua.*—and use the fully qualified names attr.Attributed and lingua.Attributed in your code. (If a type with the same name exists in two packages imported on demand, you cannot use the simple name of either type.)

It is an error to import a specific type that exists as a top-level (that is non-nested) type within the current source file. For example, you can't declare your own Vector class and import java.util.Vector. Nor can you import the same type name from two different packages using two single type imports. As noted above, if two types of the same name are implicitly imported on demand, you cannot use the simple type name but must use the fully qualified name. Use of the simple name would be ambiguous and so result in a compile-time error.

Package Access

You have two options when declaring accessibility of top-level classes and interfaces within a package: package and public. A public class or interface is accessible to code outside that package. Types that are not public have package scope: They are available to all other code in the same package, but they are hidden outside the package and even from code in subpackages. Declare as public only those types needed by programmers using your package, hiding types that are package implementation details. This technique gives you flexibility when you want to change the implementation—programmers cannot rely on implementation types they cannot access, and that leaves you free to change them.

A class member that is not declared public, protected, or private can be used by any code within the package but is hidden outside the package. In other words, the default access for an identifier is “package” except for members of interfaces, which are implicitly public.

Fields or methods not declared private in a package are available to all other code in that package. Thus, classes within the same package are considered “friendly” or “trusted.” This allows you to define application frameworks that are a combination of predefined code and placeholder code that is designed to be overridden by subclasses of the framework classes. The predefined code can use package access to invoke code intended for use by the cooperating package classes while making that code inaccessible to any external users of the package. However, subpackages are not trusted in enclosing packages or vice versa. For example, package identifiers in package dit are not available to code in package dit.dat, or vice versa.

Every type has therefore three different contracts that it defines:

  • The public contract, defining the primary functionality of the type.

  • The protected contract, defining the functionality available to subtypes for specialization purposes.

  • The package contract, defining the functionality available within the package to effect cooperation between package types.

All of these contracts require careful consideration and design.

Accessibility and Overriding Methods

A method can be overridden in a subclass only if the method is accessible in the superclass. If the method is not accessible in the superclass then the method in the subclass does not override the method in the superclass even if it has the same signature. When a method is invoked at runtime the system has to consider the accessibility of the method when deciding which implementation of the method to run.

The following contrived example should make this clearer. Suppose we have a class AbstractBase declared in package P1:

package P1;

public abstract class AbstractBase {
    private   void pri() { print("AbstractBase.pri()"); }
              void pac() { print("AbstractBase.pac()"); }
    protected void pro() { print("AbstractBase.pro()"); }
    public    void pub() { print("AbstractBase.pub()"); }

    public final void show() {
        pri();
        pac();
        pro();
        pub();
    }
}

We have four methods, each with a different access modifier, each of which simply identifies itself. The method show invokes each of these methods on the current object. It will show which implementation of each method is invoked when applied to different subclass objects.

Now we define the class Concrete1, which extends AbstractBase but lives in the package P2:

package P2;

import P1.AbstractBase;

public class Concrete1 extends AbstractBase {
    public void pri() { print("Concrete1.pri()"); }
    public void pac() { print("Concrete1.pac()"); }
    public void pro() { print("Concrete1.pro()"); }
    public void pub() { print("Concrete1.pub()"); }
}

This class redeclares each of the methods from the superclass and changes their implementation to report that they are in class Concrete1. It also changes their access to public so that they are accessible to all other code. Executing the code

new Concrete1().show();

produces the following output:

AbstractBase.pri()
AbstractBase.pac()
Concrete1.pro()
Concrete1.pub()

Because the private method pri is inaccessible to subclasses (or to any other class) the implementation from AbstractBase is always the one invoked from show. The package accessible pac method of AbstractBase is not accessible in Concrete1, thus the implementation of pac in Concrete1 does not override that in AbstractBase, so it is AbstractBase.pac that show invokes. Both the pro and pub methods are accessible in Concrete1 and get overridden, so the implementation from Concrete1 is used in show.

Next we define the class Concrete2, which extends Concrete1 but is in the same package P1 as AbstractBase:[2]

package P1;

import P2.Concrete1;

public class Concrete2 extends Concrete1 {
    public void pri() { print("Concrete2.pri()"); }
    public void pac() { print("Concrete2.pac()"); }
    public void pro() { print("Concrete2.pro()"); }
    public void pub() { print("Concrete2.pub()"); }
}

Each method of Concrete2 overrides the version from Concrete1 because they are all public and therefore accessible. Also, because Concrete2 is in the same package as AbstractBase, the method AbstractBase.pac is accessible in Concrete2 and so is overridden by Concrete2.pac. Invoking show on a Concrete2 object prints

AbstractBase.pri()
Concrete2.pac()
Concrete2.pro()
Concrete2.pub()

Finally, we define the class Concrete3, which extends Concrete2 but is in a different package P3:

package P3;

import P1.Concrete2;

public class Concrete3 extends Concrete2 {
    public void pri() { print("Concrete3.pri()"); }
    public void pac() { print("Concrete3.pac()"); }
    public void pro() { print("Concrete3.pro()"); }
    public void pub() { print("Concrete3.pub()"); }
}

Invoking show on a Concrete3 object prints

AbstractBase.pri()
Concrete3.pac()
Concrete3.pro()
Concrete3.pub()

Here the method Concrete3.pac appears to have overridden the inaccessible AbstractBase.pac. In fact, Concrete3.pac overrides Concrete2.pac, and Concrete2.pac overrides AbstractBase.pac—therefore Concrete3.pac transitively overrides AbstractBase.pac. By redeclaring pac as public, Concrete2 made it accessible and overridable by any subclass.[3]

Package Contents

Packages should be designed carefully so that they contain only functionally related classes and interfaces. Classes in a package can freely access one another's non-private members. Protecting class members is intended to prevent misuse by classes that have access to internal details of other classes. Anything not declared private is available to all types in the package, so unrelated classes could end up working more intimately than expected with other classes.

Packages should also provide logical groupings for programmers who are looking for useful interfaces and classes. A package of unrelated classes makes the programmer work harder to figure out what is available. Logical grouping of classes helps programmers reuse your code because they can more easily find what they need. Including only related, coherent sets of types in a package also means that you can use obvious names for types, thereby avoiding name conflicts.

Packages can be nested inside other packages. For example, java.lang is a nested package in which lang is nested inside the larger java package. The java package contains only other packages. Nesting allows a hierarchical naming system for related packages.

For example, to create a set of packages for adaptive systems such as neural networks and genetic algorithms, you could create nested packages by naming the packages with dot-separated names:

package adaptive.neuralNet;

A source file with this declaration lives in the adaptive.neuralNet package, which is itself a subpackage of the adaptive package. The adaptive package might contain classes related to general adaptive algorithms, such as generic problem statement classes or benchmarking. Each package deeper in the hierarchy—such as adaptive.neuralNet or adaptive.genetic—would contain classes specific to the particular kind of adaptive algorithm.

Package nesting is an organizational tool for related packages, but it provides no special access between packages. Class code in adaptive.genetic cannot access package-accessible identifiers of the adaptive or adaptive.neuralNet packages. Package scope applies only to a particular package. Nesting can group related packages and help programmers find classes in a logical hierarchy, but it confers no other benefits.

Package Annotations

A package can have annotations applied to it. The problem is that there is no actual definition of a package to which you can apply such annotations—unlike a class or a method—because a package is an organizational structure, not a source code entity. So you annotate packages by applying the annotation to the package statement within a source file for that package. However, only one package declaration per package can have annotations applied to it.

So how do you actually annotate a package? There is no required way to deal with this “single annotated package statement” rule. The suggested approach is to create a file called package-info.java within the package directory, and to put in that file nothing but a package statement with any annotations for the package (plus any useful comments, of course). For example, a package-info.java file for the attr package might look like this:

@PackageSpec(name = "Attr Project", version = "1.0")
@DevelopmentSite("attr.project.org")
@DevelopmentModel("open-source")
package attr;

where PackageSpec, DevelopmentSite, and DevelopmentModel are made up annotation types—with a runtime retention policy, of course. The package-info.java file should be compiled along with the rest of the package source files.

The package-info.java file is recommended as the single place to put all package-related information. To that end, you can place a documentation comment at the start of the file, and that will be the package documentation for that package. Package documentation is covered in more detail in “Package and Overview Documentation” on page 496.

The java.lang.Package class implements AnnotatedElement, so you can ask about any applied annotations using the methods discussed in “Annotation Queries” on page 414.

Package Objects and Specifications

Packages typically implement a specification, and they are also typically from one organization. A Package object, unlike the other reflection types (see Chapter 16), is not used to create or manipulate packages, but acts only as a repository for information about the specification implemented by a package (its title, vendor, and version number) and for information about the implementation itself (its title, vendor, and version number). Although a package typically comes from a single organization, the specification for that package (such as a statistical analysis library) may have been defined by someone else. Programs using a package may need to be able to determine the version of the specification implemented by the package so that they use only functionality defined in that version. Similarly, programs may need to know which version of the implementation is provided, primarily to deal with bugs that may exist in different versions. The main methods of Package allow access to this information:

  • public String getName()

    • Returns the name of this package.

  • public String getSpecificationTitle()

    • Returns the title of the specification this package implements, or null if the title is unknown.

  • public String getSpecificationVersion()

    • Returns a string describing the version of the specification that this package implements, or null if the version is unknown.

  • public String getSpecificationVendor()

    • Returns the name of the vendor that owns and maintains the specification that this package implements, or null if the vendor is unknown.

  • public String getImplementationTitle()

    • Returns the title of the implementation provided by this package, or null if the title is unknown.

  • public String getImplementationVersion()

    • Returns a string describing the version of the implementation provided by this package, or null if the version is unknown.

  • public String getImplementationVendor()

    • Returns the name of the organization (vendor) that provided this implementation, or null if the organization is unknown.

For example, extracting this information for the java.lang package on our system yielded the following:[4]

Specification Title:    Java Platform API Specification
Specification Version:  1.4
Specification Vendor:   Sun Microsystems, Inc.
Implementation Title:   Java Runtime Environment
Implementation Version: 1.5.0_02
Implementation Vendor:  Sun Microsystems, Inc.

Specification version numbers are non-negative numbers separated by periods, as in "2.0" or "11.0.12". This pattern allows you to invoke the isCompatibleWith method to compare a version following this pattern with the version of the package. The method returns true if the package's specification version number is greater than or equal to the one passed in. Comparison is done one dot-separated number at a time. If any such value in the package's number is less than the one from the passed in version, the versions are not compatible. If one of the version numbers has more components than the other, the missing components in the shorter version number are considered to be zero. For example, if the package's specification version is "1.4" and you compare it to "1.2", "1.3.1", or "1.1.8", you will get true, but if you compare it to "1.4.2" or "1.5", you will get false. This comparison mechanism assumes backward compatibility between specification versions.

Implementation version numbers do not have a defined format because these will be defined by the different organizations providing the implementations. The only comparison you can perform between implementation versions is a test for equality—there is no assumption of backward compatibility.

Packages can be sealed, which means that no classes can be added to them. An unsealed package can have classes come from several different places in a class search path. A sealed package's contents must all come from one place—generally either a specific archive file or a location specified by a URL. Two methods ask if a package is sealed:

  • public boolean isSealed()

    • Returns true if the package is sealed.

  • public boolean isSealed(URL url)

    • Returns true if the package is sealed with respect to the given URL, that is, classes in the package can be loaded from the URL. Returns false if classes in the package cannot be loaded from the given URL or if the package is unsealed.

The specification and implementation information for a package is usually supplied as part of the manifest stored with the package—such as the manifest of a Java Archive (jar) file, as described in “Archive Files — java.util.jar” on page 735. This information is read when a class gets loaded from that package. A ClassLoader can dynamically define a Package object for the classes it loads:

  • protected Package definePackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase)

    • Returns a Package object with the given name, and with specification and implementation values set to the corresponding arguments. If sealBase is null the package is unsealed, otherwise it is sealed with respect to that URL. The Package object for a class must be defined before the class is defined and package names must be unique within a class loader. If the package name duplicates an existing name, an IllegalArgumentException is thrown.

You can get the Package object for a given class from the getPackage method of the Class object for that class. You can also get a Package object by invoking either the static method Package.getPackage with the name of the package, or the static method Package.getPackages, which returns an array of all known packages. Both methods work with respect to the class loader of the code making the call by invoking the getPackage or getPackages method of that class loader. These class loader methods search the specific class loader and all of its parents. If there is no current class loader then the system class loader is used. Note that the class loader methods will return null if the package is unknown because no type from the package has been loaded.

 

When a shepherd goes to kill a wolf, and takes his dog along to see the sport, he should take care to avoid mistakes. The dog has certain relationships to the wolf the shepherd may have forgotten.

 
 --Robert Prisig, Zen and the Art of Motorcycle Maintenance


[1] For example, if you use import on demand for a given package and there is a type in that package with the same name as a type in the current package, then the current package type would be found ahead of the imported package. In this case you could use a single type import for that one type to make it clear that it is the type from the other package that is required. Alternatively, of course, you could just use the fully qualified name.

[2] Having an inheritance hierarchy that weaves in and out of a package is generally a very bad idea. It is used here purely for illustration.

[3] This illustrates why weaving in and out of a package can be confusing and should be avoided.

[4] Yes the 1.4 specification version is a bug

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.139.8