Module 12, Part 2: Managing Java Projects: Packages, JAR Files, Build Tools#
In this second part of Module 12 we explore some features and tools of the Java programming language and ecosystem that are particularly useful when working on larger projects, where we may need to organise many classes and/or reuse classes from other projects. These features and tools are:
packages for grouping files and classes, and the related “import” directive and “protected” modifier;
creating and using JAR files to easily distribute and reuse Java classes and programs;
a brief look at Java build tools that automate many tasks during the development of Java projects.
Important
These new topics are not part of the final exam of this course. They are addressed because they clarify some aspects that may pop up during daily usage of Java, even for small programs. Moreover, these topics usually become very important as soon as one tries to work with existing Java programs created by experienced developers.
The weekly assessments for this module are besed on a past exam paper, and are based on topics we have addressed in previous modules: you should work on them during the lab and submit them on DTU Autolab as usual, since they are provided to train your programming skills!
Java Packages#
Until now we have written Java programs that consist of a small number of
.java
files in a single directory. Keeping a few files in a directory works
well for programs of limited size (which are the focus of this course) — but
the approach has two drawbacks:
if a program grows functionality and in size, the number of its
.java
files may grow as well, and they may become chaotic and difficult to manage;it may be hard to reuse classes written for one program in other programs. For example, we may have a program (maybe written by someone else) that defines a class called
Utils
with various handy static methods, and we may want to reuse that class in another program — but if that other program also happens to define a different class calledUtils
, then we would need to rename one of the two classes and update the code accordingly.
To address this issue, Java allows us to organise class definitions by grouping them in packages. We have already encountered packages: e.g. when discussing how to create and use a Scanner object we have briefly seen that when we write:
var s = new java.util.Scanner(System.in);
we are creating a new object of the class Scanner
contained in the Java
package java.util
, which is part of the Java API. Technically,
java.util.Scanner
is the fully-qualified name of the class (i.e. the
package name followed by the class name).
To organise our code in packages, we have to consider three main aspects:
We must declare at the beginning of each
.java
file the name of the package that “owns” the file, by writing e.g.:package my.cool.code
Package names are hierarchical, and the different levels of the hierarchy are separated by dots: e.g. if a file called
Utils.java
begins with the line “package my.cool.code
” above, then its code is organised with:a main package called
my
, which containsa sub-package called
cool
, which containsa sub-package called
code
, which containsany class defined in the file
Utils.java
.
We must save the files in a hierarchy of directories that matches the package hierarchy. For example, if a file called
Utils.java
begins with the linepackage my.cool.code
, then the file must be saved inside nested subdirectories calledmy
,cool
, andcode
:my (directory) └── cool (directory) └── code (directory) └── Utils.java (Java file)
Finally, we need to consider how the classes defined in our
.java
file can be accessed:A class definition like
class Utils { ... }
(i.e. a class definition that is not explicitly declaredpublic
) means that the classUtils
is package-private, i.e. it can only be accessed by code contained in the same packagemy.cool.code
. Similarly, fields and methods defined withoutpublic
norprivate
modifiers are package-private, hence they can be only accessed by code contained in the same package.Instead, a class definition like
public class Utils { ... }
can be accessed from everywhere (including code belonging to other packages). Similarly,public
fields and methods can be accessed from any package.Important
A
.java
file can define at most onepublic
class, and the name of thepublic
class must coincide with the name of the file. For instance, apublic class Utils { ... }
must be defined in a file calledUtils.java
.
The reason why Java enforces the restriction 2. and 3. above is that they can
help working with large code bases: if a programmer is looking for the
definition of a class a.b.c.ClassName
, then the programmer can find the class
definition inside a directory hierarchy that matches the package a.b.c
— and
moreover, if the class ClassName
is public, then the programmer knows that its
code can be found in a file called ClassName.java
.
This is illustrated in Example 73 below.
(A simple hierarchy of packages)
Suppose we need to write a program that manages the DTU Pet Clinic, using pet classes similar to Example 63, with the following requirements:
the class
Pet
and the derived classesDog
andCat
must bepublic
and must belong to a package calleddk.dtu.pets
;the
main
method of the program must belong to apublic
class calledMain
in the packagedk.dtu.petclinic
.
To obtain this, we must:
save the
public
classes in separate.java
files with a name that matches the class name;write the desired package name at the beginning of each file
.java
file, andplace each
.java
file under a subdirectory that matches its package name.
As a result, the files and directories are organised as follows:
dk (directory)
└── dtu (directory)
├── pets (directory)
│ ├── Pet.java (Java source code file)
│ ├── Dog.java (Java source code file)
│ └── Cat.java (Java source code file)
│
└── petclinic (directory)
└── Main.java (Java source code file)
You can download these source files here: dtu-pet-clinic.zip
To explore the source code, you can download and unzip the file above, and open
the resulting folder dtu-pet-clinic
with Visual Studio Code (e.g. by launching
Visual Studio Code and clicking on File -> Open Folder…).
Note
In the ZIP file above, the file Main.java
in the package dk.dtu.petclinic
needs to use the classes Pet
, Cat
, and Dog
, which belongs to a different
package dk.dtu.pets
. To do that, the code in Main.java
refers to the
fully-qualified class names, e.g. dk.dtu.pets.Pet
. (Just like we have been
using e.g. the fully-qualified name java.util.Scanner
to use the class
Scanner
contained in the package java.util
.) The file Main.java
is shown
below.
1package dk.dtu.petclinic;
2
3public class Main {
4 public static void main(String[] args) {
5 System.out.println("Welcome to the DTU Pet Clinic!");
6
7 var pets = new dk.dtu.pets.Pet[] { new dk.dtu.pets.Dog("Dr Wolf"),
8 new dk.dtu.pets.Dog("Mr Coyote"),
9 new dk.dtu.pets.Cat("Prof Los"),
10 new dk.dtu.pets.Cat("Dr Puma") };
11
12 System.out.println("Current list of pets:");
13 for (var p: pets) {
14 System.out.println(" - " + p.getDescription());
15 }
16 }
17}
To compile and run the program in the ZIP file above, you can open a terminal in the unzipped folder, and execute the following commands.
Compile all Java files in all the various subdirectories, to generate a
.class
file for each.java
file (as previously explained):javac dk/dtu/pets/*.java
javac dk/dtu/petclinic/*.java
After the compilation, the files and directory structure will look as follows (the new
.class
files are highlighted)dk (directory) └── dtu (directory) ├── pets (directory) │ ├── Pet.java (Java source code file) │ ├── Pet.class (Java class file) │ ├── Dog.java (Java source code file) │ ├── Dog.class (Java class file) │ ├── Cat.java (Java source code file) │ └── Cat.class (Java class file) │ └── petclinic (directory) ├── Main.java (Java source code file) └── Main.class (Java class file)
Run the program, by asking Java to execute the static method
main
of the classMain
in the packagedk.dtu.petclinic
:java dk.dtu.petclinic.Main
Importing Classes from Other Packages#
When a class from another package is used often, it can be convenient to
import
the class instead of repeating its fully-qualified name over and
over again. This is illustrated in Example 74 below.
(Importing the Pet class)
In Example 73 we have seen that the file Main.java
in the
package dk.dtu.petclinic
uses e.g. the fully-qualified class name
dk.dtu.pets.Dog
to refer to a class in another package. The file Main.java
can instead import
the class dk.dtu.pets.Dog
: after being imported, the
class can be simply referred as Dog
. The same can be done for the classes
Pet
and Cat
.
The modified file Main.java
using import
is shown below. (The differences
with the file Main.java
in Example 73 are highlighted)
1package dk.dtu.petclinic;
2
3import dk.dtu.pets.Pet;
4import dk.dtu.pets.Dog;
5import dk.dtu.pets.Cat;
6
7public class Main {
8 public static void main(String[] args) {
9 System.out.println("Welcome to the DTU Pet Clinic!");
10
11 var pets = new Pet[] { new Dog("Dr Wolf"),
12 new Dog("Mr Coyote"),
13 new Cat("Prof Los"),
14 new Cat("Dr Puma") };
15
16 System.out.println("Current list of pets:");
17 for (var p: pets) {
18 System.out.println(" - " + p.getDescription());
19 }
20 }
21}
Protected Fields (and Methods)#
Until now we have seen that, when defining a class, we can decide the visibility
of a field or method by using the optional modifiers public
or private
:
a field/method defined with the
private
modifier is only accessible by code written in the same class;a field/method defined without modifiers is package-private, hence it is only accessible by code within the same package;
a field/method defined with the
public
modifier is accessible by code written in any package.
However, there may be cases where none of these three options fits our needs:
we may want to protect a field/method from arbitrary “external” access (so we cannot make it
public
), andwe may want to access the field/method from the subclasses (so we cannot make it
private
), andsuch subclasses might be defined in other packages (so we cannot make the field/method package-private).
In these cases, we can make the field/method protected
, i.e. only accessible
by:
code belonging to the same package, and
the owner class and its subclasses.
Note
The protected
modifier is typically relevant for larger Java applications, and
it is not crucial for this course. You can experiment with the protected
modifier by trying Exercise 35 below.
Distributing Java Classes and Programs as JAR Files#
When we compile Java source code, the Java compiler javac
generates .class
files that can be used without the original source code, on any computer
architecture and operating system supported by Java: for instance, we can
compile a .java
file into a .class
file on Linux, and that .class
file can
be used e.g. on MacOS (with an Intel or AMD or Apple Silicon CPU) or Windows
(with an Intel or AMD or ARM CPU). This is possible because .class
files are
loaded and interpreted by the java
program, which in turn implements the
Java Virtual Machine (JVM) and is available on
many operating systems and CPU architectures.
In principle we could distribute our Java projects by distributing such .class
files directly — but in practice, this can be cumbersome: each Java class is
compiled into a separate .class
file, so large Java projects may have many
.class
files.
For this reason, the Java specification defines a standard way to package and distribute classes and programs: the JAR (Java ARchive) file format — which is just a ZIP file that contains:
a “Manifest file” with information about the contents of the archive;
.class
files organised in a directory structure that matches their packages (as previously explained);other relevant files, if necessary.
This is illustrated in Example 75, Example 76, and Example 77 below.
(Creating a JAR file)
Take the ZIP file provided in Example 73
(dtu-pet-clinic.zip):
we now see how to generate a JAR file containing the compiled .class
files
(while in Example 76 below we will use that JAR file to develop
a new program).
First, it is convenient to compile all .java
files into .class
files stored
in a separate directory, called e.g. build
. To do this, you can open a
terminal in the folder dtu-pet-clinic
extracted from the ZIP file above, and
run the javac
compiler with the option -d build
:
javac -d build dk/dtu/pets/*.java
javac -d build dk/dtu/petclinic/*.java
After these commands, the contents of the directory dtu-pet-clinic
should look
as follows: (notice that the compiled .class
files are now inside the
directory build
, and they are arranged in subdirectories that match the
directory structure of the source files)
dtu-pet-clinic (directory extracted from the ZIP file)
├── dk (directory containing the Java source code)
│ └── dtu (directory)
│ ├── pets (directory)
│ │ ├── Pet.java (Java source code file)
│ │ ├── Dog.java (Java source code file)
│ │ └── Cat.java (Java source code file)
│ │
│ └── petclinic (directory)
│ └── Main.java (Java source code file)
│
└── build
└── dk (directory contaning compiled .class files)
└── dtu (directory)
├── pets (directory)
│ ├── Pet.class (Java class file)
│ ├── Dog.class (Java class file)
│ └── Cat.class (Java class file)
│
└── petclinic (directory)
└── Main.class (Java class file)
We can now create a JAR file called e.g. PetClinic.jar
based on the contents
of the directory build
, with the following command (which also specifies which
class contains the main
method):
jar --create --file PetClinic.jar --main-class dk.dtu.petclinic.Main -C build .
After executing this command, you should see a new file called PetClinic.jar
containing all the .class
files above: we discuss how to use it in
Example 76 below.
(Using a JAR file)
This is a follow-up to Example 75 above, and the starting
point is the file PetClinic.jar
created there.
The file PetClinic.jar
can be easily distributed to reuse the Java classes it
contains. For instance, we can run the DTU Pet Clinic program contained in
PetClinic.jar
by executing in a console (from within the same directory
containing the file):
java -jar PetClinic.jar
We can also develop new Java code that uses the classes saved in the JAR file
PetClinic.jar
. For example, consider the code below, that defines a class
Kangaroo
extending dk.dtu.pets.Pet
, with a simple main
method for testing:
class Kangaroo extends dk.dtu.pets.Pet {
public Kangaroo(String name) {
super(name, "kangaroo");
}
public static void main(String args[]) {
var k = new Kangaroo("Clara");
System.out.println("Pet description: " + k.getDescription());
}
}
If you save the code above in a file called e.g. Kangaroo.java
, and you try to
compile and run it by executing java Kangaroo.java
, you will get an error like
“package dk.dtu.pets does not exist”: this is because Java does not know where
to find the package dk.dtu.pets
and the classes it contains.
However, we can tell Java to look for such package and classes using the option
-classpath
(or -cp
for short) with the file PetClinic.jar
created above.
If PetClinic.jar
and Kangaroo.java
are in the same directory, we can execute
(from within that directory):
java -classpath PetClinic.jar Kangaroo.java
And now the program will compile and run, using the class dk.dtu.pets.Pet
provided by the JAR file PetClinic.jar
. The output will be:
Pet description: Clara (kangaroo)
(Using JAR files provided by other Java projects)
This is a follow-up to Example 75 (starting from the file
PetClinic.jar
) and Example 76: we now discuss how we can
improve the small program in this last example by using a collection of Java
classes (i.e. a library) that is not part of the standard Java API.
Suppose we want to add some coloured output to the file Kangaroo.java
developed in Example 76. The Java API does not provide any
class nor method to produce coloured output on the console — but there is a
popular Java library called Jansi that
provides such facilities.
To use the Jansi library, we first need to download its JAR file: you can
find the JAR file for Jansi version 2.4.0 on its
download page.
Please save the file jansi-2.4.0.jar
in the directory that also contains the
files PetClinic.jar
and Kangaroo.java
.
The, we can modify the file Kangaroo.java
(from Example 76) as
follows, using the Jansi library according to its
API documentation.
import org.fusesource.jansi.AnsiConsole;
import org.fusesource.jansi.Ansi;
class Kangaroo extends dk.dtu.pets.Pet {
public Kangaroo(String name) {
super(name, "kangaroo");
}
public static void main(String args[]) {
var k = new Kangaroo("Clara");
AnsiConsole.systemInstall();
var message = Ansi.ansi().fgGreen().a("Pet description: ")
.bgRed().fgBrightYellow().bold().a(k.getDescription())
.fgDefault().bgDefault();
System.out.println(message);
AnsiConsole.systemUninstall();
}
}
We can now compile and run the revised file Kangaroo.java
using both JAR
files PetClinic.java
and jansi-2.4.0.jar
: (NOTE: this is done slightly
differently on Windows, compared to macOS and Linux)
java -classpath "PetClinic.jar;jansi-2.4.0.jar" Kangaroo.java
java -classpath PetClinic.jar:jansi-2.4.0.jar Kangaroo.java
and the program should produce coloured output on the console.
Note
The Java API is made available via a collection of JAR files containing many packages and classes (including the ones we have used thus far, like e.g.
java.util.Scanner
). Those JAR files are part of Java installation and they are automatically used when we compile or run Java programs, without need to load them with the-classpath
command-line option (as we had to do for loadingPetClinic.jar
andjansi-2.4.0
in Example 76 and Example 77).The Java library includes a package called
java.lang
whose classes are automaticallyimport
ed. This is why we can write e.g.System.out.println("...")
without importing the classSystem
, or we can create andthrow
an object of the classArithmeticException
without importing the classArithmeticException
itself: both classesSystem
andArithmeticException
are part of the packagejava.lang
, hence they are automatically imported in our Java programs. Correspondingly, writing e.g.System.out.println("...")
is equivalent to writing the more verbosejava.lang.System.out.println("...")
.
Java Build Tools#
The steps described in the previous sections and examples (compiling multiple
Java files with the correct options, creating JAR files, downloading JAR files
when needed, remembering to specify -classpath
…) become very repetitive and
time-consuming when developing Java programs. For this reason, the Java
ecosystem includes various build tools that automate these steps, and let
programmers focus more on designing and writing their Java code.
These build tools can be installed and used in a terminal — and editors like Visual Studio Code provide additional facilities to use build tools through a more intuitive point-and-click interface.
These tools are outside the scope of this course — but if you are curious, here are two introductions to two very popular build tools for Java projects:
Gradle: https://www.baeldung.com/gradle
Concluding Remarks#
You should now have an understanding of the following topics:
Java packages and the related “import” directive and “protected” modifier.
Creating and using JAR files.
The role of Java build tools in automating many tasks during the development of Java programs.
Important
As mentioned in the opening, these topics are not part of the final exam of this course. They are presented here because they usually become very important as soon as one tries to work on other people’s code, or steps beyond the scope of introductory programs.
Most of the tasks described in this module can be automated using build tools (like Maven) and editors (like Visual Studio Code) and other advanced Java development environments (like Eclipse or IntelliJ Idea). Still, it it is important to have an intuition of what is happening “behind the scenes” to understand how to address possible issues. For instance, an error reporting that a package used in your code “does not exist” might mean that you need to download the relevant JAR file(s) — or you may need to configure Maven or Eclipse or IntelliJ Idea to do that for you.
References and Further Readings#
You are invited to read the following sections of the reference book:
Section 3.3 - “Packages”
Section 9.1 - “Crating Subclasses” - “The
protected
Modifier”
More details about Java packages are available in this tutorial:
Here are more details on how to compile multiple Java files in various directories:
Finally, this tutorial discusses how to create JAR files.
Exercises#
Note
These exercises are not part of the course assessment. They are provided to help you self-test your understanding.
(Experimenting with Java packages)
To experiment with the package structure in Java, you may take one of the
previous assessments and rearrange their classes into packages with the
corresponding directory structure, as we did for Pet
, Dog
, and Cat
in
Example 73.
For example, you could take the solution to 04 - Video Game Monsters, Part 5 (either your own solution, or the one provided by the teacher) and adapt it by:
making all monster classes
public
(so they must be placed in their own file), andgrouping all monster classes in a package called
dk.dtu.game.monsters
, andplacing the class
GameUtils
in a package calleddk.dtu.game.utils
.
After you reorganise the classes in this way, you can try revising some of the
tests in the handout of 04 - Video Game Monsters, Part 5, changing
their code to make it compile and run using the new package names. For
instance, you may need to import
the class dk.dtu.game.monsters.Monster
…
(Experimenting with protected fields)
To see the “protected” modifier in action, you can try the following experiment:
take the ZIP file provided in Example 73 (dtu-pet-clinic.zip);
modify the field
name
of the classdk.dtu.pets.Pet
to beprotected
instead ofprivate
;define e.g. a class
Porcupine
that extendsPet
, but belongs to the packagedk.dtu.petclinic
(so,Porcupine
andPet
belong to different packages);modify the code of
Porcupine
andMain
to observe that:in the code written inside the class
dk.dtu.petclinic.Porcupine
, you can directly read and modify the value of the fieldthis.name
. However,inside the static method
dk.dtu.petclinic.Main.main
, ifp
is an object of the classPet
(or one of its subclasses), then you cannot access the fieldp.name
.
Lab and Weekly Assessments#
During the lab you can try the exercises above or begin your work on the weekly assessments.
Important
For this Module, you can find the weekly assessments in a sample exam paper:
open the course page on DTU Learn (https://learn.inside.dtu.dk/d2l/home/215298);
navigate to Contents → Sample Exam Papers;
select the Sample Exam 01 PDF file.