Manually compiling java files without ide

Posted by Fog Juice on Wed, 02 Feb 2022 07:45:20 +0100

https://imshuai.com/using-javac
Tools such as IDE or maven have been responsible for compiling Java programs. However, the more advanced the tool is, the more details will be hidden. Once there is a problem, it will be confused. In the final analysis, the basic concept is not reliable. Returning to the original place, javac, will make the problem suddenly clear. Here is a step-by-step demonstration of running a conventional project with javac and Java freehand compilation.

Hello World practice

Let's make a simple start. Let's sacrifice the ancestral HelloWorld program. (if you are interested, you can try whether you can write it with your bare hands ~)

public class HelloWorld{
	public static void main(String[] args){
		System.out.println("Hello, World!");
	}
}

After writing, save as: HelloWorld Java, and then execute the javac compilation command in the current directory:

javac HelloWorld.java

Check the current directory (more accurately, the same level directory of java files), and it sure enough generates HelloWorld class:

maoshuai@ms:~/javaLinux/w1$ ls
HelloWorld.class  HelloWorld.java

Continue to run the java command in the current directory and print out Hello, World correctly!

maoshuai@ms:~/javaLinux/w1$ java HelloWorld 
Hello, World!

Old driver, steady! It looks simple: javac first, then java.

Simple as it is, novices often make the mistake of imagining to execute class files, such as written like this, will naturally report errors:

maoshuai@ms:~/javaLinux/w1$ java HelloWorld.class
Error: Could not find or load main class HelloWorld.class

It should be understood that java parameters are passed in the name of the class where the main function is located, not the class file; java will automatically find the class file according to the class name.

With a package name

Everything is going well, but it's unprofessional to have no package name, so we add an awesome package. Com imshuai. javalinux:

package com.imshuai.javalinux;
public class HelloWorld{
	public static void main(String[] args){
		System.out.println("Hello, World!");
	}
}

Still the same, compile with javac and check

HelloWorld. In the current directory Class generated, very smooth.

It's the same as the java command, and you get slapped in the face in an instant:

maoshuai@ms:~/javaLinux/w1$ java HelloWorld 
Error: Could not find or load main class HelloWorld

After thinking about it, HelloWorld already has its own package name, so its name is no longer HelloWorld without a last name. Its new name is com imshuai. javalinux. HelloWorld, it's natural to use a new name when passing it to java. Try again:

maoshuai@ms:~/javaLinux/w1$ java com.imshuai.javalinux.HelloWorld
Error: Could not find or load main class com.imshuai.javalinux.HelloWorld

I was beaten in the face. At this time, the old driver told you to create a com/imshuai/javalinux directory, and then put HelloWorld Put class in and execute:

maoshuai@ms:~/javaLinux/w1$ mkdir -p com/imshuai/javalinux
maoshuai@ms:~/javaLinux/w1$ mv HelloWorld.class com/imshuai/javalinux
maoshuai@ms:~/javaLinux/w1$ java com.imshuai.javalinux.HelloWorld
Hello, World!

Sure enough, Hello, World was printed normally!

The above steps illustrate two points:

  1. The package name has been added, so the class name has also changed. If you don't change your name, you should naturally take your last name (that is, the so-called fully qualified name).
  2. Java will map out the directory structure according to the package name, and search the directory from the class path to find the class file. Since the default class path is the current directory, com imshuai. javalinux. HelloWorld must be stored in/ com/imshuai/javalinux/HelloWorld.class

Of course, it's too troublesome to create the directory of package path every time- d parameter can do the above work:

maoshuai@ms:~/javaLinux/w1$ javac -d . HelloWorld.java 
maoshuai@ms:~/javaLinux/w1$ ls
com  HelloWorld.java
maoshuai@ms:~/javaLinux/w1$ java com.imshuai.javalinux.HelloWorld
Hello, World!

-d specifies the root directory of the generated class file (the current directory is used here), and the subdirectory will be created according to the package path of class.

Compile two dependent class es

The package name is solved. Let's make it more complicated and make a dependency call. First, we extract a HelloService:

package com.imshuai.javalinux;
public class HelloService{
	public void printHello(String name){
		System.out.println("Hello, " + name + "!");
	}
}

Then modify HelloWorld Java, call HelloService to complete say hello:

package com.imshuai.javalinux;
public class HelloWorld{
	public static void main(String[] args){
		HelloService service = new HelloService();
		service.printHello("World");
	}
}

Then we compile in turn: helloservice Java and HelloWorld Java, finally run:

maoshuai@ms:~/javaLinux/w1$ javac -d . HelloService.java 
maoshuai@ms:~/javaLinux/w1$ javac -d . HelloWorld.java 
maoshuai@ms:~/javaLinux/w1$ ls
com  HelloService.java  HelloWorld.java
maoshuai@ms:~/javaLinux/w1$ java com.imshuai.javalinux.HelloWorld
Hello, World!

Intuitively, you need to compile helloservice Java, that's right. If you compile HelloWorld first What about Java? Face slapping, of course:

