サンプルコードで身につける: 成果物の単位: .class・JAR・WAR・実行可能 JAR
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1ソース1ファイルから .class が9個できる
Shapes.java という1つのソースをコンパイルした結果です。入れ子のやは Shapes$Shape.class のように $ でつないだ名前の別ファイルになり、合わせて9個できました。 は「ソースファイルごと」ではなく「型ごと」の単位です。(その場で書く関数)だけは、ファイルになりません。
$ find src -type fsrc/com/example/units/Shapes.java$ javac -d out src/com/example/units/Shapes.java$ find out -type f | sortout/com/example/units/SecondTopLevel.classout/com/example/units/Shapes$1.classout/com/example/units/Shapes$1Local.classout/com/example/units/Shapes$Circle.classout/com/example/units/Shapes$Helper.classout/com/example/units/Shapes$Inner.classout/com/example/units/Shapes$Shape.classout/com/example/units/Shapes$Unit.classout/com/example/units/Shapes.class$ find out -type f | wc -l9
// src/com/example/units/Shapes.java
package com.example.units;
import java.util.function.Supplier;
public class Shapes { // 1: Shapes.class
interface Shape { double area(); } // 2: Shapes$Shape.class(入れ子のインターフェース)
record Circle(double r) implements Shape { // 3: Shapes$Circle.class(入れ子の record)
public double area() { return Math.PI * r * r; }
}
enum Unit { CM, INCH } // 4: Shapes$Unit.class(入れ子の enum)
static class Helper { // 5: Shapes$Helper.class(static な入れ子のクラス)
static String label(Unit u) { return u.name().toLowerCase(); }
}
class Inner { } // 6: Shapes$Inner.class(内部クラス)
public static void main(String[] args) {
Shape square = new Shape() { // 7: Shapes$1.class(無名クラス)
public double area() { return 4.0; }
};
Supplier<String> lambda = () -> "lambda"; // ファイルはできない
Runnable methodRef = System.out::println; // ファイルはできない
class Local { } // 8: Shapes$1Local.class(メソッドの中のクラス)
System.out.println(new Circle(1).area() + " " + square.area() + " " + lambda.get() + " " + Helper.label(Unit.CM));
System.out.println("lambda class at run time: " + lambda.getClass().getName().replaceAll("0x[0-9a-f]+", "0x..."));
methodRef.run();
}
}
class SecondTopLevel { } // 9: SecondTopLevel.class(同じファイルの2つ目の型)
2JAR の中身: .class の階層とマニフェスト
2クラスだけのプログラムを コマンドでまとめた app.jar(1,800 B)の中です。.class がフォルダの階層ごと入り、 が足されています。の の行が、 で起動するときの入口です。
$ ls -l app.jar && jar tf app.jar-rw-r--r-- 1 root root 1800 Oct 1 22:26 app.jarMETA-INF/META-INF/MANIFEST.MFcom/com/example/com/example/app/com/example/app/Main.classcom/example/greet/com/example/greet/Greeter.class# META-INF/MANIFEST.MF の中身Manifest-Version: 1.0Created-By: 25.0.4.1 (Eclipse Adoptium)Main-Class: com.example.app.Main
3WAR の中身: 置く場所が決まっている
で作った の (7,825,305 B)の中です。自分の .class は の下、 は の下、設定は と、置く場所が決まっています。 はこの決まりに従って中身を読みます。マニフェストに Main-Class は無く、java -jar で起動するものではありません。
META-INF/META-INF/MANIFEST.MFWEB-INF/WEB-INF/web.xmlWEB-INF/applicationContext.xmlWEB-INF/dispatcher-servlet.xmlindex.htmlWEB-INF/classes/WEB-INF/classes/jp/WEB-INF/classes/jp/co/WEB-INF/classes/jp/co/example/WEB-INF/classes/jp/co/example/shop/WEB-INF/classes/jp/co/example/shop/service/WEB-INF/classes/jp/co/example/shop/web/WEB-INF/classes/jp/co/example/shop/service/GreetingService.classWEB-INF/classes/jp/co/example/shop/web/HelloController.classWEB-INF/lib/WEB-INF/lib/commons-logging-1.3.5.jarWEB-INF/lib/jspecify-1.0.0.jarWEB-INF/lib/micrometer-commons-1.16.7.jarWEB-INF/lib/micrometer-observation-1.16.7.jarWEB-INF/lib/spring-aop-7.0.9.jarWEB-INF/lib/spring-beans-7.0.9.jarWEB-INF/lib/spring-context-7.0.9.jarWEB-INF/lib/spring-core-7.0.9.jarWEB-INF/lib/spring-expression-7.0.9.jarWEB-INF/lib/spring-web-7.0.9.jarWEB-INF/lib/spring-webmvc-7.0.9.jar
4単位ごとに「何を入れて、何に渡すか」
このコースで実際に作ったを、単位ごとに並べた表です。上から下へ、入れるものが増えていきます。jar コマンドで作る までは依存が外にあり、WAR と は依存を中に持ちます。渡す先が コマンドなのか、java の上で動いている Tomcat なのかで、起動の仕方も変わります。
.class入れるもの : 型1つ分のバイトコード渡す先 : JVM(クラスを読む係が、必要になった時点で読む)JAR入れるもの : .class と設定ファイルを階層ごと zip にしたもの + META-INF/MANIFEST.MF依存 : jar コマンドで普通に作ると入らない(実行時に -cp で並べる)渡す先 : java -cp x.jar 入口のクラス名 / Main-Class があれば java -jar x.jar実例 : app.jar 1,800 B(.class 2個)WAR入れるもの : WEB-INF/classes/(自分の .class)+ WEB-INF/lib/(依存の JAR)+ WEB-INF/web.xml + 静的ファイル依存 : 入る渡す先 : Tomcat などの Servlet コンテナ(java で動いている)。webapps/ に置く。main は無い実例 : shop.war 7,825,305 B(依存の JAR 11本)実行可能 JAR(Spring Boot)入れるもの : BOOT-INF/classes/(自分の .class)+ BOOT-INF/lib/(依存の JAR)+ 起動用のローダ依存 : 入る(Tomcat も依存の1つとして入る)渡す先 : java -jar x.jar実例 : demo-0.0.1-SNAPSHOT.jar 約 19.9 MB(依存の JAR 34本)実行可能 WAR(Spring Boot)入れるもの : WEB-INF/classes/ + WEB-INF/lib/ + WEB-INF/lib-provided/(組み込みの Tomcat など)+ 起動用のローダ渡す先 : java -jar x.war でも、Tomcat の webapps/ でも実例 : demo-0.0.1-SNAPSHOT.war 19,904,763 B
5同じ WAR を java -jar でも Tomcat でも動かす
で作った WAR は、java -jar で直接起動することも、Tomcat の に置くこともできました(各1回の確認)。違いは誰が先に起きるかです。java -jar では が中の Tomcat を起こし、Tomcat に置いた場合は Tomcat が Spring を起こします。後者では URL に WAR の名前が入るので、/hello は 404 になり、/demo/hello で応答が返りました。
# 1. java -jar で起動$ java -jar target/demo-0.0.1-SNAPSHOT.war2026-10-01T22:32:28.574Z INFO 1 --- [demo] [ main] o.s.boot.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'$ curl -i http://localhost:8080/helloHTTP/1.1 200Content-Type: text/plain;charset=UTF-8Content-Length: 22Hello from Spring Boot# 2. 同じファイルを Tomcat 11.0.26 の webapps/demo.war に置く01-Oct-2026 22:32:29.518 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deploying web application archive [/usr/local/tomcat/webapps/demo.war]2026-10-01T22:32:30.487Z INFO 1 --- [demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.524 seconds (process running for 1.272)01-Oct-2026 22:32:30.496 INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [/usr/local/tomcat/webapps/demo.war] has finished in [978] ms$ curl -i http://localhost:8080/demo/helloHTTP/1.1 200Content-Type: text/plain;charset=UTF-8Content-Length: 22Hello from Spring Boot$ curl -i http://localhost:8080/helloHTTP/1.1 404