Java で1つのソースコードファイルに複数のクラスを記述する
実験で書いてみましたが業務で利用するといろんな人がびっくりするのでやめましょう
テストコードならありなのかなぁ
前書き的なやつ
よくライブコーディングで見かけるやつが気になったので整理しました。 Java でも 1 つのソースコードファイルに複数のクラスを定義することができます。
example/app/Application.java
- 定義してるクラス
- Application クラス
- public static void main メソッドを定義してる
example/test/Utility.java
- 定義してるクラス
- Utility クラス
- Item クラス ?
- Option クラス ??
package example.app; import example.test.Utility; public class Application { public static void main(String[] args) { var utility = new Utility(); System.out.println("app: " + utility.doSomething(args[0], args[1])); } }
package example.test; public class Utility { public static void main(String[] args) { var utility = new Utility(); System.out.println("test: " + utility.doSomething(args[0], args[1])); } public Option doSomething(String name, String value) { return new Option(new Item(name, value)); } } class Item { private final String name; private final String value; Item(String name, String value) { this.name = name; this.value = value; } @Override public String toString() { return "Item[name=" + this.name + ",value=" + this.value + "]"; } } class Option { private final Item item; Option(Item item) { this.item = item; } @Override public String toString() { return "Option[item=" + this.item.toString() + "]"; } }
後者の Utility.java
に3つもクラスが定義されてるのは、普段見かけないからすごい不思議な感じがすると思います。
個人的には Application
クラスから example.test.Option
を返り値とする Utility#doSomething
を利用できてるのが不思議です。
理屈
パッケージに関する Java 言語仕様が関係してます。
少なくとも1つはファイル名と同じクラスやインターフェイスが存在すること、それらのクラスやインターフェイスの可視性が public であることがコンパイル可能であることの制約になっています。
結局、 Utility.java
で定義している Item
や Option
はただのパッケージスコープになっているということです。
パッケージがファイルシステム(7.2.1)に格納されるとき,ホストシステムは,次のいずれかが真の場合は,型名及び(.java 又は .jav のような)拡張子から構成される名前でファイル内にその型を見つけられなければ,コンパイル時エラーとする制約を課すことができる。
実践
コンパイルしてみましょう。成功するはずです。
$ ${JAVA_HOME}/bin/javac -version javac 11.0.8 $ ${JAVA_HOME}/bin/javac -verbose -d out example/app/Application.java example/test/Utility.java [/modules/java.transaction.xa/module-info.classを読込み中] [/modules/jdk.internal.jvmstat/module-info.classを読込み中] [/modules/jdk.jartool/module-info.classを読込み中] [/modules/jdk.crypto.ec/module-info.classを読込み中] [/modules/jdk.jshell/module-info.classを読込み中] [/modules/java.datatransfer/module-info.classを読込み中] [/modules/java.desktop/module-info.classを読込み中] [/modules/jdk.naming.rmi/module-info.classを読込み中] [/modules/jdk.jdeps/module-info.classを読込み中] [/modules/jdk.jsobject/module-info.classを読込み中] [/modules/jdk.jfr/module-info.classを読込み中] [/modules/jdk.security.jgss/module-info.classを読込み中] [/modules/java.logging/module-info.classを読込み中] [/modules/java.smartcardio/module-info.classを読込み中] [/modules/jdk.rmic/module-info.classを読込み中] [/modules/java.instrument/module-info.classを読込み中] [/modules/jdk.dynalink/module-info.classを読込み中] [/modules/jdk.pack/module-info.classを読込み中] [/modules/jdk.naming.dns/module-info.classを読込み中] [/modules/java.se/module-info.classを読込み中] [/modules/java.security.sasl/module-info.classを読込み中] [/modules/jdk.charsets/module-info.classを読込み中] [/modules/jdk.internal.vm.compiler.management/module-info.classを読込み中] [/modules/jdk.internal.ed/module-info.classを読込み中] [/modules/java.rmi/module-info.classを読込み中] [/modules/jdk.sctp/module-info.classを読込み中] [/modules/jdk.security.auth/module-info.classを読込み中] [/modules/jdk.internal.vm.ci/module-info.classを読込み中] [/modules/jdk.jlink/module-info.classを読込み中] [/modules/java.base/module-info.classを読込み中] [/modules/java.sql/module-info.classを読込み中] [/modules/jdk.unsupported.desktop/module-info.classを読込み中] [/modules/jdk.compiler/module-info.classを読込み中] [/modules/java.net.http/module-info.classを読込み中] [/modules/jdk.xml.dom/module-info.classを読込み中] [/modules/jdk.scripting.nashorn.shell/module-info.classを読込み中] [/modules/jdk.management.agent/module-info.classを読込み中] [/modules/java.management/module-info.classを読込み中] [/modules/jdk.management/module-info.classを読込み中] [/modules/jdk.internal.le/module-info.classを読込み中] [/modules/jdk.zipfs/module-info.classを読込み中] [/modules/jdk.jstatd/module-info.classを読込み中] [/modules/jdk.internal.vm.compiler/module-info.classを読込み中] [/modules/java.compiler/module-info.classを読込み中] [/modules/java.xml.crypto/module-info.classを読込み中] [/modules/jdk.httpserver/module-info.classを読込み中] [/modules/jdk.aot/module-info.classを読込み中] [/modules/jdk.jcmd/module-info.classを読込み中] [/modules/java.scripting/module-info.classを読込み中] [/modules/jdk.localedata/module-info.classを読込み中] [/modules/jdk.editpad/module-info.classを読込み中] [/modules/java.prefs/module-info.classを読込み中] [/modules/java.xml/module-info.classを読込み中] [/modules/jdk.unsupported/module-info.classを読込み中] [/modules/java.sql.rowset/module-info.classを読込み中] [/modules/jdk.net/module-info.classを読込み中] [/modules/jdk.jdi/module-info.classを読込み中] [/modules/jdk.internal.opt/module-info.classを読込み中] [/modules/java.naming/module-info.classを読込み中] [/modules/jdk.crypto.cryptoki/module-info.classを読込み中] [/modules/jdk.management.jfr/module-info.classを読込み中] [/modules/jdk.hotspot.agent/module-info.classを読込み中] [/modules/jdk.jdwp.agent/module-info.classを読込み中] [/modules/java.security.jgss/module-info.classを読込み中] [/modules/jdk.attach/module-info.classを読込み中] [/modules/jdk.accessibility/module-info.classを読込み中] [/modules/jdk.javadoc/module-info.classを読込み中] [/modules/jdk.scripting.nashorn/module-info.classを読込み中] [/modules/jdk.crypto.mscapi/module-info.classを読込み中] [/modules/jdk.jconsole/module-info.classを読込み中] [/modules/java.management.rmi/module-info.classを読込み中] [ソース・ファイルの検索パス: .] [/modules/java.base/java/lang/Object.classを読込み中] [/modules/java.base/java/lang/String.classを読込み中] [/modules/java.base/java/lang/Deprecated.classを読込み中] [/modules/java.base/java/lang/Override.classを読込み中] [/modules/java.base/java/lang/annotation/Annotation.classを読込み中] [/modules/java.base/java/lang/annotation/Retention.classを読込み中] [/modules/java.base/java/lang/annotation/RetentionPolicy.classを読込み中] [/modules/java.base/java/lang/annotation/Target.classを読込み中] [/modules/java.base/java/lang/annotation/ElementType.classを読込み中] [example.app.Applicationを確認中] [/modules/java.base/java/io/Serializable.classを読込み中] [/modules/java.base/java/lang/AutoCloseable.classを読込み中] [/modules/java.base/java/lang/System.classを読込み中] [/modules/java.base/java/io/PrintStream.classを読込み中] [/modules/java.base/java/lang/Appendable.classを読込み中] [/modules/java.base/java/io/Closeable.classを読込み中] [/modules/java.base/java/io/FilterOutputStream.classを読込み中] [/modules/java.base/java/io/OutputStream.classを読込み中] [/modules/java.base/java/io/Flushable.classを読込み中] [out\example\app\Application.classを書込み完了] [example.test.Utilityを確認中] [out\example\test\Utility.classを書込み完了] [example.test.Optionを確認中] [/modules/java.base/java/lang/Byte.classを読込み中] [/modules/java.base/java/lang/Character.classを読込み中] [/modules/java.base/java/lang/Short.classを読込み中] [/modules/java.base/java/lang/Long.classを読込み中] [/modules/java.base/java/lang/Float.classを読込み中] [/modules/java.base/java/lang/Integer.classを読込み中] [/modules/java.base/java/lang/Double.classを読込み中] [/modules/java.base/java/lang/Boolean.classを読込み中] [/modules/java.base/java/lang/Void.classを読込み中] [/modules/java.base/java/lang/invoke/StringConcatFactory.classを読込み中] [/modules/java.base/java/lang/invoke/MethodHandles.classを読込み中] [/modules/java.base/java/lang/invoke/MethodHandles$Lookup.classを読込み中] [/modules/java.base/java/lang/invoke/MethodType.classを読込み中] [/modules/java.base/java/lang/invoke/CallSite.classを読込み中] [out\example\test\Option.classを書込み完了] [example.test.Itemを確認中] [out\example\test\Item.classを書込み完了] [合計317ミリ秒]
ちゃんと実行できます。
$ ${JAVA_HOME}/bin/java -cp out example.app.Application a 1 app: Option[item=Item[name=a,value=1]] $ ${JAVA_HOME}/bin/java -cp out example.test.Utility b 2 test: Option[item=Item[name=b,value=2]]