“import static”を使いこなしてますか?
どのような場面で使うのかご自身の方針はありますか?
古いUSBメモリに10年以上前に作りかけだったプログラムを発見したので興味本位で再開しました。
元々ビルドツールとしてAntを使っていたのですが、後継としてGradleが良さそうだと思い、Libraryサンプルを元に学習しています。
そのサンプルのプログラムで使用されていた”import static”というステートメントが分からなかったので使い方や方針をまとめてみました。
ご自身のコーディング方針と比較しつつ読んでいただければ、新たな知見やアイデアが得られるかもしれませんし、もしあなたがJava言語勉強中であれば、言語習得のアプローチ等、何かしらの参考になるかもしれませんので是非ご一読ください。
動機
開発を再開したシステムで使っている自作ライブラリパッケージのビルドツールをAntからGradleにしたかったので、Gradleのドキュメントを参考にライブラリプロジェクトのサンプルを学習ました。その際に非常に簡単なサンプルプログラムで定義されていた”import static”がわかりませんでした。
この構文の意味と使い方を学習する中で、疑問に思ったことや使い方の方針をまとめてみました。
“imoprt static”の要約
“import static”はJava1.5で追加されたステートメントで、クラスに定義されたpublic staticな変数およびメソッドを他のクラスからクラス名修飾なしで参照できるようにします。
これにより、ソース内で変数やメソッドを複数回参照する必要がある場合に、毎回クラス名修飾もしくはFQDNを記述する必要がなくなります。
アンチパターンとの関係性
上記内容だけに注目すると単なるユーティリティ的な機能に感じますが、その背景や効果的な使い方を調べると、ある有名なアンチパターンを回避しつつ、かつ効率的にコーディングができるようにするために利用できるということがわかりました。
昔のOracleのドキュメントには、そもそもinterfaceに対して定数を定義してしまうという「定数インタフェース」というアンチパターンへの対応として利用できることが記載されています。
※最新のドキュメントで同様の記載のあるページが見つけられませんでした。
このOracleドキュメントにも記載されいている「Effective Java」という書籍でもアンチパターンへの代替案としてこのimport staticが活用できるという事で紹介されています。
私が持っている書籍は第2版ですが、第3版が出版されているのでリンクを掲載しておきます。
Effective Java 第3版【電子書籍】[ ジョシュア・ブロック ]
※楽天ブックスへのリンクです。
私は”import static”の潜在的なデメリットも感じたので、この記事の後半で紹介します。
使い方
私が学習したGradleのサンプルでは、単体テストのソース内でAssertionsクラスに定義されている多くのpublic staticメソッドを参照するために使っていました。
後で気づきましたが、この使い方は「テストを実行する」という関心事の領域において「多くのテスト関連メソッドを参照する必要がある」という状況でimport staticを使っており、非常に理想的な使い方だと思います。
import static を理解するために私が書いた最初のコードです。
これは後でダサい使い方だと気が付きましたが、import staticというものを理解するためには十分機能したソースでした。
import static java.lang.System.out; // publicなスタティック変数
import static java.util.Arrays.sort; // publiciなスタティックメソッド
class HelloWorld {
static void main(string[] args) {
String[] strs = {"c", "a", "b"};
List<String> strList1 = Arrays.asList(strs);
strList1.forEach(str -> out.println(str)); // c, a, b
sort(strs);
List<String> strList2 = Arrays.asList(strs);
strList2.forEach(str -> out.println(str)); // a, b, c
}
}
“import static”の特徴
いくつか特徴がわかったので以下にまとめてみました。
同名かつ異なるシグネチャーのメソッドは自動オーバーロード
複数のクラスに定義されている同名メソッドを参照する場合、メソッドシグネチャーが異なっていれば自動的にオーバーロードしてくれるので、引数に一致するメソッドが自動的に実行されます。
同名かつ同じシグネチャーのメソッドはコンパイル時エラー
複数のクラスに定義されている同名メソッドで、かつ同じシグネチャーのメソッドを参照している場合、コンパイル時にエラーとなります。
同名の変数はコンパイル時エラー
複数のクラスに定義されている同名変数を参照している場合、コンパイル時にエラーとなります。
Enumメンバー
Enumメンバーもpublic staticなメンバーなのでimport static 対象にできます。この時の特徴は変数と同じです。
シャドウイングによる影響
Javaソースコードの特定スコープ内において、”優先度の高い変数と同名の優先度の低い変数”がある場合、優先度の低い変数は対象スコープ内では参照されない変数となるので注意が必要です。
これはJavaの言語仕様であり、「シャドウイング」といい、import static で参照される変数にも適用されます。
クラス変数と同じ名前のメソッド引数やメソッド内ローカル変数を定義した場合、優先されるのはメソッド引数やメソッド内ローカル変数となり、import staticした変数は参照されません。
例えば、先のサンプルプログラムにおいてjava.lang.Systemクラスのout変数を参照してましたが、メソッドローカルにも同名の変数を定義した場合、優先度が高いのはローカル変数なので、以下のプログラムでは標準出力されず、ファイルへ出力されます。
import static java.lang.System.out; // publicなスタティック変数
import static java.util.Arrays.sort; // publiciなスタティックメソッド
class HelloWorld {
static void main(string[] args) {
String[] strs = {"c", "a", "b"};
PrintStream out = new PrintStream("sample.txt"); // ファイル出力ストリームとして生成
List<String> strList1 = Arrays.asList(strs);
strList1.forEach(str -> out.println(str)); // c, a, b
sort(strs);
List<String> strList2 = Arrays.asList(strs);
strList2.forEach(str -> out.println(str)); // a, b, c
out.close();
}
}
見解と方針案
import static は、コード記述量が削減できるというメリットはありますが、昨今のIDEやエディタには自動補完機能があるため、私は殆どメリットを感じませんでした。
もし使用する場合は、使い所や使用時のルールを慎重に検討した方がよいと思いました。
理由は、私の経験上、複数のクラスに対して同名の変数名やメソッド名を定義することはあるので、名前が被る可能性は結構あると思います。その際にコンパイルエラーに対処したり、シャドウイングが原因となるバグに対処したりといった余計な作業が発生しそうです。
バグの原因になりそう
名前が一意に解決できない場合はコンパイルエラーとなるし、そもそも使いすぎるとソース可読性が落ちるのではと懸念を抱いてしまいました。
例えば、以下3つの変数に対しクラス名修飾がない変数記述が特定スコープ内に混在する場合、開発者がどの実体を参照しているのかぱっと見で分からなくなりそうで、エンジニアの不注意で発見しづらいコーディングミスによるバグを引き起こしそうです。
- import staticされた変数
- クラス内メンバー変数
- 引数宣言された変数もしくはローカル変数
コーディング中はできるだけロジックに集中したい
私が現役エンジニアだった時、コーディング中はできるだけ思考範囲が局所化できるコーディングを心かけてました。
そのスコープからできるだけ複雑さを取り除くことが高品質/高スピード開発につながるという考えです。
import staticはその逆で、使い方を間違えると複雑さを持ち込む1つの要因になりそうです。
コーディング中は変数やメソッドの参照元を意識するのは必須なのですが、シャドウイングにより意図しない変数が参照されたりとか、メソッドの引数を間違えることにより意図しないクラスのメソッドをコールしてしまうとか、効率が落ちそうな気がしてなりません。
変数やメソッドを定義しているクラス名を付与するだけで、直感的にかつ確実に自分の意図した通りのコーディングだと確信を持てると思いますし、コーディングが面倒ならIDEのコード補完使えば良いと感じてしまいます。
使用する際の方針案
もちろん便利な用途もあると思いましたので、その案を提示します。
Gradleのサンプルにもありましたが、例えばテストするためのユーティリティ的なメソッドや、決まりきった定数というのはあるわけで、それをまとめたユーティリティクラスとしてまとめてimport staticで参照するというのは非常に効率的な使い方だと思いました。
要するに、特定のドメインに関する共通な情報やロジックをクラスに定義して”import static”するということです。
例えば、Webサーバーの機能を提供するユーティリティとして、環境情報へのアクセス処理がまとまっているServletContextクラスだったり、テスト実行のユーティリティあれば今回Gradleのサンプルに記載のあったJunitのAssertionsクラスなどです。
これらはとてもわかりやすいし直感的であり、コーディングの効率化および情報とロジックの局所化という面で非常によい用途かと思います。
まとめ
私は自分のコード内でimport staticを利用する場合、packageである程度の領域を区切り、その中で何をimport staticするか方針を決めて使用するのが良いと思います。
[Udemy] 一週間で身につくJava言語
※Udemyへのリンクです。
Effective Java 第3版【電子書籍】[ ジョシュア・ブロック ]
※楽天ブックスへのリンクです。
コメント