Javaのクラス名の形式まとめ
Javaのクラス名の表現方法で
— なぎせ ゆうき (@nagise) April 14, 2020
「全部同じじゃないですか」
「ちがいますよーーっ」
「これだからしろうとはダメだ!もっとよく見ろ!」
をやっている🤔
Java言語を扱っていると何通りかのクラス名の表記法を見ることがある
nagise.sample.Hoge.Piyo
nagise.sample.Hoge$Piyo
nagise/sample/Hoge$Piyo
[Lnagise.sample.Hoge.Piyo
これらの違いは何なのか。
Javaのクラス名関連の専門用語を調べ直してみた。
用語 | 日本語 | 概要 |
---|---|---|
Identifier | 識別子 | 識別に用いるもの全般を指した抽象度の高い表現 |
Class Name | クラス名 | 一般に言うクラス名。比較的曖昧な表現。文脈によってはInterfaceも含む。ネストしたクラス、内部クラスの場合には外側のクラスを含めたり含めなかったりする。 |
Type Name | 型名 | ここでいう型はInterface / Classを区別しない。参照型に限定した表現でプリミティブ型を含まない |
Simple Type Name | 単純型名 | パッケージ名を含まない型名。Qualified Type Nameと対比して使う表現 |
Qualified Type Name | 限定型名 | パッケージ名を含む型名。Simple Type Nameと対比して使う表現 |
Fully Qualified Name | 完全限定名 | パッケージ名を含む型名。こちらは参照型に限定せずプリミティブ型も含めての型名として用いられている |
Canonical Name | 正規名 | 通常はFully Qualified Nameに同じ。内部クラスなどで同じクラスを表す別Fully Qualified Nameが生じるケースが有って、そうした場合に正規化されたものになる |
Binary Name | バイナリ名 | 概ね正規名と同じ。内部クラスでは外側のクラスと$で結合したものとなる |
Binary Name(class file) | バイナリ名 | クラスファイルフォーマット中のバイナリ名はThe Java® Language Specificationのバイナリ名のピリオドをスラッシュに置き換えたもの |
Simple Type Name と Qualified Type Name
The Java® Language Specificationの Chapter 6. Names では識別子について書かれている。この中の 6.5.5. Meaning of Type Names では Simple Type Name と Qualified Type Name という用語が出てくる。
これはクラス名だけの部分をさして "Simple Type Name" 単純型名と呼び、パッケージ名つきのものを "Qualified Type Name" 限定型名と呼んでいる。これはそう難しくはない。
Fully Qualified Name 完全限定名(FQN)
同じく Chapter 6. Names の 6.7. Fully Qualified Names and Canonical Names では Fully Qualified Names という用語が出てくる。Fully Qualified Names は略してFQNとも呼ばれる。
・The fully qualified name of a primitive type is the keyword for that primitive type, namely byte, short, char, int, long, float, double, or boolean.
6.7. Fully Qualified Names and Canonical Names
・プリミティブ型の完全限定名はプリミティブ型のキーワード名と同じくbyte, short, char, int, long, float, double, boolean である
"Fully Qualified Name" という用語にはプリミティブ型も含まれるということだ。また、詳しくは取り上げないがパッケージ名のFQNについても書かれているのでFQNにはパッケージも含むことになるだろう。
トップレベルのクラスは「パッケージ名.クラス名」となる。
そしてメンバークラス(これはネストしたクラスと内部クラスをまとめた概念)の場合は「外部クラスFQN.メンバークラス名」となる。
nagise.sample.Hoge.Piyo
は"Fully Qualified Name" 完全限定名か!?
Canonical Name 正規名
6.7. Fully Qualified Names and Canonical Names には "Fully Qualified Name" 完全限定名 と "Canonical Name" 正規名が出てくる。正規名は完全限定名を正規化したものとなる。
package nagise.sample; public class Hoge { public class Inner {} } class Piyo extends Hoge {}
といったように、内部クラスInnerを持つHoge型を継承したPiyo型があったとする。このとき
nagise.sample.Hoge.Inner
と
nagise.sample.Piyo.Inner
は同じ型を指している。同じ型に対して複数の完全限定名があり得るということだ。これを正規化したものが "Canonical Name" 正規名と言うことになる。
nagise.sample.Hoge.Piyo
のようなクラス名はプログラムから出力されたものがであることがほとんどだろう。完全限定名ではなく正規名である可能性が高い。
Binary Name バイナリ名
13.1. The Form of a Binary にバイナリ名の規定がある。
・The binary name of a top level type (§7.6) is its canonical name (§6.7).
・The binary name of a member type (§8.5, §9.5) consists of the binary name of its immediately enclosing type, followed by $, followed by the simple name of the member.
・The binary name of a local class (§14.3) consists of the binary name of its immediately enclosing type, followed by $, followed by a non-empty sequence of digits, followed by the simple name of the local class.
・The binary name of an anonymous class (§15.9.5) consists of the binary name of its immediately enclosing type, followed by $, followed by a non-empty sequence of digits.
13.1. The Form of a Binary
・トップレベル型(§7.6)のバイナリ名は正規名(§6.7)である
・メンバー型(§8.5, §9.5)のバイナリ名はすぐ外側のクラスのバイナリ名に$と単純名を繋げたものである
・ローカルクラスのバイナリ名はすぐ外側のクラスのバイナリ名に$とシーケンス番号と単純名を繋げたものである
・無名クラスのバイナリ名はすぐ外側のクラスのバイナリ名にシーケンス番号を繋げたものである
このバイナリ名はclassファイル名となって現れる。
nagise.sample.Hoge$Piyo
というわけでこれはバイナリ名。
Binary Name バイナリ名 JavaVM版
Java言語仕様とJavaVM仕様でバイナリ名にずれがある。
For historical reasons, the syntax of binary names that appear in class file structures differs from the syntax of binary names documented in JLS §13.1. In this internal form, the ASCII periods (.) that normally separate the identifiers which make up the binary name are replaced by ASCII forward slashes (/). The identifiers themselves must be unqualified names (§4.2.2).
4.2.1. Binary Class and Interface Names
歴史的な理由により、classファイル構造に表示されるバイナリ名の構文は、JLS§13.1に記載されているバイナリ名の構文とは異なります。この内部形式では.、通常、バイナリ名を構成する識別子を区切るASCIIのピリオド(.)は、ASCIIのフォワードスラッシュ(/)に置き換えられます。識別子自体は修飾されていない名前でなければなりません(§4.2.2)。
「歴史的な理由」についてはわからなかったが、Java1.0の時代から続く仕様なので、1995年以前のJavaの開発時に端を発する話なのだろうと思う。
nagise/sample/Hoge$Piyo
ということで、これはJavaVMでのバイナリ名。
java.lang.Class クラスの各種メソッド
java.lang.ClassクラスはJavaのクラスを表すクラスである。ClassクラスはオブジェクトのインスタンスからObject#getClass()メソッドで取得することができる。また、クラス名.class で得ることもできる。
そのClassクラスにはいくつかクラス名を取るメソッドが存在する。
以下、サンプルのためのクラス定義はだいたい以下のようなイメージで記載している。
package nagise.sample; public class Hoge { /** 内部クラス */ public class Inner {} /** ネストしたクラス */ public static class Piyo { /** 二重にネストしたクラス */ public static class Foo {} } public static void main(String[] args) { // ローカルクラス class Local {} } static void xxx() { // 別メソッドに定義されたローカルクラス class LocalX {} } }
getName() と getTypeName() はほとんど同じ。内部実装的にgetTypeName()はgetName()を呼んでいる。ただし、配列の場合の挙動は異なりgetName()の場合は
[Lnagise.sample.Hoge.Piyo
という出力になる。これがgetTypeName()の場合は
nagise.sample.Hoge$Piyo[]
となる。このふたつの違いは配列の場合だけ。
では、それ以外の場合を見ていこう。
対象となるclass | getSimpleName() | getName() | getCanonicalName() |
---|---|---|---|
nagise.sample.Hoge型 | Hoge | nagise.sample.Hoge | nagise.sample.Hoge |
interface IHoge型 | IHoge | nagise.sample.IHoge | nagise.sample.IHoge |
Hoge型配列 | Hoge[] | [Lnagise.sample.Hoge | nagise.sample.Hoge[] |
Hoge型配列の配列 | Hoge[][] | [[Lnagise.sample.Hoge | nagise.sample.Hoge[][] |
Hoge型の内部クラスInner型 | Inner | nagise.sample.Hoge$Inner | nagise.sample.Hoge.Inner |
Hoge型のネストしたクラスPiyo型 | Piyo | nagise.sample.Hoge$Piyo | nagise.sample.Hoge.Piyo |
Piyo型のネストしたクラスFoo型 | Foo | nagise.sample.Hoge$Piyo$Foo | nagise.sample.Hoge.Piyo.Foo |
ローカルクラスLocal型 | Local | nagise.sample.Hoge$1Local | null |
別メソッドのローカルクラスLocalX型 | LocalX | nagise.sample.Hoge$2LocalX | null |
無名クラス | 空文字 | nagise.sample.Hoge$1 | null |
Integer.TYPE | int | int | int |
先にバイナリ名のところに記載したルールに準じている。
ローカルクラスの場合は、シーケンス番号は定義されるメソッドが異なる場合にシーケンス番号が異なることが確認できた。無名クラスも同様。
ローカルクラスや無名クラスではgetCanonicalName()では定義がとれずにnullが帰ることもポイント。javadocには
存在する場合は基本となるクラスの正規名。そうでない場合はnull。
https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Class.html#getTypeName()
と書かれている。まさに存在しない場合が該当ケースということだろう。
ラムダ式については挙動の根拠になる言語仕様はわからなかった。スクロールしてしまって邪魔なので分離して記載。
対象となるclass | getSimpleName() | getName() | getCanonicalName() |
---|---|---|---|
ラムダ式 | Hoge$$Lambda$1/0x0000000800062040 | nagise.sample.Hoge$$Lambda$1/0x0000000800062040 | nagise.sample.Hoge$$Lambda$1/0x0000000800062040 |
ラムダ式は無名クラスの単なる糖衣構文ではない。生成されるバイトコードが異なる。状態をもつラムダ式とそうではないものでも生成されるバイトコードは変わってくる。本稿ではラムダ式については深入りしない。参考文献として Lambda 式に invokedynamic を使うのかもしれない話 - 映画は中劇 を挙げるにとどめておく。