maoshuai@ms:~/javaLinux/w1$ javac -d . HelloWorld.java 
HelloWorld.java:4: error: cannot find symbol
		HelloService service = new HelloService();
		^
  symbol:   class HelloService
  location: class HelloWorld
HelloWorld.java:4: error: cannot find symbol
		HelloService service = new HelloService();
		                           ^
  symbol:   class HelloService
  location: class HelloWorld
2 errors

If the order is determined according to the dependency during compilation, it's too low. I think the Java command should automatically solve it. Pass two java files to it at one time:

maoshuai@ms:~/javaLinux/w1$ javac -d . HelloWorld.java HelloService.java 
maoshuai@ms:~/javaLinux/w1$ ls
com  HelloService.java  HelloWorld.java
maoshuai@ms:~/javaLinux/w1$ java com.imshuai.javalinux.HelloWorld
Hello, World!

Cow force, it automatically solves the order problem, like one (although I put HelloWorld.java in the front with bad intentions)!

Using src and target directories

As can be seen from the above example, although the class file must be placed in a directory with the same package name, the java source file does not have this requirement. However, for the convenience of management, we also put the java source files in the package structure directory:

maoshuai@ms:~/javaLinux/w1$ mkdir -p com/imshuai/javalinux
maoshuai@ms:~/javaLinux/w1$ mv *.java com/imshuai/javalinux/
maoshuai@ms:~/javaLinux/w1$ ls com/imshuai/javalinux/
HelloService.java  HelloWorld.java
maoshuai@ms:~/javaLinux/w1$ javac -d . com/imshuai/javalinux/*.java
maoshuai@ms:~/javaLinux/w1$ ls com/imshuai/javalinux/
HelloService.class  HelloService.java  HelloWorld.class  HelloWorld.java
maoshuai@ms:~/javaLinux/w1$ java com.imshuai.javalinux.HelloWorld
Hello, World!

When compiling, javac needs to pass in a new Java file path (wildcard is used here), which is no different from others. You can see that the class file is generated in the same directory as the java file. It's not refreshing to put the class file and Java source file together. Can you put the java file in the src directory and the class file in the target directory as in the IDE? Let me try.

First create src and target directories and move the original java files to src Directory:

maoshuai@ms:~/javaLinux/w1$ mkdir src
maoshuai@ms:~/javaLinux/w1$ mkdir target
maoshuai@ms:~/javaLinux/w1$ mv com src
maoshuai@ms:~/javaLinux/w1$ ls
src  target

Then compile and specify the - d parameter to the target directory:

maoshuai@ms:~/javaLinux/w1$ javac -d target src/com/imshuai/javalinux/*.java
maoshuai@ms:~/javaLinux/w1$ ls target/com/imshuai/javalinux/
HelloService.class  HelloWorld.class

How does it work? Running directly in the current directory is not enough. After all, there is an additional layer of target directory. Enter the target directory to run. It's appropriate:

maoshuai@ms:~/javaLinux/w1/target$ java com.imshuai.javalinux.HelloWorld
Hello, World!

In addition to entering the target directory, the more common method is to set the classpath through the - classpath (or abbreviated as - cp) option:

maoshuai@ms:~/javaLinux/w1$ java -cp target com.imshuai.javalinux.HelloWorld
Hello, World!

CLASSPATH classpath
The above demonstrates setting the classpath through - cp. Let's take a closer look at classpath.

Classpath is the path that JRE searches for user level class files or other resources. javac or java and other tools can specify the classpath. If not set, the default class path is the current directory. However, if the classpath is set, the default value will be overwritten, so if you want to keep the current directory as the classpath, you need to set the Join, a bit like the default constructor.

The CLASSPATH can be set through the environment variable CLASSPATH or the - cp parameter, which overrides the former. The - cp setting is recommended. It will only affect the current process.

Classpath is similar to the concept of path in the operating system, but it is the path for java tools to search class files. Similarly, Classpaths can be multiple and separated by semicolons:

export CLASSPATH=path1:path2:...

Or:

sdkTool -classpath path1:path2:...
sdkTool Can be java, javac, javadoc Wait.

The classpath can be not only a directory, but also a jar package or zip package.

Class paths are set in order. java will give priority to searching in the first class path. This is similar to the path of the operating system.

The classpath can match jar or zip with wildcard * but

  1. The wildcard only matches jar or zip. For example, path / * only adds the following jar or zip to the classpath, but the path itself does not add to the classpath.
  2. The wildcard does not recursively search, that is, it refers to matching the jar or zip under the first layer directory.
  3. The order in which the wildcard matches the jar or zip and is added to the classpath is uncertain. Therefore, it is safer to enumerate all jars or zips displayed.
  4. Wildcards apply to the CLASSPATH variable or the - cp parameter, but not to the manifest file of the jar package.

More realistic scenes

The following Java projects have more package structures and jar package dependencies. How to compile them? (engineering code download: https://github.com/maoshuai/java-linux-weekly/tree/master/attachment/w1)

├── lib
│   ├── logback-classic-1.2.3.jar
│   ├── logback-core-1.2.3.jar
│   └── slf4j-api-1.7.26.jar
├── resources
│   └── logback.xml
├── src
│   └── com
│       └── imshuai
│           └── javalinux
│               ├── HelloWorld.java
│               └── service
│                   ├── IGreetingService.java
│                   └── impl
│                       ├── AlienGreetingService.java
│                       ├── CatGreetingService.java
│                       ├── DogGreetingService.java
│                       └── HumanGreetingService.java
└── target

The most direct way is the same as just now, but with more manual work. Enumerate all java files and use wildcards to add jar s under lib to the classpath:

javac \
-cp "lib/*" \
-d target \
src/com/imshuai/javalinux/HelloWorld.java \
src/com/imshuai/javalinux/service/IGreetingService.java \
src/com/imshuai/javalinux/service/impl/*.java

After the compilation is successful, run the java command (Note: jar s under target and lib need to be added to the class path):

maoshuai@ms:~/javaLinux/w1$ java -cp "target:lib/*" com.imshuai.javalinux.HelloWorld XiaoMing
22:16:15.887 [main] INFO HumanGreetingService - XiaoMing is saying hello: Ni Chou Sha!

If there are many files, it is unrealistic to list them manually. You can use the find command:

javac -cp "lib/*" -d target $(find src -name "*.java")

javac also provides a method to list files, that is, write the list of java files to be compiled into a text file, and we use the find command to complete it:

maoshuai@ms:~/javaLinux/w1$ find src -name "*.java" >javaFiles.txt

Generated javafiles Txt reads as follows:

src/com/imshuai/javalinux/service/IGreetingService.java
src/com/imshuai/javalinux/service/impl/HumanGreetingService.java
src/com/imshuai/javalinux/service/impl/CatGreetingService.java
src/com/imshuai/javalinux/service/impl/DogGreetingService.java
src/com/imshuai/javalinux/service/impl/AlienGreetingService.java
src/com/imshuai/javalinux/HelloWorld.java

Start with @ javafiles @ Txt, which means that the list file name is passed to javac:

javac -cp "lib/*" -d target @javaFiles.txt

Not only that, parameters can also be put into files (Note: - cp cannot be put in). For example, javafiles Txt add - d target

-d target
src/com/imshuai/javalinux/service/IGreetingService.java
src/com/imshuai/javalinux/service/impl/HumanGreetingService.java
src/com/imshuai/javalinux/service/impl/CatGreetingService.java
src/com/imshuai/javalinux/service/impl/DogGreetingService.java
src/com/imshuai/javalinux/service/impl/AlienGreetingService.java
src/com/imshuai/javalinux/HelloWorld.java

In this way, only:

javac -cp "lib/*"  @javaFiles.txt

However, for clarity, we can put the parameter - d target into a separate file javaoptions Txt, and then transfer two @ files:

javac -cp "lib/*" @javaOptions.txt  @javaFiles.txt

The advantage of using list file is that it avoids the limit of command line parameter length and can run on any operating system.

With the above preparation, we can write a script for automatic compilation:

PROJECT_DIR=/home/maoshuai/javaLinux/w1
# clean target directory
rm -rf $PROJECT_DIR/target/*
# prepare arg files
find $PROJECT_DIR/src -name "*.java">$PROJECT_DIR/target/javaFiles.txt
echo "-d $PROJECT_DIR/target" >$PROJECT_DIR/target/javaOptions.txt
# compile
javac -cp "$PROJECT_DIR/lib/*" @$PROJECT_DIR/target/javaOptions.txt @$PROJECT_DIR/target/javaFiles.txt
# copy resources to target
cp -rf $PROJECT_DIR/resources/* $PROJECT_DIR/target
# clean temp files
rm -rf $PROJECT_DIR/target/javaOptions.txt $PROJECT_DIR/target/javaFiles.txt

It's time to take a closer look at javac

The official Oracle documentation describes javac as follows:

Reads Java class and interface definitions and compiles them into bytecode and class files.

The syntax of javac is as follows:

javac [ options ] [ sourcefiles ] [ classes] [ @argfiles ]
  • options: some parameters, such as - cp, - d
  • sourcefiles: compiled java files, such as HelloWorld java, which can be multiple and separated by spaces
  • classes: used to process annotations. I don't know how to use it for the time being
  • @argfiles is the file path containing the option or java file list, starting with the @ symbol, as shown above
    @javaOptions.txt and @ javafiles txt

summary

The basic usage of javac is summarized as follows:

-The cp parameter sets the classpath. The basic usage is to add the jar package that depends on during compilation to the classpath. You can also use * to configure jar packages.
-The d parameter is used to set the class file to compile to a separate directory and create a subdirectory according to the package name.
Theoretically, you can pass all the paths of java files to javac, but operationally, you can output the file list to the file through the find command and pass it through the @ argfiles parameter.

reference resources

https://docs.oracle.com/javase/8/docs/technotes/tools/unix/javac.html
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/classpath.html
https://docs.oracle.com/javase/tutorial/getStarted/problems/index.html
http://docs.oracle.com/javase/8/docs/technotes/tools/findingclasses.html

Topics: Java Programming Maven IDE