I have only a beginner level knowledge in Java and haven’t done any serious coding with Java up until recently. I have been using C/C++ extensively in my university projects and other open-source projects (especially in symengine) I contribute to. So far, my experience with Java has been something completely different from what I am normally used to.
Java is very much IDE oriented. Even the most simple Java project uses an IDE. IDEs make coding and managing the project very easy. However, this is in clear contrast to how C/C++ projects are developed. For example, in my work on symengine, I only use a text editor like gedit (with some plugins) and the command-line.
So I was a bit curious to find out what is going on under an IDE when compiling Java code into bytecode and wanted to do that by myself with a command-line. Let’s play with a couple of Java files and try to compile them into bytecode using Java command-line tools.
Compiling a single file is very straightforward. Suppose we have a Java file A.java
with the following content.
package org.application;
public class A {
public static void main(String[] args) {
System.out.println("Inside A.main()");
}
}
Java assumes that directory structure adheres to the package definition. Inside A.java
file, we have defined it in the package org.application
. So, I have created a directory structure that matches the package definition and put A.java
file in the appropriate place. Below is my directory tree (generated using tree command). Here “.
” denotes the current directory (Let’s call it root hereafter).
.
└── org
└── application
└── A.java
Now you can compile A.java
to bytecode using javac
command. There are two ways to do this. You can use the following single-liner,
or change directory to org/application
from the root and then invoke javac
.
After executing the above command(s), you will be able to see a new file called A.class
inside org/application/
. Here is the new directory structure.
.
└── org
└── application
├── A.class
└── A.java
Now let’s try to run the compiled class file. If you are still at the root (i.e., if you compiled with the single-liner) then you can do:
If you are inside the application
directory, then you might hope (if you are a java newbie like me) that something like java A
which is cooked using the recipe of the command for running the programme at root will do the trick for you. Well, it won’t. To understand why, we need to learn a bit about the Java Classpath.
The Classpath (shorter name for class search path) is the path that the Java runtime environment (jre) searches for third party and user-defined classes. The default Classpath is the current directory. Java command-line tools like java, javac, javadoc
allow programmers to set Classpath using -classpath
option. Also, one can set system Classpath by setting the environment variable CLASSPATH
in both Unix and Windows environments. If you have resources that need to be found by java tools very often, then it’s a good idea to put the path of those resources in the CLASSPATH
variable.
Now, you might be able to figure out why we couldn’t run the compiled class inside the org/application
directory. When we try to run it from there, Java runtime searches for org/application/
directory structure (remember: directory structure should match package definition) which is not found under org/application
. When we run the programme from the root, Java runtime finds the expected directory structure so the programme gets executed with no exceptions.
So, what should we do if we want to run the programme inside org/application
? We need to set the Classpath manually overriding the default value. Since the root directory is two levels above the current directory, we can use something like:
Let’s move another step forward and try to compile a project with multiple files. Let’s create a new file called B.java
which creates an object of type A
inside its main method. That way, when we compile B
, we will have to tell the Java compiler about the A
class. Contents of B.java
should look something similar to the following.
package org.application;
public class B {
public static void main(String[] args) {
A a = new A();
System.out.println("Inside B.main()");
}
}
And below is the new directory structure.
.
└── org
└── application
├── A.java
└── B.java
If you are at the root, then compiling B.java
file is no different from compiling A.java
file. You can simply execute:
Java runtime won’t have any problem in figuring out where the A.java
is. While compiling B.java
, it looks for A.class
in the package org.application
(which is the only known package to the compiler) which maps to the directory structure org/application/A.class
. This is found below the root (or compiled from the respective .java
file).
But what if you want to compile inside org/application
? Now you can’t do javac B.java
as before. Java compiler will look for org/application/A.class
in the Classpath (which is the current directory) and well, there isn’t such a file. But you know what to do, right?
Note that -cp
is a shorthand for -classpath
.
Running the programme is the same as what we did with a single file.
Exercise: Try compiling two source files in different packages. For example, put class A
in org.applicationA
and B
in org.applicationB
.
After compiling B.java
, your directory structure might look something similar to the following.
.
└── org
└── application
├── A.class
├── A.java
├── B.class
└── B.java
So we have our source files and output .class
files from javac
under the same root side by side. This is not desirable as it might clutter our source directory (imagine a project with hundreds, thousands of Java files). So, a good practice is to generate these files in a separate directory, say build
under root. You can ask javac
to do this using -d
option. At root execute:
Now the directory structure would look like something below.
.
├── build
│ └── org
│ └── application
│ ├── A.class
│ └── B.class
└── org
└── application
├── A.java
└── B.java
Note that, even though we asked javac
to compile B.java
, it has compiled A.java
as well because Class A
is used by Class B
. What if we had already compiled A.java
and have its A.class
file? Recompiling it will be a waste. So let’s try to compile B.java
using existing A.class
file.
To do this, first remove the existing B.class
file.
Now let’s compile B.java
file by setting the classpath to build
directory so that javac
can find the existing A.class
file.
You can read the articles listed under references for more information.