1src/service に置いても動く
service; と書いたを src/service に、入口を src/app に置いた構成です。 でコンパイルと実行ができ、同じ配置を でもビルドできました。の接頭辞も も、Java としては必須ではありません。ただしこれは を載せていない素の Java で、Boot では app と service の上に共通の根のが要ります。
$ find src -type f | sortsrc/app/Main.javasrc/service/UserService.java[exit 0]$ javac -d out -sourcepath src src/app/Main.java[exit 0]$ find out -type f | sortout/app/Main.classout/service/UserService.class[exit 0]$ java -cp out app.Mainuser-1 (Main is app.Main, service is service.UserService)[exit 0]# 同じ配置を Maven でビルドする。pom.xml に足したのは次の3行(Maven 3.9.16)<build><sourceDirectory>src</sourceDirectory></build>$ mvn -B -o -ntp -q package; echo "mvn exit $?"mvn exit 0[exit 0]$ find target/classes -type f | sort; ls target/*.jartarget/classes/app/Main.classtarget/classes/service/UserService.classtarget/flat-1.0.0.jar[exit 0]$ jar tf target/flat-1.0.0.jar | grep classapp/Main.classservice/UserService.class[exit 0]$ java -cp target/flat-1.0.0.jar app.Mainuser-1 (Main is app.Main, service is service.UserService)[exit 0]
2package 宣言と、出力先と、起動の仕方
package com.example.demo; と書いたクラスをコンパイルすると、出力は out/com/example/demo/Hello.class になります。起動できるのは、の起点(out)との両方が合っているときだけです。ファイルのあるディレクトリへ移動して Hello としても、wrong name のエラーになります。末尾は、名前をそのままファイル名にした平らな置き方で、これも見つかりません。
$ find src -type fsrc/com/example/demo/Hello.java[exit 0]$ javac -d out src/com/example/demo/Hello.java[exit 0]$ find out -type fout/com/example/demo/Hello.class[exit 0]##### (2) how to run it$ java -cp out com.example.demo.HelloHello from com.example.demo.Hello[exit 0]$ java -cp out HelloError: Could not find or load main class HelloCaused by: java.lang.ClassNotFoundException: Hello[exit 1]$ cd out/com/example/demo && ls && java HelloHello.classError: Could not find or load main class HelloCaused by: java.lang.NoClassDefFoundError: Hello (wrong name: com/example/demo/Hello)[exit 1]$ cd out/com/example/demo && java -cp . com.example.demo.HelloError: Could not find or load main class com.example.demo.HelloCaused by: java.lang.ClassNotFoundException: com.example.demo.Hello[exit 1]$ java -cp out com/example/demo/HelloHello from com.example.demo.Hello[exit 0]$ java -cp out/com/example/demo HelloError: Could not find or load main class HelloCaused by: java.lang.NoClassDefFoundError: Hello (wrong name: com/example/demo/Hello)[exit 1]##### (2b) flat file name does not work$ mkdir -p flat && cp out/com/example/demo/Hello.class flat/com.example.demo.Hello.class && ls flatcom.example.demo.Hello.class[exit 0]$ java -cp flat com.example.demo.HelloError: Could not find or load main class com.example.demo.HelloCaused by: java.lang.ClassNotFoundException: com.example.demo.Hello[exit 1]
3同じ名前のクラスが2つの JAR にあるとき
com.example.util.Version という同じ名前のクラスを、a.jar と b.jar の両方に入れた実験です。 に並べる順番を入れ替えるだけで、エラーも警告も無いまま、使われるクラスが変わりました。なお、入口の app.Main は逆ドメインの接頭辞を持たないパッケージですが、問題なく動いています。
$ javac -d dup/m -cp dup/a.jar dupmain/app/Main.java[exit 0]$ java -cp dup/m:dup/a.jar:dup/b.jar app.Mainfrom a.jarfile:/w/dup/a.jar[exit 0]$ java -cp dup/m:dup/b.jar:dup/a.jar app.Mainfrom b.jarfile:/w/dup/b.jar
4親子に見えるパッケージに特別な関係は無い
jp.co.example.shop の中だけで使えるクラス(修飾子なし)を、同じパッケージから使うと動き、jp.co.example.shop.order から使うとコンパイルエラーになりました。下の階層は別のパッケージで、親の中身を見る特権はありません。(JLS 7.1)にも there is no special access relationship と書かれています。
$ find pp -type fpp/jp/co/example/shop/order/OrderService.javapp/jp/co/example/shop/SameshopUser.javapp/jp/co/example/shop/ShopSecret.java[exit 0]$ javac -d outpp pp/jp/co/example/shop/ShopSecret.java pp/jp/co/example/shop/SameshopUser.java && java -cp outpp jp.co.example.shop.SameshopUsersecret[exit 0]$ javac -d outpp pp/jp/co/example/shop/ShopSecret.java pp/jp/co/example/shop/order/OrderService.javapp/jp/co/example/shop/order/OrderService.java:3: error: ShopSecret is not public in jp.co.example.shop; cannot be accessed from outside packageimport jp.co.example.shop.ShopSecret;^pp/jp/co/example/shop/order/OrderService.java:6: error: ShopSecret is not public in jp.co.example.shop; cannot be accessed from outside packageString read() { return ShopSecret.value(); }^2 errors[exit 1]
5Node は場所で指し、Java は名前で指す
TypeScript では、自分のコードは相対パスで、ライブラリはパッケージ名で します。ライブラリの実体は node_modules という別の場所にあります。Java は、自分のコードもライブラリも同じ完全修飾名で指し、同じクラスパスから探します。世界で重ならない名前を、npm ではパッケージ名(と)が担い、Java ではクラスの名前そのものが担っています。
// TypeScript / Node: 自分のコードは「場所」で指す(このファイルから見た相対パス)
import { OrderService } from './order.service.js';
// ライブラリは「パッケージ名」で指す。実体は node_modules という別の場所にある
import { Injectable } from '@nestjs/common';
// Java では、自分のコードもライブラリも同じ書き方(完全修飾名)で指す
// import jp.co.example.shop.order.OrderService; 自分のコード
// import org.springframework.stereotype.Service; ライブラリ
// どちらも「クラスパスのどこかにある、この名前のクラス」という意味で、場所は書かない
@Injectable()
export class OrderFacade {
constructor(private readonly orders: OrderService) {}
}