[LINUX] Java compilation and execution understood by CLI

I think most of the time I use an IDE for Java programming. Even when you start learning Java programming, it is often required from the preparation of the IDE. However, when beginners start learning from Java programming in the IDE, even if they can write code on the IDE, they cannot deeply understand the parts that the IDE supports, or they can only work depending on the IDE. I can't grow from the state. This article is for beginners to deepen their understanding by executing simple Java programs with CLI without using IDE. It does not mention the detailed reading or writing of the program. Difficult words and concepts may appear for beginners on the way, but it is not necessary to understand them immediately, so please read them and gradually understand them.

environment

It will be the OS to work on this time and the version of Java to be used. In this article, I'm working on CentOS, but if you can't install vagrant, you can work on Mac, and if you replace UNIX commands, you can work on Windows command prompt (not verified). ..

OS

Use this BOX to start a VM with vagrant on VirtualBox and work in it.

$ cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

Java

Install This OpenJDK with yum and use it.

$ java -version
java 10.0.2 2018-07-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.2+13)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.2+13, mixed mode)

$ javac -version
javac 10.0.2

Java program

Java programs are usually compiled with the javac tool from .java to .class bytecode (also called intermediate code) before execution. Then, when the program is executed, the bytecode is executed on the JVM in an interpreted manner, or it is recompiled into machine code by the JIT compiler and executed. The JIT compiler is one of the components of JRE (Java Runtime Environment), and is a mechanism to optimize the machine code of program methods and improve performance.

From here, you will actually compile and execute Java programs and create archives with the CLI.

Basic compilation and execution

First, write a program that outputs Hello world. and execute it. The program file to be executed is as follows, so prepare it with vi etc.

App.java


class App {
    public static void main(String[] args) {
        System.out.println("Hello world.");
    }
}

Compile this with javac.

$ ls
App.java

$ javac App.java

$ ls
App.class  App.java

You can see that the .class file has been generated. Then run it in java. Note that the argument is the class name, not the file name.


$ java App 
Hello world.

It was easy, but I compiled and ran the program. There is no problem so far.

Execution with arguments

Try passing arguments when running the program. Make the following modifications to the previous program file.

App.java


class App {
    public static void main(String[] args) {
        for (String arg: args) {
            System.out.println(String.format("Hello %s.", arg));
        }
    }
}

Compile and run. Unlike before, let's pass arguments at runtime.

$ java App Alice Bob Carol
Hello Alice.
Hello Bob.
Hello Carol.

You can see that it has received the arguments.

Use of other classes

Try accessing other classes in your program. First, create a Human class that represents humans as another class in another file.

Human.java


class Human {
    String name;
    Human(String name) {
            this.name = name;
    }
    void introduceMyself() {
            System.out.println(String.format("My name is %s.", this.name));
    }
}

Instantiate the Human class in the main method of the App class.

App.java


class App {
    public static void main(String[] args) {
        for (String arg: args) {
            Human human = new Human(arg);
            human.introduceMyself();
        }
    }
}

Let's compile it.

$ ls
App.java  Human.java

$ javac App.java

$ ls
App.class  App.java  Human.class  Human.java

You can see that Human was compiled with the compilation of the app.

$ java App Alice Bob Carol
My name is Alice.
My name is Bob.
My name is Carol.

I created as many Human instances as the number of arguments and confirmed that the method was executed.

Package management

Give it a package name and compile it as a program in a separate package. First, give the Human class a package name. Also, since this class is accessed from another package, I gave each qualifier correctly.

Human.java


package jp.co.sample.lib;

public class Human {
    private String name;
    public Human(String name) {
        this.name = name;
    }
    public void introduceMyself() {
        System.out.println(String.format("My name is %s.", this.name));
    }
}

Next, for the App class, give the package name and also describe import to access the Human class.

App.java


package jp.co.sample;

import jp.co.sample.lib.Human;

class App {
    public static void main(String[] args) {
        for (String arg: args) {
            Human human = new Human(arg);
            human.introduceMyself();
        }
    }
}

The package name has been assigned, but it cannot be compiled as it is. In Java, it is necessary to arrange the files in the same directory structure as the package name. So create a directory as below and move the files.

$ tree
.
└── jp
    └── co
        └── sample
            ├── App.java
            └── lib
                └── Human.java

4 directories, 2 files

After moving the file, compile and run it.

$ javac jp/co/sample/App.java

$ tree
.
└── jp
    └── co
        └── sample
            ├── App.class
            ├── App.java
            └── lib
                ├── Human.class
                └── Human.java

4 directories, 4 files

$ java jp.co.sample.App Alice Bob Carol
My name is Alice.
My name is Bob.
My name is Carol.

It was confirmed that the .class file was created in the same hierarchy as each .java file and the program could be executed.

Creating a JAR file

Combine the created .class files into .jar and create an archive. The .java file is not included in .jar, so separate it as a src directory.

$ tree
.
└── src
    └── jp
        └── co
            └── sample
                ├── App.java
                └── lib
                    └── Human.java

5 directories, 2 files

Compile and output the .class file to the classes directory. If you want to run in a location other than the package origin src directory, you need to specify the package origin with the -sourcepath option.

$ javac -sourcepath src -d classes src/jp/co/sample/App.java 

$ tree
.
├── classes
│   └── jp
│       └── co
│           └── sample
│               ├── App.class
│               └── lib
│                   └── Human.class
└── src
    └── jp
        └── co
            └── sample
                ├── App.java
                └── lib
                    └── Human.java

