Javaで躓いた点,小技をまとめるメモ.
Case with braces
switchのcase内に無名のブロックを書くことで変数名の衝突を防ぐ
switch(cond) {
case val1: {
int x = 1;
} break;
case val2: {
int x = 2; // xを使いまわせる
} break;
default {
} break;
}
breakはブロック内に書いても同じように動作するが,外に出した方が好み.
Streamを使ってファイル入出力
以下のように書くとシンプルに文字列の読み書きができる
String inputFile = null, outputFile = null;
try (BufferedReader br = Files.newBufferedReader(Paths.get(inputFile), Charset.forName("UTF-8"));
PrintWriter pw = new PrintWriter(outputFile, "UTF-8")) {
// Streamでいろいろ処理
br.lines().forEach(l -> pw.println(l));
pw.flush();
} catch (IOException e) {
// ファイルが見つからなかった
throw new UncheckedIOException(e);
}
継承とLiskovの置換原則
クラスTがクラスSを継承するときs:Sを引数に取れるところはt:Tも引数に取れなきゃならないというアレ
時間があるときFamily Values: A Behavioral Notion of Subtypingを読もう
よくぶつかる問題と自分なりの対策を列挙する
equals
子クラスの実装を親クラスが知らない状況で置換可能を満たしながら同値関係(反射律,対称律,推移律)を満足するのは難しい.
基本的な対処法としては,equalsを実装する上位のクラスで,そのクラスが表現する概念に対する同値関係を注意深く定義することになる.
主な対処法
- クラスをfinalとする
- そもそも継承させない
- 実装が簡単
- 子クラスとのequalsは必ずfalse
- 継承を制限したくない
- 同じクラス間でのみ同値関係を定義(反射律による制限)
- 同値関係を判定するためのインターフェースを定義
- 正規形を返すようなメソッドを作る
- 正確には定義ではないがAbstractListのIterableを利用した実装は参考になるかも
件の論文ではTripleがPairを継承するときequalはどうするのかという問題として現れる.
矛盾が出ないようにするためにはpair用とtriple用と別のメソッドにすることになるよ.と.
例外の使い分け
メソッド呼び出しに対して発生する例外について,責任の所在によって使い分けるという提案
例外 | 責任の所在 | 例 |
---|---|---|
RuntimeException | callerのバグ,前提条件を満たさない呼出し | IllegalArgumentException, IllegalStateException |
Error | calleeのバグ,VMの問題 | AssertionError, OutOfMemoryError |
Exception | 発生を防げないが想定が必要なもの | FileNotFoundException |
FileNotFoundExceptionを自分のコードから投げることは少ないかもしれない.
- メソッドの前提条件としてファイルが存在する -> callerのバグなのでRuntimeExceptionでラップする
- calleeがファイルを用意する仕様である -> calleeのバグなのでErrorでラップする
参考:都元ダイスケさんの意見
スーパークラスでサブクラスの型に関する制約を設けるときは再帰的ジェネリクスを利用する
To be written
コンストラクタからオーバーライド可能なメソッドを呼んではならない
理由
オーバーライドされたメソッドはサブクラスのコンストラクタ実行前に呼ばれる.
コンストラクタの実行が完了する前のオブジェクトは一貫性が無い可能性がある.
事故例
class Super {
final String fullName;
final String shortName;
Super(String name) {
this.fullName = name;
shortName = getShort();
}
String getShort() {
// 表示用の短い名前
return fullName.length() > 5 ?
fullName.subString(0,5) :
fullName;
}
}
class Sub {
final int num;
Sub(String name, int num) {
super(name);
this.num = num;
}
getShort() {
// 追加した数字も含める
return super.getShort() + num;
}
}
オーバーライドしたメソッドがサブクラスで追加したフィールドを参照する場合,
そのフィールドは初期化前に参照される.
この例ではSub#getShort()
はコンストラクタのthis.num = num
より前に呼ばれるため,
Super#<init>
からnum
は0
に見える.
対処法
- コンストラクタからそのクラスのインスタンスメソッドを呼ばない
- フィールドを参照する必要がないのでクラスメソッドにできるはず
- どうしてもインスタンスメソッドが好みの場合finalで修飾する
サブクラスからフィールドの初期化を変更したい場合は以下の方法を用いる.
protectedでプライマリコンストラクタを用意する
スーパークラスに全てのフィールドを引数で指定できるプライマリコンストラクタを用意する.
可視性をprotectedとすることでサブクラス以外との疎結合を保つ.
class Super {
protected Super(String fullName, String shortName) {
this.fullName = name;
this.shortName = shortName;
}
Super(String name) {
this(name, getShort());
}
...
}
こうすることでSub#<init>
からsuper(name, getShort2())
などとして変更できる.
Super
で文字数に関する性質を保証したい場合は,プライマリコンストラクタに確認用のコードを追加すればよい.
protected Super(String fullName, String shortName) {
checkShortName(shortName);
this.fullName = name;
this.shortName = shortName;
}
private static void checkShortName(String shortName) {
if(shortName.length() > 5) {
throw new IllegalArgumentException("shortName : "+shortName);
}
}
参考
clone()内も同様に注意すること
同期
同期処理の種類
volatile修飾子
: フィールドの値をスレッド間で共有する
synchronyzedブロック
: 単一スレッドのみ進入可能なブロックを作る
java.util.concurrent.atomic
: 読み書きが同期されたクラス
java.util.concurrent.lock
: 読み込み,書き込み
大まかな使い分け
-
書き込みスレッドが1つ → volatile
-
書き込みスレッドが複数
- 単体の値の同期 → java.util.concurrent.atomic
- コレクションの同期 → java.util.Collections#concurrent* で同期するか並列処理用のコレクションを利用
- 複数の変数や条件
- synchronyzed
- java.util.concurrent.lock
-
メソッドの同期
シングルトン Singleton
あるクラスのインスタンスが1つしか存在しないようにして,また外部からアクセスできるようにする.
インスタンスを複数生成させないために留意すべき点は以下の通り
- newさせない
- コンストラクタはprivateに
- インスタンスはファクトリメソッドなどを利用して取得
- 遅延初期化したい
- 複数スレッドからのアクセスを想定
- デシリアライズさせない
- readResolveを利用
- クローンさせない
以上を守れば普通は問題ないが,よりセキュアにするために以下のような観点もあるようだ
- GCに回収させない(他のClassLoaderを利用させない)
- リフレクションで作成させない
がんばって(そしてテクニカルに)実装する
間違えやすいので後述のenumを使ったほうが良い.
この例は機能をテンコ盛りだが,必要な部分だけ実装すればOK
class Singleton extends CloneableClass implements Serializable {
// フィールドは全てtransientに
private transient String field;
// 外部のクラスからコンストラクタにアクセスさせない
private Singleton() {
// 初期化
}
// Initialize-on-Demand Holderパターンによる遅延初期化クラス
private static class SingletonHolder {
static Singleton singleton = new Singleton();
Singleton getInstance() {
return singleton;
}
}
// ファクトリメソッド
public static Singleton getInstance() {
return SingletonHolder.getInstance();
}
// デシリアライズの結果を上書き
private Object readResolve() throws ObjectStreamException {
return getInstance();
}
// クローンさせない
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
enumを利用
enumを利用すると(特にserialize周りで)面倒なことをVMに任せられる
enum Singleton { // enumは既にSerializable
INSTANCE;
// transientにしなくてよい
private String field;
}
ただし,この方法はインターフェースは実装できるがクラスを継承できない.
バージョン Version
コンパイルと実行に必要なJavaのバージョン
VM | Class file | 追加主要機能 |
---|---|---|
1.4 | 48.0 | アサーション, nio |
1.5 | 49.0 | 総称型, 拡張for文, アノテーション, enum, 可変長引数 |
1.6 | 50.0 | 主にbug-fix, OutOfMemoryErrorの捕捉, Fileの権限設定など |
1.7 | 51.0 | try-with-resources, multicatch, strings-in-switch |
1.8 | 52.0 | ラムダ式 |
1.9 | 53.0 | Jigsaw, Kulla(REPL), Reactive Stream |
18.3 | 54.0 | 局所変数型推論, Time-Based Release Versioning |
バイナリ名(binary name)
クラスファイル中に記述してある名前
最上位型
: 正準名と同じ
メンバ型
: <ひとつ外側の型のバイナリ名>$<メンバ型の単純名>
局所クラス
: <ひとつ外側の型のバイナリ名>$<数字><局所クラスの単純名>
匿名クラス
: <ひとつ外側の型のバイナリ名>$<数字>
ディスクリプタ
フィールド型
BaseType Chatacter | Type | Interpretation |
---|---|---|
B | byte | signed byte |
C | char | Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
D | double | double-precision floating-point value |
F | float | single-precision floating-point value |
I | int | integer |
J | long | long integer |
L ClassName ; | reference | an instance of class ClassName |
S | short | signed short |
Z | boolean | true or false |
[ <BaseType> | reference | one array dimension |
メソッドディスクリプタ(method discriptor)
(<引数の型のディスクリプタ>)<戻り値の型のディスクリプタ>
戻り値の型はvoidを表す'V'を含む
Maven
No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?
Window -> Preference -> Installed JREsにJDKのパスを設定すると直る
ファイル操作
java.io.File
やjava.nio.file.*
を用いる.
ディレクトリの再帰的探索
java.nio.file.Files#walk
かjava.nio.file.Files#find
を用いる.
Java8の仕様書によるとファイルの属性を利用する場合はfindの方が効率的らしい.
以下は文字列baseで指定したディレクトリから1階層下までのjarファイルを列挙する例
Files.find(Paths.get(base), 1, (p,a) -> !a.isDirectory())
.filter(path -> path.File().getName().endsWith(".jar"))
.forEach(path -> System.out.println(path));
拡張子の判定
java.io.File#getName
を用いてfile.getName().endsWith(".foo")
のようにする.
java.nio.Path#endsWith
はディレクトリの(グラフ理論的な)パスのある点より後ろの完全一致を確かめるのでこの用途には不適
境界ワイルドカード
型変数の定義は共変性 extends
,または反変性 super
のワイルドカードを指定できる.
例えば T extends/super Foo
と指定するとT
としてFooかその 子/親 クラス
が許される
java.util.Comparator#comparingの例
T型のソートするためのComparatorを返すメソッドで
comparing(Function<? super T,? extends U> keyExtractor, Comparator<? super U> keyComparator)
というシグネチャを持つ.Tの大小判定はU型のオブジェクトを用いる.
keyExtractorはT型のオブジェクトからU型のオブジェクトを得る方法である.
UはTの親の型から得られてもよく,得られる方がUの子の型でもよいからFunction<? super T, ? extends U>
keyComparatorはU型の大小関係を比較するComparatorである.
U型またはその親の型を比較できればよいのでComparator<? super U>
正規表現 Pattern
文字クラス
文字を[
と]
で囲むと,囲んだ文字の論理和を簡潔に表現できる.
文字クラス内ではいくつかの文字が特殊な扱われ方をする
-
[
直後の^
は文字クラスの否定 - 文字と文字の間の
-
は文字コードの範囲 -
.
,(
,$
,?
,+
などは(エスケープなしで)その文字そのもの
[
直後でない^
と文字の間にない-
は文字そのものを表すが,
メタ文字以外に\
を付けても意味は変わらないので,否定や範囲でないことを明示した方が良いだろう
replaceAll
Matcher#replaceAll
はMatcherの状態をリセットする.
引数はreplacementの,$
と\
はエスケープが必要
直列化(永続化) Serialize
JavaオブジェクトをByte列にする方法
基本
インターフェースSerializableを実装したクラスは
ObjectOutputStream#writeObject
, ObjectInputStream#readObject
を使ってそれぞれシリアライズ,デシリアライズできる.
// 直列化対象オブジェクト
String target = "Hello";
// 直列化バイト列
byte[] serialized;
// 復元オブジェクト
String restored;
// バイト列格納用
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(target); // 直列化!
// バイト列
serialized = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(serialized);
ObjectInputStream in = new ObjectInputStream(bis);
restored = (String) in.readObject(); // 復元!
System.out.println(target); // Hello
System.out.println(Arrays.toString(serialized)); // [-84, -19, 0, 5, 116, 0, 5, 72, 101, 108, 108, 111]
System.out.println(restored); // Hello
この例ではObjectOutputStreamを使ってbyte配列に書き出しているが,FileOutputStreamを使えばファイルに書き込めるし,Socketを使えばネットワークへ送信できる.
自作クラスの直列化
Serializableはマーカーインターフェースなので,自作のクラスも宣言するだけで直列化可能となる.
transient
修飾子は付けたフィールドを直列化の対象から外す(復元時の変数は初期値)が,逆にそれ以外のフィールドは全て自動で直列化される. ただし,直列化するフィールドは全て直列化可能である必要がある.
直列化可能でないオブジェクトの例
- java.sql.Connectionなど外部とやりとりするもの(NotSerializableException)
- 循環参照を含むもの(StackOverflow)
serialVersionUID
直列化するときのクラス構造と復元するときの構造が同一であることを確認するためのハッシュ値のようなもの(厳密には知らん 要調査)
以下のように定義する.
public class Target implements Serializable {
public static final long serialVersionUID = ...
...
}
Serializableを宣言したにも関わらずこの値が未定義であると,コンパイル時に警告を受ける.
serialVersionUID
は直列化と復元を厳密にするためのものであり,定義しなくとも,構造に変化がなければ復元は問題なく行われる.構造に変化があると多くの場合は復元時にエラーとなるが,運が悪いと変更後の構造としてに読み込めてしまう.これを防ぐのがこの値の役目である.
この値はクラス構造の変化に伴って値を更新する必要があり,更新する気がないなら定義すべきでない.
直列化の独自実装
特別なメソッドを定義することで,byte列への符号化,および復号化の規則を独自実装することができる.
- writeObject,readObject
- writeReplace,readResolve
これによって例えば,次のようなことが可能である.ノード同士の参照関係で実装したグラフクラスをそのまま直列化すると,循環グラフになっていた場合に参照をたどり続けてStackOverflowを引き起こす.しかし直列化の際に隣接行列などの循環参照を含まない形に変換することで直列化可能となる.
writeObject,readObject
この方法は以下のメソッドを宣言し,直列化時のbyte列,復元時のフィールドにそれぞれアクセスする.
private void writeObject(ObjectOutputStream out) throws IOException
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
制限としては,
- finalなフィールドに書き込めない
- 親クラスのprivateフィールドにアクセスできない
- Serializableでなくデフォルトコンストラクタを持たないクラスの子クラスには使えない(実行時例外)
等がある.
以下の例では Foo
クラスの transient
フィールド baz
を独自に直列化,および復元する.defaultWriteObject
, defaultReadObject
はデフォルトの直列化,複合を行うメソッドである.
class Foo implements Serializable {
int bar;
transient int baz;
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(baz)
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
baz = in.readInt();
}
}
このように独自に扱いたいフィールドを trancient
で指定し, writeObject
,readObject
内で直列化,復元する.
例ではbazのint値をそのまま書き出したが,読み出し時に復元さえできれば,ビット反転しても,文字列化してjsonにしても良い. 書き出し,読み込みの順番に注意
writeReplace,readResolve
この方法は以下のメソッドを宣言し,直列化,復元時にオブジェクトを丸々入れ替える.
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
制限としては,拡張可能なクラスと互換性がないということがあげられる.
以下に例示する Serialization Proxy パターンや,シングルトンオブジェクトの直列化などで有用である.(参照: シングルトン)
class Foo implements Serializable {
private final int bar; // 保存したいデータ
private final int baz; // 保存したいデータ
private Foo(int bar, int baz) {
this.bar = bar;
this.baz = baz;
}
... // メソッドとか
// 以下 Serialization Proxy パターン
private static class SerializationProxy implements Serializable {
private long qux; // 実際に保存するデータ
SerializationProxy(int bar, int baz) {
qux = (((long) bar) << 32) | baz // 変換
}
private Object readResolve() {
Foo result = new Foo((int) (qux >>> 32), (int) qux); // 逆変換
return result;
}
private static final long serialVersionUID = ...
}
Object writeReplace() {
// 書き込み時は SerializationProxy を書き込む
return new SerializationProxy<>(this);
}
// 無理矢理読み込むの禁止
private void readObject(ObjectInputStream in) throws InvalidObjectException {
throw new java.io.InvalidObjectException("Proxy required");
}
}
動作は以下のようになる.
Foo
の直列化を行う際には, writeReplace
の宣言が見つかり, Foo
の代わりにこのメソッドの戻り値を利用することがわかる.
writeReplace
の呼び出しで,Foo
インスタンスの情報を持った SerializationProxy
のインスタンスが生成され,代わりに書き込まれる.
復元を行う際には,まず書き込んだ SerializationProxy
のインスタンスが復元される.
しかしこのクラスは readResolve
を宣言しているため,このメソッドの戻り値が代わりに返る.
readResolve
の呼び出しで Foo
のインスタンスが生成され,代わりに読みだされる.
モナド Monad
圏論は数学を熟成させて絞った感じがするので,そのものには触れない.
ジェネリックなクラスで副作用を閉じ込めつつ手続きの合成を記述する際の技法として有用なので記す.
ネット上の情報はHaskellが多いのでHaskellでの定義を参照しつつJavaに応用する.
以下では標準ライブラリのStreamを例とする.
Streamは手続きをメソッドチェインで合成でき,かつ実装は並列処理に切り替えられるようにうまく切り分けられている.
モナドに必要なもの
- 型構築子 M
- 関数
- unit :: α → α M
- bind :: α M → (α → β M) → β M
Java的にはMはイレイジャ型,unitはコンストラクタ,bindはインスタンスメソッドで実装すると都合が良いだろう.
Streamにおいてはunitはof,bindはflatMapがそれぞれ対応するため,(Stream, Stream#of, Stream#flatMap)はモナドである.
OptionalもofとflatMapが対応する.
モナド則の確認
演算は次の性質を満たす必要がある.(>>=はbindの中置演算子)
- unit x >>= f = f x
- m >>= unit = m
- (m >>= f) >>= g = m >>= (λx.(f x >>= g))
特に応用があるわけではないので,メソッドがこれらを満たすように設計すればよい.
Streamを例に確認する.
それぞれ
x = "x"
f = str -> Stream.of(capitalize(str)) :: String → Stream<String>
g = str -> Stream.of(str.length()) :: Stirng → Stream<Integer>
としてモナド則に当てはめると
Stream.of(x).flatMap(f) ≡ f(x)
m.flatMap(Stream::of) ≡ m
m.flatMap(f).flatMap(g) ≡ m.flatMap(str -> f(str).flatMap(g))
と成り立っている
Zero と Plus
以下の条件を満たす値mzeroと演算plusがあると便利なことがある(+はplusの中置演算子)
- mzero >>= f = mzero
- m >>= (λx.mzero) = mzero
- mzero + m = m
- m + mzero = m
Streamではemptyとconcatが対応する
Stream.empty().flatMap(f) ≡ Stream.empty()
m.flatMap(Stream::empty) ≡ Stream.empty()
Stream.concat(Stream.empty(), m) ≡ m
Stream.concat(m, Stream.empty()) ≡ m
concatはインスタンスメソッドにしたほうが中置できてよさげ
ファンクタ Fanctor
モナドに続いて満たしておくと何かと便利第2段
ファンクタはモナドをさらに抽象化したやつ
ファンクタに必要なもの
- 型構築子 F
- 関数 fmap :: (α → β) → (α F → β F)
fmapは構造Fに関係しない関数(α → β)を構造Fのある関数(α F → β F)に対応付けているといえる.
fmapはHaskellでは中置演算子<$>が利用できる
Java的にはFはイレイジャ型,fmapはインスタンスメソッドとすると都合が良いだろう.
StreamやOptionalではmapがfmapに対応する
持ち上げ
fmapに関数を部分適用することによって,Fに依存しない関数をFに依存する関数に変換することを持ち上げと呼ぶ.
Integer::toStrningをStreamに持ち上げる例を示す.
Function<Integer, String> int2str = String::valueOf;
Function<Stream<Integer>, Stream<String>> sint2sstr = sint -> sint.map(int2str);
Applicative
Applicativeはファンクタとモナドの中間的な抽象度のクラスである.
(Fanctor ⊃ Applicative ⊃ Monad)
Applicativeではさらに以下の要素が追加される
- 関数 pure :: α → α F
- 中置演算子 (
<*>
) :: (α → β) F → (α F → β F)
pureは構造Fの世界の外にある値をFの世界に持ってくる関数(モナドではunitに対応する)である.
中置演算子<*>
は構造Fで包んだ演算(α → β) Fを構造Fの世界の関数(α F → β F)に対応付けているといえる.
Applicativeは構造から値を取り出すことなく構造の中で演算を行うことができる.
例えば,
func :: α → (β → γ)
arg1 :: α F
arg2 :: β F
とするとfunc <$> arg1 <*> arg2
は
func :: α → (β → γ)
func <$> :: α F → (β → γ) F
func <$> arg1 :: (β → γ) F
func <$> arg1 <*> :: β F → γ F
func <$> arg1 <*> arg2 :: γ F
のように型がつく.(<$>はfmap::α F → (α → β) → β F の中置演算子)
構造Fから値を取り出すことなしに,Fの内側の世界を制御することができる.
Javaでの応用
to be written
cloneの実装
インスタンスをコピーする機能を持たせるにはCloneable
インターフェースを実装しclone
メソッドをオーバーライドする.
実装法
- implements Cloneable
- Override clone
- 可視性をpublicに拡大
- 戻り値の型を実装クラスの型に縮小
- throwsをはずす(正しく実装していれば起こらないよね?)
-
super.clone()
を呼び出し - primitiveでもimmutableでもないフィールドを適宜複製して上書き(ディープコピー対応)
コード例
@Override
public TargetClass clone() {
try {
TargetClass clone = (TargetClass) super.clone();
clone.mutableField = mutableField.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new Error("Implementation defect", e);
}
}
失敗例
以下のコードはフィールドprimitive
及びarray
を書き換え忘れた失敗例である.
import java.util.Arrays;
import java.util.List;
public class CloneTest implements Cloneable {
int primitive = 1;
int[] array = new int[]{1};
List<Integer> mutable = Arrays.asList(1);
@Override
public CloneTest clone() {
try {
return (CloneTest) super.clone();
} catch (CloneNotSupportedException e) {
throw new Error("Implementation defect", e);
}
}
@Override
public String toString() {
return "primitive: "+primitive+
", array:"+Arrays.toString(array)+
", mutable: "+mutable;
}
public static void main(String[] args) {
CloneTest original = new CloneTest();
CloneTest clone = original.clone();
// originalを出力
// primitive: 1, array:[1], mutable: [1]
System.out.println(original);
// cloneを変更
clone.primitive = 2;
clone.array[0] = 2;
clone.mutable.set(0, 2);
// originalを出力
// primitive: 1, array:[2], mutable: [2]
System.out.println(original);
}
}
スレッドの強制終了
スレッドを外部から停止する方法について
- Thread#stop
- volatile変数
- Thread#interrupt
を用いる方法がある.
Thread#stop
によって強制的にスレッドを停止させるのが最も簡単な方法であるが,オブジェクトの一貫性を損なう危険がある.
Thread#interrupt
に対応させて,割り込みによってジョブの中止を実現するのがベスト.
Thread#stop
time out 機能などの専用に用意されたAPIを使う以外で,計算を外部から止める時に使う.
Thread#stop
は実行中のスレッドにThreadDeath例外を発生させ,オブジェクトの一貫性を損なう恐れがあるため,deprecatedとなっている.
ロックは開放される.
以下の例では,スレッドを停止させるタイミングによってオブジェクトの一貫性を損なうことがある.
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class StopThread {
public static void main(String[] args) throws InterruptedException {
ConsistenceTarget target = new ConsistenceTarget();
List<WorkingThread> threads = IntStream.range(0, 5)
.mapToObj(i -> new WorkingThread(target))
.collect(Collectors.toList());
threads.forEach(t -> {
t.start();
System.out.println("Start thread-"+t.getId());
});
Thread.sleep(1000);
threads.forEach(t -> {
t.stop();
System.out.println("Stop thread-"+t.getId());
try {
Thread.sleep(100);
} catch(InterruptedException e) {}
});
}
// Start thread-11
// Start thread-12
// Start thread-13
// Start thread-14
// Start thread-15
// Stop thread-11
// Innconsistent in thread-12
// Innconsistent in thread-14
// Innconsistent in thread-15
// Innconsistent in thread-13
// Stop thread-12
// Stop thread-13
// Stop thread-14
// Stop thread-15
static class WorkingThread extends Thread {
private ConsistenceTarget target;
public WorkingThread(ConsistenceTarget target) {
this.target = target;
}
@Override
public void run() {
while(true) {
target.doSomething();
}
}
}
static class ConsistenceTarget {
private boolean consistency = true;
public synchronized void doSomething() {
if(!consistency) {
System.err.println("Innconsistent in thread-"
+Thread.currentThread().getId());
Thread.currentThread().stop();
}
consistency = false;
for (int i = 0; i < 10000; i++) {
// 何らかの処理
}
consistency = true;
}
}
}
volatileな通知用変数を用いる
スレッド停止のためのフラグをつくり,安全に停止できるタイミングで確認する方法.
これは簡潔な記述であるが,より明示的にするには後述のThread#interrupt
を用いる.
以下の例では停止用の変数を利用してオブジェクトの一貫性が損なうことなくジョブを停止する.
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class StopThreadUsingVolatile {
public static void main(String[] args) throws InterruptedException {
ConsistenceTarget target = new ConsistenceTarget();
List<WorkingThread> threads = IntStream.range(0, 5)
.mapToObj(i -> new WorkingThread(target))
.collect(Collectors.toList());
threads.forEach(Thread::start);
Thread.sleep(1000);
threads.forEach(t -> {
t.safeStop();
try {
Thread.sleep(100);
} catch(InterruptedException e) {}
});
}
// Start thread-11
// Start thread-14
// Start thread-12
// Start thread-13
// Start thread-15
// Stop thread-11
// Stop thread-12
// Stop thread-13
// Stop thread-14
// Stop thread-15
static class WorkingThread extends Thread {
private ConsistenceTarget target;
private volatile boolean running;
public WorkingThread(ConsistenceTarget target) {
this.target = target;
this.running = true;
}
@Override
public void run() {
System.out.println("Start thread-"
+Thread.currentThread().getId());
while(running) {
target.doSomething();
}
System.out.println("Stop thread- "
+Thread.currentThread().getId());
}
public void safeStop() {
running = false;
}
}
static class ConsistenceTarget {
private boolean consistency = true;
public synchronized void doSomething() {
if(!consistency) {
System.err.println("Innconsistent in thread-"+
Thread.currentThread().getId());
Thread.currentThread().stop();
}
consistency = false;
for (int i = 0; i < 10000; i++) {
// 何らかの処理
}
consistency = true;
}
}
}
Thread#interruptを用いる
スレッドには割り込みステータスを表すフラグがあらかじめ割り当てられている.
割り込みたい場合は,自前で変数を用意せずにこちらを使うべき.
APIは以下の3つ
Thread#interrupt
:
スレッドに割り込みを行う(フラグをセットする)
Thread#isInterrupt
:
スレッドの割り込みステータスを返す
Thread.interrupted
:
カレントスレッドの割り込みステータスを返し,フラグをリセットする
外部から実行を中断する機能を提供する場合はこれらのメソッドを利用して実現する.
長時間スレッドを停止させる可能性のあるThread#sleep
などは停止中の割り込みに対応するためにInterruptedException
をthrowする.
以下の例では割り込みを利用して,オブジェクトの一貫性が損なうことなくジョブを停止する.
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class StopThreadUsingInterrupt {
public static void main(String[] args) throws InterruptedException {
ConsistenceTarget target = new ConsistenceTarget();
List<WorkingThread> threads = IntStream.range(0, 5)
.mapToObj(i -> new WorkingThread(target))
.collect(Collectors.toList());
threads.forEach(Thread::start);
Thread.sleep(1000);
threads.forEach(t -> {
t.interrupt();
try {
Thread.sleep(100);
} catch(InterruptedException e) {}
});
}
// Start thread-11
// Start thread-14
// Start thread-12
// Start thread-13
// Start thread-15
// Stop thread-11
// Stop thread-12
// Stop thread-13
// Stop thread-14
// Stop thread-15
static class WorkingThread extends Thread {
private ConsistenceTarget target;
public WorkingThread(ConsistenceTarget target) {
this.target = target;
}
@Override
public void run() {
System.out.println("Start thread-"
+Thread.currentThread().getId());
while(true) {
target.doSomething();
if(Thread.interrupted()) {
break;
}
}
System.out.println("Stop thread-"
+Thread.currentThread().getId());
}
}
static class ConsistenceTarget {
private boolean consistency = true;
public synchronized void doSomething() {
if(!consistency) {
System.err.println("Innconsistent in thread-"
+Thread.currentThread().getId());
Thread.currentThread().stop();
}
consistency = false;
for (int i = 0; i < 10000; i++) {
// 何らかの処理
}
consistency = true;
}
}
}
標準ライブラリのキューとスタック
標準ライブラリにはQueue
インターフェースとStack
クラスがあるが,
Stack
クラスはVector
クラスを拡張した古いもので,
より一貫性のあるDeque
インターフェースを利用すべきである.
Deque
インターフェースはQueueとしてもStackとしても利用でき,
操作が失敗した場合の例外の有無との組み合わせで,
それぞれ以下のメソッドが対応する.
Queue
操作 | 例外なし | 例外あり |
---|---|---|
挿入 | offer / offerLast | add / addLast |
削除 | poll / pollFirst | remove / removeFirst |
検査 | peek / peekFirst | element / getFirst |
Stack
操作 | 例外なし | 例外あり |
---|---|---|
挿入 | offerFirst | push / addFirst |
削除 | pollFirst | pop / removeFirst |
検査 | peekFirst | peek / getFirst |
利用する実装は,同期が必要ないならArrayDeque
,必要ならConcurrentLinkedDeque
を使う.
サブインターフェースのBlockingDeque
は固定容量が必要な場合に利用する.
First/Lastつきのメソッドは名前の対称性が高いが,
StackまたはQueue専用として使うなら短い名前のメソッドを使ったほうが可読性が高いように思う.
Queueとしてのみ使う場合は,Queue型で受けたほうが間違いが少ないだろう.
なぜインターフェースをStackとQueueで分けなかったのか疑問である.
Repository リポジトリ
特定のクラスのオブジェクトを格納するとき,コンテナのインターフェースと格納先の実装を分離するパターン.
実装と分離することで,実データの保管場所を切り替えることが容易になる.
インターフェースはドメインに特化したものが良いだろう(汎用ならjava.util.Collectionなどが参考になるだろう).
Visitor ビジタ
データ構造の各要素を処理するとき,データ構造と処理の実装を分離するパターン.
データ構造内のデータを型ごとに振り分けるAccepter
と,型ごとにデータを処理するVisitor
で構成する.
以下に基本的な例を示す.
interface Node {
void accept(Visitor visitor);
}
class Branch implements Node {
public Node left;
public Node right;
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Leaf implements Node {
public int value;
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
interface Visitor {
void visit(Branch branch);
void visit(Leaf leaf);
}
class PrintNode implements Visitor {
@Override
public void visit(Branch branch) {
System.out.println("(");
accept(branch.left);
System.out.println(",");
accept(branch.right);
System.out.println(")");
}
@Override
public void visit(Leaf leaf) {
System.out.println(leaf.value);
}
}
class SumNode implements Visitor {
int sum = 0;
@Override
public void visit(Branch branch) {
accept(branch.left);
accept(branch.right);
}
@Override
public void visit(Leaf leaf) {
sum += leaf.value;
}
}
この例では二分木データ構造Node
をAcceptor
として,木構造を出力するPrintNode
と和を計算するSumNode
の各Visitor
を実装した.
特徴はAcceptor
の具象クラスでオーバロードされたvisitを適切に呼び出すダブルディスパッチである.
Acceptor
の責務は型によってVisitor
の適切なコールバックを呼ぶことである.
とはいっても基本的にはacceptメソッドを実装するだけ.
Visitor
の責務は走査されたデータに応じた処理を行うことである.
このオブジェクトを変えることで走査の順番やデータに対する処理を変更する.
状態を持つこともある.
データ構造の要素型の変更はVisitor
全てに伝播するため困難である.
TODO visitをdefaultで定義したらどうなるか調査する.
走査順を固定してよいならば,Acceptor
側に子要素のacceptを記述するInternal Visitorパターンの方がVisitor
側のコードがすっきりする.
対してこの例のようにVisitor
で走査順を記述するスタイルをExternal Visitorと呼ぶ.
Visitorの拡張
以下のようにVisitor
を拡張して,行きがけ,帰り掛け,スキップの処理を実現することもできる.
abstract class BeginEndVisitor implements Visitor {
public abstract boolean beginVisit(Branch branch);
public abstract void endVisit(Branch branch);
@Override
public final void visit(Branch branch) {
if(beginVisit(branch)) {
visit(branch.left);
visit(branch.right);
endVisit(branch);
}
}
}
Functional Visitor
引数や戻り値,例外を設定できるようなAcceptor
,Visitor
も考えられる.
interface Node {
<R, P, E extends Exception> R accept(FunctionalVisitor<R, P> visitor, P param) throws E;
}
iterface FunctionalVisitor<R, P, E extends Exception> {
R visit(Branch branch, P param) throws E;
R visit(Leaf leaf, P param) throws E;
}
これによってVisitor
内部に状態を持たずに戻り値で和を返したり,StringBuilder
に文字列を溜め込んだり,
例外を投げるメソッドを利用したりできるようになる.
やりすぎな感はある.
ネストしたクラス間のメンバーアクセス
インナークラスのメンバーは(それがprivateだったとしても)アウタークラスから参照できる.
しかし,バイトコードにはそのような特殊な可視性を表す属性はない.
コンパイラはこの性質を実現するするために,以下のようなトリッキーなバイトコードを生成する.
例となるコードとそのコンパイル結果を記述する.
コンパイラはEclipseを用いた.バイトコードはjavapの結果を示す.
ソースコード
package inner;
public class Outer {
{
new Inner(1).innerField=0;
}
static class Inner {
private int innerField;
private Inner(int i) {
innerField = i;
};
}
}
アウタークラスのバイトコード
class inner.Outer
{
public inner.Outer();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=4, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: new #10 // class inner/Outer$Inner
7: dup
8: iconst_1
9: aconst_null
10: invokespecial #12 // Method inner/Outer$Inner."<init>":(ILinner/Outer$Inner;)V
13: iconst_0
14: invokestatic #15 // Method inner/Outer$Inner.access$1:(Linner/Outer$Inner;I)V
17: return
}
SourceFile: "Outer.java"
InnerClasses:
static #26= #10 of #1; //Inner=class inner/Outer$Inner of class inner/Outer
インナークラスのバイトコード
class inner.Outer$Inner
{
private int innerField;
descriptor: I
flags: ACC_PRIVATE
private inner.Outer$Inner(int);
descriptor: (I)V
flags: ACC_PRIVATE
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: aload_0
5: iload_1
6: putfield #13 // Field innerField:I
9: return
inner.Outer$Inner(int, inner.Outer$Inner);
descriptor: (ILinner/Outer$Inner;)V
flags: ACC_SYNTHETIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: iload_1
2: invokespecial #21 // Method "<init>":(I)V
5: return
static void access$1(inner.Outer$Inner, int);
descriptor: (Linner/Outer$Inner;I)V
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #13 // Field innerField:I
5: return
}
SourceFile: "Outer.java"
InnerClasses:
static #30= #1 of #28; //Inner=class inner/Outer$Inner of class inner/Outer
インナークラスのバイトコードにACC_SYNTHETIC
というフラグのついた,ソースコードにないメソッドが2つ追加されている.
privateコンストラクタへのアクセス
バイトコード中でInner
クラスのコンストラクタは,private inner.Outer$Inner(int);
となっているとおりprivateであり,クラス外からはアクセスできない.
しかしアウタークラスからはアクセスできる必要があるため,コンパイラは追加のコンストラクタinner.Outer$Inner(int, inner.Outer$Inner);
を生成し,これを経由してInner
クラスを実体化する.
内部を詳しく見ると追加されたコンストラクタは,追加の引数を受けておいて結局利用していない.
また呼び出し側もinner.Outer$Inner
型の引数にnullを渡して呼び出している.
引数にinner.Outer$Inner
を追加するのは,単純に識別子の衝突を防ぐためのようだ.(わざと衝突させる識別子を用いたところ,さらにコンパイラは引数を増やして回避した.)
JDKで同様のコードをコンパイルすると,追加の引数としてinner.Outer$1
を追加で定義していた.
匿名クラスを利用することでアウタークラス以外から呼び出されないようにしているのだろう.
inner.Outer$1
クラスにはACC_SYNTHETIC
フラグがついていた.
JDKでコンパイルした場合のバイトコードを参考までに載せる.
class inner.Outer$Inner
{
private int innerField;
descriptor: I
flags: ACC_PRIVATE
private inner.Outer$Inner(int);
descriptor: (I)V
flags: ACC_PRIVATE
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #3 // Method java/lang/Object."<init>":()V
4: aload_0
5: iload_1
6: putfield #1 // Field innerField:I
9: return
inner.Outer$Inner(int, inner.Outer$1);
descriptor: (ILinner/Outer$1;)V
flags: ACC_SYNTHETIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: iload_1
2: invokespecial #2 // Method "<init>":(I)V
5: return
static int access$102(inner.Outer$Inner, int);
descriptor: (Linner/Outer$Inner;I)I
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=3, locals=2, args_size=2
0: aload_0
1: iload_1
2: dup_x1
3: putfield #1 // Field innerField:I
6: ireturn
}
SourceFile: "Outer.java"
InnerClasses:
static #12; //class inner/Outer$1
static #16= #4 of #23; //Inner=class inner/Outer$Inner of class inner/Outer
フィールドへのアクセス
to be written
親クラス(インターフェース)メンバの明示的な呼出し
親クラス(インターフェース)の名前をParent
,呼出すメンバをmember
とすると,
Parent.super.member
で指定できる.当然メソッド呼出しの場合は括弧が必要.
ビットテクニック
ビットシフト演算の右辺は,左辺がintなら下5bit,左辺がlongなら下6bitのみが有効
ビットフィールドの操作
要素の列挙
以下のコードでループ内の変数i
はビットフィールドbitField
の要素上を動く
int bitField = 0b10011;
for(int i = bitField & -bitField; i != 0 ; i = bitField & (~bitField + (i << 1))) {
// i = 0b00001, 0b00010, 0b10000
}
部分集合の列挙
以下のコードでループ内の変数i
はビットフィールドbitField
の部分集合上を動く
int bitField = 0b10011;
for(int i = 0;;i = (i - bitFiled) & bitFiled) {
// i = 0b00000, 0b00001, 0b00010, 0b00011, 0b10000, 0b10001, 0b10010, 0b10011
if (y == bitFiled) break;
}
for(int i = bitFiled;;i = (i - 1) & bitFiled) {
// i = 0b10011, 0b10010, 0b10001, 0b10000, 0b00011, 0b00010, 0b00001, 0b00000
if (y == 0) break;
}
参考:The Entire World of Bit Twiddling Hacks
基本:Low Level Bit Hacks You Absolutely Must Know
符号なし整数の操作
Javaのint型は符号ありだが符号なしとして扱いたくなることもしばしば
オーバーフロー検出
以下のコードで符号なし整数同士の加算(a+b)がオーバーフローすればtが負数になる
t = (a & 0x7ffffff)+(b & 0x7fffffff)
t = a & (b|t) | b&t
ジェネリクスの型取得
型推論とリフレクションを利用すると以下のように配列を生成できる.
public <T> T getArray(int length, T... dummy) {
return (T[]) Array.newInstance(dummy.getClass().getComponentType(), length);
}
Stream操作
Streamに対する操作はステートレスなものとステートフルなものがある.
ステートレス操作
- filter
- map
ステートフル操作
- distinct
- sorted
ステートフル操作は以前の要素を参照する必要があるため,複数回の走査やバッファが必要になる.
列挙子 Enum
概要
enumは特殊なクラスであり,キーワードenum
を使って定義する.
enumクラスは定義した要素以外のインスタンスを生成できない.
enumE
の直接の親クラスはEnum<E>
である.
enumは暗黙的にfinalで†,ネストした場合は暗黙的にstatic扱いとなる.
†要素がクラスボディを持たない限り
可能な拡張
- interfaceの実装
- privateなコンストラクタの定義
- finalなメンバの追加
- クラスとして共通メンバ
- 匿名クラスとして固有のメンバ
使いどころ
種類や種別など,あらかじめ定義したインスタンスだけが必要な概念を表すとき使う.
メンバを持たせられるため,機能をもつクラスであってもenumにできる.
特にホットスポットが小さいクラスは,それを宣言部に記述することでテーブルの
ようにまとまる.
switch-caseで利用できるため,定数値以外はstatic final
を使わず積極的にenumとする.
singletonの実装としても,利用可能であれば簡易で確実な実装となる.
(Singleton シングルトンを参照)
利用例
学生の所属課程
所属課程種別を示すクラスCourse
のenumを使った実装例を示す.
Course
は学籍番号から種別を判定する機能classify(String)
を持つ.
public enum Course {
BACHELOR("_"), // 学部生
MASTER ("M"), // 修士課程
DOCTOR ("D"), // 博士課程
RESEARCH("R"); // 研究生
private String str; // 学籍番号の出だし
private Course(String str) {
this.str = str;
}
public boolean check(String studentId) {
return studentId.startsWith(str);
}
public static Course classify(String studentId) {
for(Course c: values()) {
if(c.check(studentId)) {
return c;
}
}
throw new IllegalArgumentException(
"studentId: "+studentId);
}
}
ホットスポットである判定条件の差異の文字列をコード上部に記述し,インスタンスの特徴がわかりやすいようにした.
アセンブリコードの分岐条件種別
より複雑な例として,教育用マイコンボードKueChip-2の分岐命令に対応するアセンブリコードの分岐条件を表すクラスBranchCondition
の一部を抜粋する.
public enum BranchCondition {
A (0b0000, flag -> true), // 常に分岐
VF(0b0001, Flag.VF.is(1)), // オーバーフローで分岐
NZ(0b0010, Flag.ZF.is(0)), // 0以外で分岐
Z (0b0011, Flag.ZF.is(1)), // 0で分岐
ZP(0b0100, Flag.NF.is(0)), // 0以上で分岐
N (0b0101, Flag.NF.is(1)), // 負で分岐
P (0b0110, Flag.NF.and(Flag.ZF).is(0)), // 正で分岐
ZN(0b0111, Flag.NF.or (Flag.ZF).is(1)), // 0以下で分岐
...
private final int lower4;
private final BitChecker checker;
private BranchCondition(int lower4, BitChecker checker) { ... }
}
コンストラクタの引数として,対応する機械語の下4bitを示すint
と,フラグレジスタの状態から分岐を判定するBitChecker
を与えている.
FlagとBitCheckerは以下のようなコード.
public enum Flag implements BitChecker {
CF(0b1000),
NF(0b0100),
VF(0b0010),
ZF(0b0001);
...
@Override
public boolean check(int flag) {
return (flag & mask) != 0;
}
}
@FunctionalInterface
interface BitChecker {
/**
* 指定したフラグレジスタの状態が条件を満たすか確認する
* @param fr フラグレジスタの状態
* @return 適合すれば1そうでなければ0
*/
boolean check(int fr);
default BitChecker and(BitChecker target) {
return fr -> check(fr) && target.check(fr);
}
...
default BitChecker is(int result) {
if(result != 0 && result != 1) {
throw new IllegalArgumentException("result: "+result);
}
return fr -> !(check(fr)^(result == 1));
}
}
インナークラス
文法的にはネストしたクラスのうちstaticでないものを指す.
インナークラスのインスタンスはエンクロージングクラスのインスタンスを参照でき,フィールドへアクセスできる.
使いどころ
データ構造にViewを提供するとき.
ファイルからの画像読込み
画像ファイルをImageのインスタンスにする方法
描画可能な画像は典型的にはImageのインスタンスをして扱うが,Imageのインスタンスを得るには
- 元となるリソースデータの読込み
- リソースデータからImageを構築
を行う.
リソースデータの読込み
リソースデータを読込む方法はいくつかある.
データは以下のような場所から読み出せる.
- ローカルのファイル // to be written
- web上のファイル // to be written
- jarファイルの中
クラスローダを利用する方法
jarファイルに埋め込んだ画像をInputStream
として読込むことができる.
classLoader.getResourceAsStream(String path)
クラスローダは,利用したいクラスローダにロードされたクラスから
getClass().getClassLoader()
で取得できる.
Image化
// to be written
Javaはクソ
歴史的なクソ仕様をまとめる
配列は共変
Javaの配列は共変なので以下のようなエラーをコンパイラが見つけてくれない.
String[] strs = new String[] {"foo", "bar"};
Object[] objs = strs;
objs[0] = Integer.valueOf(42); // ArrayStoreException
文字列はUTF-16
クラスファイルの文字列定数はUTF-16で記述される.
当時のUnicodeは16bitでこの世の全ての文字を表すつもりだったらしい.
char型はサロゲートペアの扱いが面倒だが,現在はcode pointをint値で扱うAPIが追加された他,
asciiのみからなる文字列はVM内部で1文字1byteで扱うなどの拡張によって欠点を補っている.
int型定数の参照は展開
あるクラスに関する情報は基本的にそのクラスファイルに集約される.
public static final int
は例外で,参照先に展開される.
double,long型は定数プールのインデックスを2つ消費する
クラスファイル内で利用する定数は定数プールに列挙され,バイトコード中ではそのインデックスで表す.
double,long型定数はその次のインデックスは欠番になる
(もしかすると当初は32bit単位のオフセットを示していたのかもしれない)
コード外でのメソッド記述
<クラス名>#<インスタンスメソッド名>
<クラス名>.<クラスメソッド名>
と書き分けるらしい.フィールドはどうするのだろう
単体テストの書き方
アプローチ
メソッドベース
: メソッド毎にテストメソッドを書く
シナリオベース
: 利用シナリオごとにテストメソッドを書く
objectclub.jpによると
次のように使い分けると良いそうだ.
クラスの種類 | テストケースの書き方 |
---|---|
データを保持するのが目的のクラス | メソッドベース |
ユーティリティ的なクラス | メソッドベース |
ふるまいを表すクラス | シナリオベース |
1メソッドに対して1テストで記述しようとすると困難な場合もある.
例外を投げることを期待するような場合,JUnitはメソッドに対するアノテーションとして期待する例外を記述するため,例外の数だけテストメソッドの作成が必要になる.
(JUnit4までの話で5は知らない.例外を投げることを期待するAssertメソッドを追加するjUtaimeライブラリがある)
また,@Test
の付いたメソッドを自動抽出してスペクトラムベースのfault localizationをするのと相性が悪い.
assert
// to be written
throw
// to be written
アルゴリズムの選択
複数のアルゴリズムから実装を選ぶ際の基準を列挙する
- ナイーブな方
- 実装を間違った場合,微妙に失敗するのではなく壊滅的
に失敗する方
JDTサンプルコード
JDTでAST level2を動作させたときのメモ
解析対象はJava10
Javaソースコード
import java.io.IOException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.eclipse.jdt.core.dom.MethodInvocation;
public class Main {
public static void main(String[] args) throws IOException {
ASTVisitor visitor = new ASTVisitor(){
@Override
public boolean visit(MethodInvocation node) {
System.out.println(node.resolveMethodBinding());
return false;
}
};
ASTParser parser = ASTParser.newParser(AST.JLS10);
parser.setResolveBindings(true);
parser.setBindingsRecovery(true);
String[] srcDirs = new String[]{"./src"};
String[] sources = new String[]{"./src/TargetForLevel2.java"};
String[] libs = new String[]{ /* no jar files */ };
parser.setEnvironment(libs, srcDirs, null, true);
parser.createASTs(sources, null, new String[0], new FileASTRequestor() {
@Override
public void acceptAST(String sourceFilePath, CompilationUnit ast) {
System.out.println("File: "+sourceFilePath);
ast.accept(visitor);
}
}, new NullProgressMonitor());
}
}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>DefUse</groupId>
<artifactId>DefUse</artifactId>
<version>0.0.1</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>10</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.core.jobs</artifactId>
<version>3.10.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.core.resources</artifactId>
<version>3.13.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.core.runtime</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.equinox.app</artifactId>
<version>1.3.500</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.core</artifactId>
<version>3.14.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.platform</groupId>
<artifactId>org.eclipse.osgi</artifactId>
<version>3.13.0</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<directory>src</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>