Eclipseプラグイン「JavadocDecorator」を作ったよ
いまさらEclipse?
InteliJ IDEAが人気でEclipse使いが減ってきてる印象もありますが、SIerのお堅い仕事の現場では全然現役のEclipse。
そんなお堅い現場だと、クラス名やメソッド名が「業務を識別する接頭辞+連番」だったりして、名前から内容が推測できず、ストレスフルな開発を強いられたりすることもある(実際に見かけたことはあるけど、運良く、自分が直接関わったことはない)。
でもまあ、名前から内容が推測できないという点では、英語で命名されてても英語が苦手な人にとっては結構しんどいんだよね。あと、ローマ字の母音を省略したやつとかも慣れてない人には辛い。
で、内容がわからないからとりあえずソースコード開いてJavadocをチラ見してすぐ閉じる。ってことを無意識にまあまあ頻繁にやってる(はず)。
そこで、そんなストレスを少しでも軽減できればと思いEclipseのプラグイン「JavadocDecorator」を作ってみた。
何ができるプラグインなの?
パッケージエクスプローラなどでツリー表示されてるJava要素(クラスとかメソッドとか)の横っちょに、その要素のJavadocの1行目を表示します。
以上、全機能。
地味。ちょー地味。
でも便利。
英語がわからなくても、
母音省略された暗号的ローマ字でも、
管理都合の名前でも、
ほら!一目瞭然!
って、こんな適当にこさえたサンプルだけだとイメージしづらいところもあると思うし、何よりも適切に命名されたソースコードでもいい感じになるよというアピールのため、ちょっと規模のあるものをgithubから探して表示してみました。
※こちらのリポジトリのソースコードを表示例にお借りしました。
GitHub - jkazama/ddd-java: Spring Boot + Java [ DDD Sample ]
ぜひ使ってみてください。
インストール方法
プラグインのインストール方法はいくつかありますが、代表的な簡単な方法を書きます。
Eclipse Marketplace からのインストール
下のボタンをEclipseのワークベンチにドラッグ&ドロップしてください。
更新サイトからのインストール
[ヘルプ]メニューの[新規ソフトウェアのインストール...]を選択し、次のURLを入力してください。
https://imkrmgn.github.io/JavadocDecorator
※ Eclipse Marketplace からもインストール出来るようにしたくて登録したつもりなんだけど、承認メッセージが届かない。地味すぎると却下とかあるのかな。。。
⇒ 半日ぐらいでメールが届いて登録されました。
参考
テスト時にプロパティファイルを動的に差し替える
はじめに
この記事は、Java8+JUnit4を使っています。
プロパティファイルを動的に差し替えたい
JUnitを使ってテストしていると、テスト対象のクラス内部でプロパティファイルを読み込んでいると、そのプロパティファイルをテストケース毎に差し替えたいことがある。
具体的にはこんな感じの実装になってるやつ。
public class Hoge{ public String example() { Properties prop = new Properties(); prop.load(this.getClass().getResourceAsStream("hoge.properties")); // 以降、propを使った処理が続く } }
hoge.properties
の内容によってexample
メソッドの返却値が変わるので、このClass#getResourceAsStream
で返却される内容を差し替えたいんだけど、外から差し替えるポイントがない。
Mavenを使ってる環境なら、src/test/resources
配下にテスト用のhoge.properties
を置いとくこともできるけど、それではバリエーションを持たせたテストが出来ない(いや、target/test-classes
配下をテストケースから書き換えれば出来なくもないか?出来たとしてもあまり選択したくない方法だな)。
本来、設計(実装)から見直して欲しいところだけれども、諸事情によりテスト対象のクラスを修正するわけにもいかず、そのままテストせざるを得ない。ライブラリの内部で間接的に使われてるときとかもそう。
今回もそんなケースだったので、プロパティファイルを動的に差し替えるためにクラスローダーでゴニョゴニョした記録を残しておく。なお、良い子はマネしちゃいけません。
クラスローダーを作る
プロパティファイルというか、クラスパス上のリソースを読み込む処理というのは、多くの場合、根本を辿るとClassLoader#getResource
で得たURLオブジェクトを通してリソースを読み込むという動作になる(ここは説明が面倒なので省略)。したがって、自分にとって都合のよいURLオブジェクトがClassLoader#getResource
から返却されるような仕掛けを用意すればよい。
ということで、テスト用のクラスローダーを作成。
以下、ポイントを絞ってコードを抜粋。
public class TestClassLoader extends URLClassLoader { private static Map<String, URL> testResourceMap = new HashMap<>(); /** * リソース名とテストリソースのマッピングを追加する * @param resourceName テストリソース名 * @param testResourceURL テストリソースを読み込むためのURL */ public static void addTestResourceMap(String resourceName, URL testResourceURL) { testResourceMap.put(resourceName, testResourceURL); } /** * テストリソースのマッピングをクリアする */ public static void clearTestResourceMap() { testResourceMap.clear(); } //省略 /** * @param name リソース名 * @return リソース名にテストリソースがマッピングされていたら、そのリソースを読み込むためのURL。 * マッピングされていない場合は、デフォルトの動作で得られたURL。 */ @Override public URL getResource(String name) { if (testResourceMap.containsKey(name)) { return testResourceMap.get(name); } else { return super.getResource(name); } } }
ClassLoader#getResouce
をオーバーライドして、引数のリソース名がtestResourceMap
のキーとして存在したら、そのリソース名にマッピングされたURLオブジェクトを返却している。testResourceMap
には、addTestResouceMap
というstaticメソッドを通して、リソース名とURLオブジェクトのマッピングを追加できるようにした。clearTestResourceMap
は、addTestResourceMap
で追加したマッピングを全てクリアする。
このクラスローダーがあることを前提に、JUnitのテストケースが次のように実装できる。
@RunWith(TestRunner.class) public class HogeTest { @Before public void setUp() { TestClassLoader.clearTestResourceMap(); } @Test public void testHoge01() throws IOException { TestClassLoader.addTestResourceMap( "hoge.properties", Paths.get("testdata/hoge_test01.properties").toUri().toURL()); Hoge hoge = new Hoge(); String actual = hoge.exampale(); //... } @Test public void testHoge02() throws IOException { TestClassLoader.addTestResourceMap( "hoge.properties", Paths.get("testdata/hoge_test02.properties").toUri().toURL()); Hoge hoge = new Hoge(); String actual = hoge.example(); //... } }
各テストメソッドの先頭でTestClassLoader#addTestResourceMap
を使ってリソース名とリソース(URLオブジェクト)のマッピングを行う。これにより、テスト対象メソッド内部のhoge.properties
の読み込みは、マッピングしたリソースへの読み込みに置き換えられる。なお、マッピングはTestClassLoader
のstatic変数に保持しているので、@Before
を付けたsetUp
メソッドにてマッピングをクリアしている。
以上でプロパティファイルを動的に差し替えることができました。
おわり。
って、説明が足りてないね。
この仕掛けを成立させるには、テスト対象のクラスHoge
をテスト用クラスローダーTestClassLoader
にロードさせる必要がある。
自作のクラスローダーを使うときは、javaコマンド実行時にシステムプロパティ「java.system.class.loader」で指定すれば使えるんだけど、この場合、自作クラスローダーがシステムクラスローダーとして動作するだけの実装が必要で、ハードルが高い。また、JUnit実行するときにシステムプロパティを指定するのも手間。
そこで、JUnitが動く仕掛けのなかで自作クラスローダーを差し込むポイントが無いか探った。
テストランナーを作る
JUnitを拡張するならまずは@Rule
、@ClassRule
だろうと探ってみたけどクラスローダーを差し込むポイントは見つけられなかった。じゃあテストランナー作るしかないなとJUnit4のデフォルトのテストランナーのコードを見たら以外とあっさり見つかった。
で、作ったテストランナーの実装はこれ。
public class TestRunner extends BlockJUnit4ClassRunner { private static TestClassLoader testClassLoader = new TestClassLoader(); public TestRunner(Class<?> testClass) throws InitializationError, ClassNotFoundException { super(testClassLoader.loadClass(testClass.getName())); } }
テストランナーのコンストラクタにテストケースのクラス(今回の例だとHogeTest
)がJUnitCore
から引き渡される。テストランナーは、このテストケースのクラスのインスタンスをリフレクションで生成して、各テストメソッドを実行している。そこで、このコンストラクタに引き渡されたテストケースのクラスを自作クラスローダーでロードし直して、それが以降の処理で使われるようにした。
あとは、@RunWith
でこのテストランナーを使うように指定して完成。
@RunWith(TestRunner.class) public class HogeTest { //... }
おわりに
クラスローダー周りは面倒なので説明を端折っちゃった。
全コードをgithubに上げておいたので興味のある人はどうぞ。もし、誰かの役に立てばなによりです。
なお、テスト対象がClassLoader#getSystemResouce
、ClassLoader#getSystemResouceAsStream
を使っている場合、この記事の方式では差し替えられません。webアプリの場合、これらのメソッドを使っていることはまあ無いと思うので大丈夫だと思いますが。
オブジェクトの配列の初期化
思い出した
確かJava8になって簡潔に書けるようになってたよなーって思いつつ、結局、思い出せず手癖でループで初期化してたこんな感じのコード。
Object[] array = new Object[10]; for (int i = 0; i < array.length; i++) { array[i] = new Object(); }
Java8以降ではこう書ける。
Object[] array = new Object[10]; Arrays.setAll(array, i -> new Object());
わーい。すっきり。
おまけ
この手の奴はいつも性能が気になっちゃうので測定結果。
cpu=8 useFor : 54 70 35 37 97 38 36 37 36 95 : avg=53.50 useSetAll : 36 36 37 40 36 58 98 75 35 35 : avg=48.60 useParallelSetAll : 241 239 238 238 237 249 236 236 235 236 : avg=238.50
測定したときのソース。
import java.util.Arrays; import java.util.LongSummaryStatistics; public class Test { static final int NUMER_OF_MEASURE = 10; static final int NUMBER_OF_RUN = 10000; static final int ARRAY_LENGTH = 1000; public static void main(String[] args) { int cpuNum = Runtime.getRuntime().availableProcessors(); System.out.printf("cpu=%d%n", cpuNum); report("useFor", Test::useFor); report("useSetAll", Test::useSetAll); report("useParallelSetAll", Test::useParallelSetAll); } static void report(String name, Runnable runner) { measure(runner); // 空回し LongSummaryStatistics statics = new LongSummaryStatistics(); System.out.printf("%-20s:", name); for (int i = 0; i < NUMER_OF_MEASURE; i++) { long time = measure(runner); statics.accept(time); System.out.printf("%,5d", time); } System.out.printf(" : avg=%,5.2f%n", statics.getAverage()); } static long measure(Runnable runner) { long t = System.currentTimeMillis(); for (int i = 0; i < NUMBER_OF_RUN; i++) { runner.run(); } return System.currentTimeMillis() - t; } static void useFor() { Object[] array = new Object[ARRAY_LENGTH]; for (int i = 0; i < array.length; i++) { array[i] = new Object(); } } static void useSetAll() { Object[] array = new Object[ARRAY_LENGTH]; Arrays.setAll(array, i -> new Object()); } static void useParallelSetAll() { Object[] array = new Object[ARRAY_LENGTH]; Arrays.parallelSetAll(array, i -> new Object()); } }
DateTimeFormatterで可変幅のミリ秒の解析
おさらい
前回の記事の続きです。
ミリ秒の解析のためにDateTimeFormatter
を使ってパターン文字"S"を1文字で指定したら、
解析対象のミリ秒が2桁以上あると解析に失敗した。
// NGな例 DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.S"); LocalDateTime ldt = dtf.parse("2018-08-12T12:34:56.12", LocalDateTime::from); System.out.println(ldt); // -> java.time.format.DateTimeParseException: Text '2018-08-12T12:34:56.12' could not be parsed, unparsed text found at index 21
原因
日付や時、分、秒はパターン文字を1文字だけ書けば複数桁の解析ができるのになぜ?
と調べてDateTimeFormatterBuilder#appendPattern(String)
のJavadocに次の記述を見つけた。
パターン文字の数によってフォーマットが決まります。ユーザーに焦点を合わせたパターンの説明については、DateTimeFormatterを参照してください。次の表は、パターン文字がどのようにビルダーにマップされるかを定義しています。
Pattern Count Equivalent builder methods H 1 appendValue(ChronoField.HOUR_OF_DAY) HH 2 appendValue(ChronoField.HOUR_OF_DAY, 2) m 1 appendValue(ChronoField.MINUTE_OF_HOUR) mm 2 appendValue(ChronoField.MINUTE_OF_HOUR, 2) s 1 appendValue(ChronoField.SECOND_OF_MINUTE) ss 2 appendValue(ChronoField.SECOND_OF_MINUTE, 2) S..S 1..n appendFraction(ChronoField.NANO_OF_SECOND, n, n, false)
※表は抜粋
この表を見るとappendPattern
に指定したパターン文字"S"は、内部でappendFraction
の呼び出しに置き換えられていて、最小幅と最大幅が"S"を書いた文字数(n)に指定されている。
だから、パターン文字"S"を1文字だけ書いて解析対象のミリ秒が1文字でない場合は解析に失敗したのか。
一方、時(H)、分(m)、秒(s)などを1文字だけ書いた場合appendValue
が内部で使用され、このメソッドは可変幅の文字も受け付けるので、解析対象が1桁でも複数桁でも解析に失敗しない。
なぜ、ミリ秒だけこんな仕様なんだ?他のフィールドと同じように1文字だけなら可変幅にすればいいじゃないかと疑問に感じたが、 冷静に考えると、解析対象としてミリ秒以外のフィールドを1文字幅に制限するということは通常考えられないが、ミリ秒(秒の小数部)は1文字幅に制限したいという可能性はある。 その可能性を考慮すると、この仕様は確かに妥当かと思い直した。
対策
とりあえず仕様に納得はしたものの困ったことには変わりない。
今やりたいことは「ミリ秒は可変幅で、それ以外のフィールドは固定幅で日時を解析」である。
みんなどうやってるのかググったところ、次のようにやってる人が多そう。
DateTimeFormatterBuilder dtfBldr = new DateTimeFormatterBuilder(); DateTimeFormatter dtf = dtfBldr .appendPattern("yyyy-MM-dd'T'HH:mm:ss") .appendFraction(ChronoField.NANO_OF_SECOND, 0, 3, true) .toFormatter(); LocalDateTime ldt = dtf.parse("2018-08-01T12:34:56.12", LocalDateTime::from); System.out.println(ldt); // -> 2018-08-01T12:34:56.120
うーん。ちょっとめんどい。
めんどいし、解析対象が小数点で終わっている場合に失敗してくれないのがちょっと嫌だ。
もうちょっとスマートにできないかと調べたら Stack Overflow で次のようにやってるのを見つけた。
DateTimeFormatter dtf = DateTimeFormatter .ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS][.SS][.S]"); LocalDateTime ldt = dtf.parse("2018-08-01T12:34:56.12", LocalDateTime::from); System.out.println(ldt); // -> 2018-08-01T12:34:56.120
なるほど。オプションで長い幅からマッチングさせてるのか。頭いい。
小数部の桁数が多いときは先の方法でやったほうがいいけど、3桁ぐらいならこっちのほうがいいな。 これなら小数点で終わっている場合に解析失敗してくれるし。
とりあえず目先の用途としてはこれでいいか。
SimpleDateFormatのミリ秒
はじめに
この記事で使用してるのはJava8です。
ミリ秒の結果がおかしい
SimpleDateFormat
でパースしたら、ミリ秒が期待した結果にならなかった。
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); Date date = sdf.parse("2018-08-12T12:34:56.12"); System.out.println(date.toInstant()); // -> 2018-08-12T12:34:56.012Z
ミリ秒が.012
で出力されてる。
いやいや。なんで1桁ずれてるのよ。
試しにもう1桁削ってみる。
Date date = sdf.parse("2018-08-12T12:34:56.1"); System.out.println(date.toInstant()); // -> 2018-08-12T12:34:56.001Z
うふふ。2桁ずれた。
逆に増やしてみる。
Date date = sdf.parse("2018-08-12T12:34:56.1234"); System.out.println(date.toInstant()); // -> 2018-08-12T12:34:57.234Z
1秒増えた(^^;
そっか。パターン文字"S"って、Javadocに書いてあるとおり「ミリ秒」なんだ。 「秒の小数部」じゃないのね。 いつも、桁数固定でパースしてたから今まで気付かなかった。
DateTimeFormatterだとどうなるの?
DateTimeFormatterのJavadocでは、パターン文字"S"は「fraction-of-second」って書いてあるから、こっちは期待した結果になりそう。
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); LocalDateTime ldt = dtf.parse("2018-08-12T12:34:56.12", LocalDateTime::from); System.out.println(ldt); // -> java.time.format.DateTimeParseException: Text '2018-08-12T12:34:56.12' could not be parsed at index 20
こけたorz
ああ。SimpleDateFormat
と違って桁数厳密なのか。
桁数をあわせて再実行。
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SS"); // Sを1文字削って入力の桁数とあわせた LocalDateTime ldt = dtf.parse("2018-08-12T12:34:56.12", LocalDateTime::from); System.out.println(ldt); // -> 2018-08-12T12:34:56.120
うん。期待した通りの結果。
しかし、私がパースしたい文字列の小数部の長さは可変なので、パターン文字"S"を1文字だけに修正。
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.S"); // Sを1文字だけに修正 LocalDateTime ldt = dtf.parse("2018-08-12T12:34:56.12", LocalDateTime::from); System.out.println(ldt); // -> java.time.format.DateTimeParseException: Text '2018-08-12T12:34:56.12' could not be parsed, unparsed text found at index 21
こけた...。
あれ?たしか可変長パースできたよな...。と次のとおり実行してみる。
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("y-M-d'T'H:m:s.SS"); // S以外のパース文字を1文字に修正 LocalDateTime ldt = dtf.parse("2018-08-12T12:34:56.12", LocalDateTime::from); System.out.println(ldt)); // -> 2018-08-12T12:34:56.120
うん。問題ない。
もう一度、ここからパターン文字"S"を1文字だけに修正して実行。
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("y-M-d'T'H:m:s.S"); // Sも1文字に修正 LocalDateTime ldt = dtf.parse("2018-08-12T12:34:56.12", LocalDateTime::from); System.out.println(ldt); // -> java.time.format.DateTimeParseException: Text '2018-08-12T12:34:56.12' could not be parsed, unparsed text found at index 21
!?
ここでJavadocを再確認して、色々、試してもわけがわからず時間をだいぶ潰す。。。
で、googleさんに聞いてみたらこんな記事が見つかった。
事象はちょっと違うけど、たぶんこれだな。 Java9で修正されてるっぽいのでJava9で実行してみたけど症状変わらず。 Java10ならどうよ?と、Java10をダウンロードして試しても症状変わらず。。。
え?バグじゃないの?もしかして仕様なの?
もう、眠いから明日調べてみよ。。。
スタンドアロンでJDTのASTを使う
JDTのASTをスタンドアロンで使いたい場合は、
Eclipseのプラグインが格納されているフォルダにある、
以下のjarを拾ってきてクラスパスを通せば使える
(アスタリスク(*)の部分は、実際にはバージョン番号とかになってる)。 Mavenのセントラルリポジトリにも登録されてるので、そこから拾うのが簡単かな。 pom.xmlはこんな感じ。 以下、ソースの断片を説明。 解析時に使用されるコンパイラオプションを引数に指定している。
注意点として、 あと、 返却された 引数に ソースファイルの中身を丸っと取得するコード。 (2017/1/10 追記) もっと詳しい情報があった。。。
qiita.com必要なライブラリ
<dependencies>
<dependency>
<groupId>org.eclipse.core</groupId>
<artifactId>org.eclipse.core.contenttype</artifactId>
<version>3.4.100.v20100505-1235</version>
</dependency>
<dependency>
<groupId>org.eclipse.core</groupId>
<artifactId>org.eclipse.core.jobs</artifactId>
<version>3.5.100</version>
</dependency>
<dependency>
<groupId>org.eclipse.birt.runtime</groupId>
<artifactId>org.eclipse.core.resources</artifactId>
<version>3.10.0.v20150423-0755</version>
</dependency>
<dependency>
<groupId>org.eclipse.core</groupId>
<artifactId>org.eclipse.core.runtime</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>org.eclipse.equinox.common</artifactId>
<version>3.6.0.v20100503</version>
</dependency>
<dependency>
<groupId>org.eclipse.equinox</groupId>
<artifactId>org.eclipse.equinox.preferences</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.10.0</version>
</dependency>
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>org.eclipse.osgi</artifactId>
<version>3.8.0.v20120529-1548</version>
</dependency>
</dependencies>
基本的な使い方
ASTParser#createAST
を使ってASTを作成。
返却されたASTNode
のaccept
メソッドにASTVisitor
を継承した自作クラスの
インスタンスを引き渡してツリーを走査する(Visitorパターン)。 String source = ...; // 解析するJavaソースの文字列
ASTParser parser = ASTParser.newParser( AST.JLS8 );
Map<?, ?> options = JavaCore.getOptions();
JavaCore.setComplianceOptions( JavaCore.VERSION_1_8, options );
parser.setCompilerOptions( options );
parser.setSource( source.toCharArray() );
ASTNode ast = parser.createAST( null );
ast.accept( new ASTVisitor() { ... } );
ASTParser#newParser
ASTParser parser = ASTParser.newParser( AST.JLS8 );
ASTParser
のインスタンスを作成するときにJavaのAPIレベルを指定しないといけないんだけど、
AST.JLS8
が全バージョンに対応してるので、 固定でAST.JLS8
を指定すればOK。
というか他が全部deprecatedになってるので選択の余地はない。ASTParser#setCompilerOptions
Map<?, ?> options = JavaCore.getOptions();
JavaCore.setComplianceOptions( JavaCore.VERSION_1_8, options );
parser.setCompilerOptions( options );
ASTParser#setSource(char[])
を使って1.5以降のソースを解析する場合、
このメソッドを使って適切なコンパイラオプションを指定する必要がある。ASTParser#setSource
にIClassFile
およびICompilationUnit
を引き渡した場合、
設定したコンパイラオプションがリセットされるらしい。options
の中身を見るとソースのフォーマットに関するオプションも入ってた。
ASTでソースを改変することがあったら、細かく調べてみようかな。ASTParser#createAST
parser.setSource( source.toCharArray() );
ASTNode ast = parser.createAST( null );
ASTParser#setSource
で設定したソースを解析してASTを構築して返却してくれる。
あとは、返却されたASTNode
のインスタンスから各ノードを辿って自分の欲しい情報を
取得する。ASTNode
はデフォルトだとCompilationUnit
のインスタンスみたい。
ASTParser#setKind
で変えることができるようだけど、必要になったときに調べる。IProgressMonitor
のインスタンスを渡せば、解析の進捗状況を確認したり、
途中でキャンセルできるみたい。必要ない場合はnull
でOK。おまけ
String source = new String(
Files.readAllBytes( Paths.get( "Hoge.java" ) ), "utf-8" );
参考
ITypeBindingから取得できる名前
JDTのASTを弄るときに、ITypeBindingから取得できる名前がどの形式で取得できたか毎度忘れちゃうのでメモ。
ITypeBinding#getQualifiedName
わかってるつもりだったんだけど、時々、思ったような結果が得られなくて混乱したので、Javadocを見ながら挙動を確認する。
Returns the fully qualified name of the type represented by this binding if it has one.
このバインディングが持つ型の完全修飾名を返します。
For top-level types, the fully qualified name is the simple name of the type preceded by the package name (or unqualified if in a default package) and a ".". Example: "java.lang.String" or "java.util.Collection". Note that the type parameters of a generic type are not included.
最上位タイプの場合、完全修飾名は、パッケージ名の前にあるタイプの単純名です(デフォルトパッケージの場合は修飾されません)。例: "java.lang.String"または "java.util.Collection"。ジェネリック型の型パラメータは含まれていないことに注意してください。
TypeDeclaration
での取得例(行コメントの右側)。参考までにITypeBinding#getName
の取得結果もあわせて記載(行コメントの左側)。
package sample; class TopLevelClass1 {} // -> "TopLevelClass1", "sample.TopLevelClass1" class TopLevelClass2<T> {} // -> "TopLevelClass2", "sample.TopLevelClass2"
For members of top-level types, the fully qualified name is the simple name of the type preceded by the fully qualified name of the enclosing type (as computed by this method) and a ".". Example: "java.io.ObjectInputStream.GetField". If the binding is for a member type that corresponds to a particular instance of a generic type arising from a parameterized type reference, the simple name of the type is followed by the fully qualified names of the type arguments (as computed by this method) surrounded by "<>" and separated by ",". Example: "pkg.Outer.Inner<java.lang.String>".
トップレベル型のメンバの場合、完全修飾名は、(このメソッドで計算された)囲み型の完全修飾名と "。"で始まる型の単純名です。例: "java.io.ObjectInputStream.GetField"。パラメータ化された型参照から生じるジェネリック型の特定のインスタンスに対応するメンバ型のバインディングの場合、その型の単純名の後に(このメソッドによって計算される)型引数の完全修飾名が囲まれます。 "<>"で区切られ、 "、"で区切られます。例: "pkg.Outer.Inner <java.lang.String>"。
TypeDeclaration
での取得例。ネストしたクラスおよびインナークラスにおいてもジェネリクスのパラメタ型が含まれていないことが確認できる。
package sample; class TopLevelClass { static class NestedClass1 {} // -> "NestedClass1", "sample.TopLevelClass.NestedClass1" static class NestedClass2<T> {} // -> "NestedClass2", "sample.TopLevelClass.NestedClass2" static class NestedClass3<T extends Object> {} // -> "NestedClass3", "sample.TopLevelClass.NestedClass3" class InnerClass1{} // -> "InnerClass1", "sample.TopLevelClass.InnerClass1" class InnerClass2<T>{} // -> "InnerClass2", "sample.TopLevelClass.InnerClass2" class InnerClass3<T extends Object>{} // -> "InnerClass3", "sample.TopLevelClass.InnerClass3" }
FieldDeclaration
およびMethodDeclaration#getReturnType2
での取得例。ジェネリクスの型引数も完全修飾名が付いた状態で取得できるのが確認できる。
package sample; class TopLevelClass { String field1; // -> "String", "java.lang.String" List field2; // -> "List", "java.util.List" List<?> field3; // -> "List<?>", "java.util.List<?>" List<String> field4; // -> "List<String>", "java.util.List<java.lang.String>" String method1() { return null; } // -> "String", "java.lang.String" List method2() { return null; } // -> "List", "java.util.List" List<?> method3() { return null; } // -> "List<?>", "java.util.List<?>" List<String> method4() { return null; } // -> "List<String>", "java.util.List<java.lang.String>" }
For primitive types, the fully qualified name is the keyword for the primitive type. Example: "int".
プリミティブ型の場合、完全修飾名はプリミティブ型のキーワードです。例: "int"
For the null type, the fully qualified name is the string "null".
null型の場合、完全修飾名は文字列 "null"です。
書いてあるとおり。わかりやすい。
Local types (including anonymous classes) and members of local types do not have a fully qualified name. For these types, and array types thereof, this method returns an empty string.
ローカル・タイプ(匿名クラスを含む)およびローカル・タイプのメンバーには、完全修飾名はありません。これらの型およびその配列型の場合、このメソッドは空の文字列を返します。
う~ん?配列になってるfield2
とvar2
の名前に一貫性なくない?var2
が"[]"を返却してるのって、Javadocの説明とあってないのでは?
package sample; class TopLevelClass { void method() { class LocalClass { // -> "LocalClass", "" LocalLocalClass field1; // -> "LocalLocalClass", "" LocalLocalClass[] field2; // -> "LocalLocalClass[]", "[]"(?) class LocalLocalClass {} // -> "LocalLocalClass", "" } LocalClass var1; // -> "LocalClass", "" LocalClass[] var2; // -> ""(?), "" new TopLevelClass() {}; // -> "", "" } }
For array types whose component type has a fully qualified name, the fully qualified name is the fully qualified name of the component type (as computed by this method) followed by "". Example: "java.lang.String".
コンポーネント型が完全修飾名を持つ配列型の場合、完全修飾名は([このメソッドで計算される])コンポーネント型の完全修飾名で、その後に""が続きます。例: "java.lang.String "。
上で試したケースで足りてるかな。コンポーネントってなんのこと?
For type variables, the fully qualified name is just the name of the type variable (type bounds are not included). Example: "X".
型変数の場合、完全修飾名は型変数の名前にすぎません(型境界は含まれません)。例: "X"
書いてあるとおり。わかりやすい。
For type bindings that correspond to particular instances of a generic type arising from a raw type reference, the fully qualified name is the fully qualified name of the erasure type. Example: "java.util.Collection". Note that the the type parameters are omitted.
raw type参照から生じるジェネリック型の特定のインスタンスに対応する型バインディングの場合、完全修飾名は消去型の完全修飾名です。例: "java.util.Collection"。型パラメータは省略されていることに注意してください。
これも上で試したケースで足りてるかな。List var;
だと"java.util.List"
ってことよね?
For type bindings that correspond to particular instances of a generic type arising from a parameterized type reference, the fully qualified name is the fully qualified name of the erasure type followed by the fully qualified names of the type arguments surrounded by "<>" and separated by ",". Example: "java.util.Collection<java.lang.String>".
パラメータ化された型参照から生じるジェネリック型の特定のインスタンスに対応する型バインディングの場合、完全修飾名は、イレージャ型の完全修飾名、続いて "<>"で囲まれた型引数の完全修飾名"、"によって。例: "java.util.Collection <java.lang.String>"。
これも上で試したケースで足りてる。List<String> var;
だと"java.util.List<java.lang.String"
。
For wildcard types, the fully qualified name is "?" optionally followed by a single space followed by the keyword "extends" or "super" followed a single space followed by the fully qualified name of the bound (as computed by this method) when present. Example: "? extends java.io.InputStream".
ワイルドカードタイプの場合、完全修飾名は "?"です。オプションで、単一のスペースの後にキーワード "extends"または "super"が続き、単一のスペースの後に(このメソッドで計算された)完全修飾名が続きます。例: "?extends java.io.InputStream"。
ジェネリクス関連をまとめてお試し。
class TopLevelClass { List var1; // -> "List", "java.lang.List" List<Number> var2; // -> "List<Number>", "java.util.List<java.lang.Number" List<?> var3; // -> "List<?>", "java.util.List<?>" List<? extends Number> var4; // -> "List<? extends Number>", "java.util.List<? extends java.lang.Number>" List<? super Number> var5; // -> "List<? super Number>", "java.util.List<? super java.lang.Number>" }
Capture types do not have a fully qualified name. For these types, and array types thereof, this method returns an empty string.
キャプチャー・タイプには完全修飾名はありません。これらの型およびその配列型の場合、このメソッドは空の文字列を返します。
キャプチャ・タイプって、下の場合のadd
の引数の型のことでいいのかな?確かに空文字列が返却される。
class TopLevelClass { void method(List<?> lst) { lst.add( null ); // -> "", "" } }
ITypeBinding#getBinaryName
Returns the binary name of this type binding. The binary name of a class is defined in the Java Language Specification 3rd edition, section 13.1. Note that in some cases, the binary name may be unavailable. This may happen, for example, for a local type declared in unreachable code.
この型バインディングのバイナリ名を返します。クラスのバイナリ名は、Java言語仕様第3版13.1節で定義されています。 場合によっては、バイナリ名が使用できないことがあります。これは、たとえば、到達不能なコードで宣言されたローカルタイプに対して発生します。
デバッガで見たときに確認できる名前。
基本的にITypeBinding#getQualifiedName
と同じ文字列になるので、異なる文字列になったものだけ下に掲載。
package sample; class TopLevelClass { static class NestedClass1 {} // -> "sample.TopLevelClass$NestedClass1" static class NestedClass2<T> {} // -> "sample.TopLevelClass$NestedClass2" static class NestedClass3<T extends Object> {} // -> "sample.TopLevelClass$NestedClass3" class InnerClass1{} // -> "sample.TopLevelClass$InnerClass1" class InnerClass2<T>{} // -> "sample.TopLevelClass$InnerClass2" class InnerClass3<T extends Object>{} // -> "sample.TopLevelClass$InnerClass3" }
class TopLevelClass { List<?> field3; // -> "java.util.List" List<String> field4; // -> "java.util.List" List<?> method3() { return null; } // -> "java.util.List" List<String> method4() { return null; } // -> "java.util.List" }
class TopLevelClass { void method() { class LocalClass { // -> "sample.TopLevelClass$1LocalClass" LocalLocalClass field1; // -> "sample.TopLevelClass$1LocalClass$LocalLocalClass" LocalLocalClass[] field2; // -> "[Lsample.TopLevelClass$1LocalClass$LocalLocalClass" class LocalLocalClass {} // -> "sample.TopLevelClass$1LocalClass$LocalLocalClass" } LocalClass var1; // -> "sample.TopLevelClass$1LocalClass" LocalClass[] var2; // -> "[Lsample.TopLevelClass$1LocalClass" new TopLevelClass() {}; // -> "sample.TopLevelClass$1" } }
class TopLevelClass { List var1; // -> "java.lang.List" List<Number> var2; // -> "java.util.List" List<?> var3; // -> "java.util.List" List<? extends Number> var4; // -> "java.util.List" List<? super Number> var5; // -> "java.util.List" }
違いとしては、
- トップレベルのクラスから下の区切り文字が"."の代わりに"$"。
- 配列の表現。
- 型パラメータの情報がなくなる。
なんだけど、キャプチャ型の場合、null
が返却された。
class TopLevelClass { void method(List<?> lst) { lst.add( null ); // -> null } }
一応、ITypeBinding#getErasure
を使えば"java.lang.Object"
は取得できた。