Section 12.5 Using Code Across Packages: Imports
In the previous section, we saw how
packages organize our code into distinct namespaces, preventing naming conflicts and grouping related classes like
ADTs.ListADT and
DataStructures.ArrayList. This organization is essential.
However, it raises a practical question. Our
ArrayList class, which will reside in the
DataStructures package, needs to
implement the
ListADT interface, which lives in the separate
ADTs package. How do we tell the Java compiler, inside
ArrayList.java, that when we write "
ListADT", we specifically mean the one defined in the
ADTs package?
One way is to always use the
fully qualified name everywhere the type is needed:
package DataStructures;
// NO import statement used here yet
// Using fully qualified names everywhere...
public class ArrayList<T> implements ADTs.ListADT<T> { // Verbose!
// ... other fields ...
// Method parameter using fully qualified name
public boolean contains(ADTs.ListADT<? extends T> otherList) { // Very verbose!
// ... implementation ...
return false;
}
// Return type using fully qualified name
public ADTs.ListADT<T> getSublist(int fromIndex, int toIndex) { // Also verbose!
// ... implementation ...
return null;
}
// Method signatures inherited from ADTs.ListADT must also be implemented...
@Override
public T get(int index) { /* ... */ return null;} // Example method from interface
// ... many other methods ...
}
While this works (the compiler knows exactly which
ListADT you mean), it makes the code incredibly verbose, harder to read, and more tedious to write. Imagine typing
ADTs.ListADT dozens of times throughout the file! This clearly violates our goal of writing clean, maintainable code.
The solution is the
import statement. The
import keyword allows you to tell the compiler which classes or interfaces you intend to use from other packages, enabling you to refer to them using only their
simple name (e.g.,
ListADT) within your current file.
Import statements appear at the top of your
.java file,
after the
package declaration (if one exists) but
before the class or interface definition. The most common form is the
single-type import:
import package.path.ClassName; // Imports a specific class or interface
Let’s rewrite our
ArrayList skeleton using an import for
ListADT:
package DataStructures;
import ADTs.ListADT; // Import the specific interface we need
// Now we can use the simple name "ListADT"
public class ArrayList<T> implements ListADT<T> { // Much cleaner!
// ... other fields ...
// Method parameter using simple name
public boolean contains(ListADT<? extends T> otherList) { // Readable!
// ... implementation ...
return false;
}
// Return type using simple name
public ListADT<T> getSublist(int fromIndex, int toIndex) { // Readable!
// ... implementation ...
return null;
}
@Override
public T get(int index) { /* ... */ return null;} // Still just needs simple name T
// ... many other methods ...
}
By adding
import ADTs.ListADT;, our code becomes significantly more readable and concise, as we can now use the simple name
ListADT throughout the file.
Subsection 12.5.1 Types of Imports
-
Single-Type Import: import package.path.ClassName; Imports one specific public class or interface. This is the
recommended approach because it makes the dependencies of your class explicitly clear just by looking at the import statements. Anyone reading the code knows exactly which types are being used from other packages.
-
On-Demand Import: import package.path.*; Imports
all public types from the specified package. While sometimes convenient if you use many types from a single package (like
java.util.*), it’s generally discouraged in modern practice. Why? It can make it less clear exactly which types are being used, and if two different imported packages happen to contain types with the same simple name (e.g., both
packageA.* and
packageB.* contain a
Helper class), it creates ambiguity that requires you to use fully qualified names anyway, defeating the purpose.
In our project, the
ArrayList.java file (in package
DataStructures) will definitely need
import ADTs.ListADT;. You will also likely need imports for standard Java exceptions like
java.lang.IndexOutOfBoundsException (although types in
java.lang are implicitly imported, explicit imports for exceptions can sometimes aid clarity) and others not in
java.lang, such as
java.util.NoSuchElementException. Understanding imports is therefore essential before we start implementing our class.
You have attempted
of
activities on this page.