Tools such as IDE or Maven have taken over the compilation of Java programs. But the more advanced the tool, the more details are hidden, and when things go wrong, the underlying concept is weak. Going back to basics, back to the original javac, opens things up. Here is a step-by-step demonstration of running a regular project using Javac and Java freehand compilation.

Hello World practice

Let’s start with a simple, ancestral HelloWorld program. (If you’re interested, try writing it out with your bare hands.)

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

When finished, save as helloworld.java and execute javac compilation in the current directory:

javac HelloWorld.java
Copy the code

Check the current directory (or more accurately, the Java file equivalent directory), and sure enough, it generates helloworld.class:

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

Continue running the Java command in the current directory, correctly printing Hello, World!

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

Old driver, steady! It seems simple enough: Javac first, Java second.

It’s a simple but common mistake for novices to make: Imagine executing a.class file, such as this, and expect an error:

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

Java parameters are passed the name of the class in which the main function resides, not the class file. Java automatically finds the class file based on the class name.

With a package name

Everything went well, but it would be unprofessional to have no package name, so we added a nifty package com.imshuai.javalinux:

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

Helloworld.class is generated in the current directory.

Same with Java command, instant slap in the face:

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

Wanted to think, the HelloWorld already have their own package name, so there is no last name the name is not the HelloWorld, new name is com. Imshuai. Javalinux. The HelloWorld, then to the Java nature with a new name, try again:

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

Create a com/imshuai/javalinux directory and place helloworld.class in it.

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!
Copy the code

Sure enough, Hello, World!

The above steps illustrate two points:

  1. The package name has been added, so the class name has been changed.
  2. Java The package name corresponds to the directory structure, and the class path is searched for the class file. Since the default class path is the current directorycom.imshuai.javalinux.HelloWorldMust be stored in./com/imshuai/javalinux/HelloWorld.class

Of course, each time to create their own package path directory is too troublesome. The -d parameter can be used for 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!
Copy the code

-d specifies the root directory to generate the class file (in this case, the current directory) and creates subdirectories based on the package path of the class.

Compile two dependent classes

Package name solved, let’s make it more complicated, 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 + "!"); }}Copy the code

Then modify helloWorld.java and call HelloService to say hello:

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

Next we compile helloService. Java and helloworld.java, and 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!
Copy the code

It makes sense to compile HelloService.java first. How about compiling Helloworld.java first? Punching in the face, 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
Copy the code

If you compile, you have to determine the order based on dependencies, which is too low. I think the Java command should solve it automatically. Try passing two Java files to it at once:

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!
Copy the code

Awesome, it automatically solved the order problem, nice (although I put helloworld.java first with bad intentions)!

Use the SRC and target directories

As you can see from the example above, although class files must be in directories with the same package name, Java source files do not have this requirement. However, for ease of administration, we put the Java source files in the package structure directory as well:

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!
Copy the code

Javac passes in a new Java file path at compile time (using wildcards here), and nothing else is different. You can see that the class files are generated in the same directory as the Java files. Java files in the SRC directory, class files in the target directory, as in the IDE. So let me try it out.

Create the SRC and target directories and move the original Java files to the 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
Copy the code

The -d argument is then compiled 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
Copy the code

How does it work? Select * from target; select * from target;

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

In addition to entering the target directory, a more common way to set the classpath is with the -classpath (or -cp for short) option:

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

Class path CLASSPATH

Setting the classpath with -cp was demonstrated above. Let’s take a closer look at the classpath.

The classpath is the path where the JRE searches for user-level class files or other resources. Tools such as Javac and Java can specify the classpath. If not set, the default classpath is the current directory. But if the classpath is set, the default value is overridden, so if you want to keep the current directory as the classpath, you need to also change the. Add, kind of like the default constructor feel.

The CLASSPATH can be set using the environment variable CLASSPATH or the -cp argument, which overrides the former. The recommended setting is -cp, which affects only the current process.

The classpath is similar to the concept of path in operating systems, except that it is the path through which Java tools search for class files. Similarly, the classpath can be multiple, separated by semicolons:

export CLASSPATH=path1:path2:...
Copy the code

Or:

sdkTool -classpath path1:path2:...
Copy the code