10 directories, 4 files

You can see that the classes directory is created and the .class files are generated under it with the same directory structure as the package. By the way, if you want to execute at a location other than the package origin at runtime, you need to specify the package origin with the -classpath option.

$ java -classpath classes jp.co.sample.App Alice Bob Carol
My name is Alice.
My name is Bob.
My name is Carol.

Next, the MANIFEST file is required to create .jar, so create the following file. The class name that has the main method is described here including the package name. Don't forget that if you don't put a blank line in the last line, it will not be recognized as a MANIFEST file.

manifest.mf


Main-Class: jp.co.sample.App

After preparing the MANIFEST file, create a .jar file with jar.

$ jar cvfm sample.jar manifest.mf -C classes .
Manifest added
jp/Is being added(Enter=0)(Out=0)(0%Stored)
jp/co/Is being added(Enter=0)(Out=0)(0%Stored)
jp/co/sample/Is being added(Enter=0)(Out=0)(0%Stored)
jp/co/sample/App.class is being added(Enter=469)(Out=343)(26%Shrinked)
jp/co/sample/lib/Is being added(Enter=0)(Out=0)(0%Stored)
jp/co/sample/lib/Human.class is being added(Enter=595)(Out=382)(35%Shrinked)

$ ls
classes  manifest.mf  sample.jar  src

$ jar -tf sample.jar 
META-INF/
META-INF/MANIFEST.MF
jp/
jp/co/
jp/co/sample/
jp/co/sample/App.class
jp/co/sample/lib/
jp/co/sample/lib/Human.class

You can see that the .jar file contains the MANIFEST file and the .class file. Finally, let's run the .jar file.

$ java -jar sample.jar Alice Bob Carol
My name is Alice.
My name is Bob.
My name is Carol.

Summary

In this article, I wrote a program, compiled it, and then created and executed a .jar file. You may have felt that the work content is very different between working in the IDE and typing commands in the CLI to compile and execute. While you can work intuitively with an IDE that has a UI, you need to understand and execute commands one by one when working with the CLI. I think that understanding the contents of this article will also help you understand the work in IDE.

bonus

OpenJDK includes a tool that can disassemble the compiled .class file, and you can follow the detailed instructions of the program, so if you can afford it, you should take a look.

$ javap -v -classpath classes jp.co.sample.App
Classfile /home/vagrant/java_test/classes/jp/co/sample/App.class
  Last modified 2019/04/30; size 469 bytes
  MD5 checksum 7ad6f96dd09200ac12a4c48cadb71ea8
  Compiled from "App.java"
class jp.co.sample.App
  minor version: 0
  major version: 54
  flags: (0x0020) ACC_SUPER
  this_class: #5                          // jp/co/sample/App
  super_class: #6                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #6.#17         // java/lang/Object."<init>":()V
   #2 = Class              #18            // jp/co/sample/lib/Human
   #3 = Methodref          #2.#19         // jp/co/sample/lib/Human."<init>":(Ljava/lang/String;)V
   #4 = Methodref          #2.#20         // jp/co/sample/lib/Human.introduceMyself:()V
   #5 = Class              #21            // jp/co/sample/App
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               StackMapTable
  #14 = Class              #23            // "[Ljava/lang/String;"
  #15 = Utf8               SourceFile
  #16 = Utf8               App.java
  #17 = NameAndType        #7:#8          // "<init>":()V
  #18 = Utf8               jp/co/sample/lib/Human
  #19 = NameAndType        #7:#24         // "<init>":(Ljava/lang/String;)V
  #20 = NameAndType        #25:#8         // introduceMyself:()V
  #21 = Utf8               jp/co/sample/App
  #22 = Utf8               java/lang/Object
  #23 = Utf8               [Ljava/lang/String;
  #24 = Utf8               (Ljava/lang/String;)V
  #25 = Utf8               introduceMyself
{
  jp.co.sample.App();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0

  public static void main(java.lang.String[]); 
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=6, args_size=1
         0: aload_0
         1: astore_1
         2: aload_1
         3: arraylength
         4: istore_2
         5: iconst_0
         6: istore_3
         7: iload_3
         8: iload_2
         9: if_icmpge     39
        12: aload_1
        13: iload_3
        14: aaload
        15: astore        4
        17: new           #2                  // class jp/co/sample/lib/Human
        20: dup
        21: aload         4
        23: invokespecial #3                  // Method jp/co/sample/lib/Human."<init>":(Ljava/lang/String;)V
        26: astore        5
        28: aload         5
        30: invokevirtual #4                  // Method jp/co/sample/lib/Human.introduceMyself:()V
        33: iinc          3, 1
        36: goto          7
        39: return
      LineNumberTable:
        line 7: 0
        line 8: 17
        line 9: 28
        line 7: 33
        line 11: 39
      StackMapTable: number_of_entries = 2
        frame_type = 254 /* append */
          offset_delta = 7
          locals = [ class "[Ljava/lang/String;", int, int ]
        frame_type = 248 /* chop */
          offset_delta = 31
}
SourceFile: "App.java"

Recommended Posts

Java compilation and execution understood by CLI
Execution by subshell (and variable update)
Notify error and execution completion by LINE [Python]
pytube execution and error
Understand the Decorator pattern by comparing JavaScript and Java code
Understand the State pattern by comparing JavaScript and Java code
Understand the Composite pattern by comparing JavaScript and Java code
The difference between foreground and background processes understood by the principle