SdkTool can be Java, Javac, Javadoc, etc.

The classpath can be a jar or zip package as well as a directory.

The classpath is set in order, and Java searches the first classpath first. This is similar to the path of an operating system.

The classpath can match jar or ZIP with the wildcard *, but

  1. Wildcards only match jars or zip, such as path/* which simply adds the following jar or zip to the classpath, but path itself does not add to the classpath.
  2. Wildcards do not recursively search, that is, match the JAR or ZIP in the first-level directory.
  3. The order in which the wildcard matches the JAR or ZIP is added to the classpath is uncertain. Therefore, it is safer to display enumerations of all JARS or Zip files.
  4. Wildcards apply toCLASSPATHA variable or-cpParameter, but does not apply to the MANIFEST file of the JAR package.

A more realistic scenario

The following Java project has more package structures and JAR package dependencies. How to compile it? (Engineering code download: github.com/maoshuai/ja…)

├ ─ ─ lib │ ├ ─ ─ logback - classic - 1.2.3. Jar │ ├ ─ ─ logback - core - 1.2.3. Jar │ └ ─ ─ slf4j - API - 1.7.26. Jar ├ ─ ─ resources │ └ ─ ─ ├─ SRC │ ├─ com │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ Java │ ├─ IGreetingService. Java │ └ ─ ─ impl │ ├ ─ ─ AlienGreetingService. Java │ ├ ─ ─ CatGreetingService. Java │ ├ ─ ─ DogGreetingService. Java │ └ ─ ─ HumanGreetingService. Java └ ─ ─ targetCopy the code

The most direct way, the same as before, but with a little more manual work, is to enumerate all Java files and add jars under Lib to the classpath using wildcards:

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
Copy the code

The target and lib jars need to be added to the classpath:

Maoshuai @ ms: ~ / javaLinux/w1 $Java - cp "target: lib / *" com. Imshuai. JavaLinux. The HelloWorld XiaoMing 22:16:15, 887 [main] INFO HumanGreetingService - XiaoMing is saying hello: Ni Chou Sha!Copy the code

If there are many files, it is not practical to list them manually. This can be done by using the find command:

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

Javac also provides a way to list files by writing a list of Java files to compile to a text file, which we do with the find command:

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

The generated javafiles.txt contains the following contents:

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.javaCopy the code

Then @javafiles. TXT, starting with @, represents the list file name passed to JavAC:

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

Not only that, arguments can also be put in files (note: -cp cannot be put in files). For example, add -d target to javaFiles. TXT

-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
Copy the code

To do this, simply execute:

javac -cp "lib/*"  @javaFiles.txt
Copy the code

-d target = ‘javaoptions.txt’; -d target = ‘javaoptions.txt’; -d target = ‘javaoptions.txt’;

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

The advantage of using a list file is that it avoids the limitation of the length of command-line arguments and can run on any operating system.

With this in mind, we can write an automated script:

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
Copy the code

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 ]
Copy the code
  • Options: are parameters, such as -cp, -d
  • Sourcefiles: is a compiled Java file, for exampleHelloWorld.java, can be multiple, separated by Spaces
  • Classes: For handling annotations. Not sure how to use it yet
  • @argfiles, which is the file path that contains a list of options or Java files, starts with the @ symbol, like @javaoptions. TXT and @javafiles.txt above

conclusion

The basic usage of Javac is summarized as follows:

  1. -cpParameter to set the classpath. The basic use is to add the jar packages that you depend on at compile time to the classpath. And can be used*Wildcard JAR package.
  2. -dThe class file is compiled to a separate directory and subdirectories are created based on the package name.
  3. Theoretically pass the entire path to a Java filejavacYou can, but operationally, print a list of files to a file using the find command, which is passed in with the @argFiles argument.

reference

  1. Docs.oracle.com/javase/8/do…
  2. Docs.oracle.com/javase/8/do…
  3. Docs.oracle.com/javase/8/do…
  4. Docs.oracle.com/javase/tuto…
  5. Docs.oracle.com/javase/8/do…

Github.com/maoshuai/ja…


Java and Linux Learning Weekly is published every Friday, updated synchronously on Github, Zhihu, Nuggets