Javaを書こう!
この記事では、Java Advent Calendar 2018 2日目として、Javaを使ったことのないプログラマー向けに、今どきはこれぐらいから始めたほうがいいよねと言えるJava開発環境を紹介するとともに、「Javaが使える」と思えるレベルになるまでコンパイラや静的解析ツールが叱ってくれるような設定をしてみたいと思います。
プログラミング自体が初めてでJavaを使ってみてるという人にはこの記事はあまりお勧めしません。他のプログラミング言語が使えて、Javaもなんとなくはわかるんだけど、って人向けです。
また、経験者におかれましては、俺の環境の方がいい、という想いは皆さんあると思いますので、ぜひご紹介ください。なお、2018年末の記事なわけですが、私の手元ではここ数年はあまり基本構成が変わってないです。そろそろFacebook InferとかChecker Frameworkが常識化されないかなあと思いつつ…
eclipseをインストールする
何はともあれ、Javaを書くにはeclipse、それも日本語化されたパッケージのPleiades All in Oneを入れます。
使い慣れたエディタで、例えばVimやEmacsでJavaを書くというのは、ムリです。eclimとかいうやや常軌を逸した高度な力技もありますが、中身はeclipseですので、eclipseで書けるようになってからの次のステップです。他のエディタもやはりダメです。言い切ってしまいますが、JavaとはIDE言語です。ビジュアルプログラミング言語をテキストエディタで書こうと思いますか?ビジュアルエディタで編集するでしょう。同じようにIDE言語はIDEで編集するものです。
eclipse以外のIDEはどうか?日本語でこのような記事を読んでいるのであれば、Pleiades All in One一択です。ここでよくある失敗は、英語ぐらいわかる、または英語の勉強、または英語の方が情報があると思って、ちゃんと知らない分野に得意でない言語で突入することです。
JavaにはJavaの用語があります。(ふだんあまり意識するものではないですが、たいていの言語には独特の用語かまたはその言語が向いているドメインに固有の用語があると思います)
IDEの機能を見たときに、それが英語だから理解できないのか、Java語だから理解できないのかわからないと、調べようもありません。そのような単語はすぐに理解することすら諦めてしまい、結果として便利な機能なのに一度も使ったことがないということによくなります。みっちり教えてくれる人がいないのであれば、少なくとも使いこなせるまでインターフェースぐらいはネイティブであるべきです。
もちろんIntelliJ(CEでも)もPleiadesを使えば日本語化できますが、日本語化は一般的あまり一般的ではなく、上級者向けです。NetBeansやVSCodeもありますが、とりあえず忘れてもいいです。良し悪しの問題ではなく、単純に今回の目的には不向きです。
さてPleiades All in Oneですが、複数の種類があります。これまた迷わずにfor JavaのFull Editionをダウンロードしてください。
他の言語にも興味があるのでUltimateを選んだ?失敗です。ecliseは重厚なIDEです。それをさらに重厚にしたのがUltimateです。言語環境切り替え方法が分からずに右往左往が関の山です。
自分で設定したいのでPlatformを選んだ?失敗で草。eclipseに慣れもしてないのに設定できるわけがないです。
もし、ほかの言語用のeclipseをすでにインストールしている場合でも、迷わず最新のfor Javaを落としてください。
eclipseはプラグインが豊富なIDEですが、プラグインが豊富だということと、複数のプラグインが協調して正常に動くということは全く異なる話です。eclipseでの失敗の原因の多くは、何でもかんでもプラグインを入れてプラグイン間の整合性が取れなくなったり、どこかのプラグインが致命的なエラーを出して、eclipseが立ち上がらなくなったりすることです。
そのことが理解できるまで、検証された安定環境を使うべきです。
eclipseをセットアップする
インストールが終われば初期セットアップします。
起動前にチェック
人権のないメモリの少ないマシンでeclipseを立ち上げるというのは不幸なことですが、まれによくある話です。個別のチューニングなどはこの記事の対象外としたいところなのですが、32ビット版や、メモリが心配な場合は、eclipse.ini
の末尾に次の2行を加えておくとよいでしょう。
-Xms1G
-Xmx1G
コンパイラを設定する
eclipseのJava環境のすごいところ(そしてまれに落とし穴)は、**Eclipse Compiler for Java (ecj)**というeclipse用のJavaコンパイラが付属していることです。JDKのみを使うIntelliJも似たようなこともできるので、今となってはecjがあるからというわけでもないのですが、ともかくecjでは独自の警告が設定でき、単体でも静的解析機能を実現しています。
しかしデフォルトでは甘々ですので、全力設定に変更します。なお全力とはいえ、警告にしておきましょう。
項目ごとに個別に設定を見てみましょう。
コードスタイル
スタイルといいつつも、だいたいは一般的なルールなのでだいたい警告にします。「外部化されていないストリング」は多くの人にとって関係のないオプションなので無視してもよいでしょう。流儀が分かれるとすると、「エンクロージング型のアクセス不可メンバーへのアクセス」「パラメーター代入」でしょうか。前者はクラス内クラスを作った時なので、おそらくしばらく気にしないでいいです。後者は引数への代入を許さないオプションです。
潜在的なプログラミングの問題
「ボクシングおよびアンボクシング変換」「enumの不完全な'switch'ケース」「'default'ケースが欠落した'switch'」「serialVersionUIDなしのシリアライズ可能クラス」は、せいぜい情報ぐらいでいいと思いますが、他はすべて有用なので警告にします。
名前のシャドーイングおよび競合
変数名被りにより、間違いが発生しやすくなるので、それの防止です。実際にIDE上では参照支援により間違いが発生することはあまりないのですが、警告にしておきましょう。
Null分析
「注釈ベースのnull分析を使用可能にする」は使い方が難しいので、offにしておくとよいでしょう。他はもちろん全力です。
フォーマッターを設定する
フォーマッターの設定自体は好みだと思いますが、デフォルト設定だとJavadoc形式のコメントに対して何もしてくれず、だいたいコメントが汚くなっていくのが常なので、それだけでも設定しておきましょう。
「Javadoc コメント・フォーマットを使用可能にする」「ブロック・コメント・フォーマットを使用可能にする」「ヘッダー・コメント・フォーマットを使用可能にする」あたりが重要です。
保存アクションを設定する
個人的にはeclipseの一番いい機能はこの保存アクションだと思います。ファイルの保存時に、フォーマットしてくれるのはもちろん、コードのクリーンアップまでやってくれます。
プロジェクトに厳密な規約がない場合は、他人の書いたソースコードを少し編集したときに、関係のないところまで自動変更されてしまうので注意が必要ですが、自分で書く分には常に有効にしておくべきです。
機能は全部使うといいでしょう。追加アクションも追加設定しましょう。
ここは好みやコーディング規約・開発スタイルの違いも出るかもしれませんが、ひとまず私の設定を解説します。保存アクションを厳しくしすぎると、作りかけのコードが掃除されてしまうので、いい感じに設定します。
コードスタイル
- if/while/for/doステートメントでブロックを使用 [on]
- Javaで書くようなものなら「常時」一択だと思います
- 'for'ループを拡張へ変換 [off]
- 数値for文による配列のインデックスアクセスを自動で拡張for文へ変換してくれますが、普通は意図的に使い分けているはずです
- 式で括弧を使用 [off]
- 括弧を付けてくれますが、これもかなり人間が意図すべきものだと思います
- 使用可能な場合、修飾子'final'を使用 [privateフィールド, パラメーター]
- 解析によってprivateフィールドにfinalを付けてくれるので、自分のコードのImmutable性が分かる便利な機能です。パラメーターはパラメーター代入を許さないのなら付けてよいと思います。それ以外のときやローカル変数は、書きかけのコードをちょっと保存したときにfinalが付いてストレスになります
- 関数型インターフェース・インスタンスへ変換
- これも普通はプログラマの意図を超えてるので変換しない方がいいと思います
コード編成
フォーマッターは一応は有効にしておきましょう。メンバーのソートは、ABC順にした方が見やすいときもあると思いますが、編集中にソートされるとかなり混乱するのでやめておいた方がいいでしょう。
メンバー・アクセス
- フィールド・アクセスに'this'修飾子を使用 [常時]
- フィールド変数へのアクセスに、
this.
を付けてくれます。IDEのカラーリングでも判別できなくもないですが、ローカル変数と見分けがつきやすく意外とありがたい機能です
- フィールド変数へのアクセスに、
- メソッド・アクセスに'this'修飾子を使用 [off]
- そういう流儀もあるのかも?
super
と使い分けることで意味が出ることが多いので、そのままで
- そういう流儀もあるのかも?
- 宣言されているクラスを修飾子として使用 [off]
- これも意図が崩れる可能性があるので微妙なところですね。ecjの設定で警告になるはずなので、そっちで検知しましょう
欠落コード
どういうシーンでも一番役に立つからだと思いますが、デフォルトで有効です。
不要なコード
- 未使用のインポートの除去
- 余計なインポートが存在すると依存関係で不幸になることもあるので、自動除去しましょう
- 未使用のprivateメンバーの除去 [off]
- だいたいの場合、書きかけのコードを保存時に消されて泣くのでoffにしておきます
- 未使用のローカル変数を除去 [off]
- 同上
- 不要キャストの除去 [on]
- いまどきはキャストをあえて書くことはほぼ無く、リファクタリングの結果不要になったコードだったりすると思います。遠慮なく消しましょう
- 不要な'$NON-NLS$'タグを除去
- NON-NLSを使っていれば、まあ
- 冗長な型引数を除去 [on]
- これを除去してコードの意味が変わるというようなことはありませんし、そこに意図があるということもないでしょう
- 冗長な修飾子を除去 [on]
- ちょっとわかりにくいかもしれないですが、例えばJavaではinterfaceのメソッドはすべてpublicになるので、publicをあえて書く必要はありません。が、そのことを知らないと、コードが逆にわかりにくくなるかもしれないので、これはポリシー次第でしょう
クリーンアップを設定する
クリーンアップは保存アクションを別に切り出したようなものです。保存アクションとおおよそ同じことができるので、たとえば、「未使用のローカル変数を除去」のような、普段動作すると困る機能をこちらではonにしておいて、実装がひと段落付いたところでクリーンアップしてみるなどで、使い分けることができます。
作ってみる
一通り設定が終わったので、作り始めましょう。
gradleを使う
今どきの主要言語にはライブラリやツールのソフトウェアリポジトリがあって、npmやpipのようなパッケージ管理ツールで依存関係込みで一発インストールできたりします。
もちろんJavaにもあります。Javaではmavenがそれに相当します。相当するのですが、古いJavaプロジェクトのお約束として、設定ファイルがXMLなので、昨今は設定ファイルがgroovyなgradleが好まれます。gradleはmavenリポジトリを利用して依存解決を行うことができるビルドツールです。
Pleiades All in Oneにはgradleプラグインbuildshipがすでに入っているので、すぐに使うことができます。
今からJavaで作るのであれば、何はともあれ、gradleプロジェクトとして作るべきです。ビルドツールですが、必ずしもgradleでビルドできる必要はなく、単純にライブラリのインストーラーとしての機能だけで極めて有用です。
作ったらプロジェクトのbuild.gradleを開きます。
mavenリポジトリがnpmリポジトリやpipリポジトリと最も異なるのは、著名なリポジトリが複数あり、独自のリポジトリを持つのもプロジェクト単位でリポジトリを設定するのも一般的というところです。
パブリックリポジトリはmaven centralとjcenterの二つが非常に有名です。
面倒なので、二つとも設定しておくとよいでしょう。ある程度のライブラリはだいたいどっちかにいます。
apply plugin: 'java-library'
repositories {
jcenter()
mavenCentral()
}
dependencies {
// 依存ライブラリは次のように指定します。
// デフォルトでbuildshipが生成する値ですが、単なるサンプルです。
api 'org.apache.commons:commons-math3:3.6.1'
implementation 'com.google.guava:guava:23.0'
testImplementation 'junit:junit:4.12'
}
mavenアーティファクト
mavenリポジトリでのライブラリ(アーティファクトと呼ばれます)の名前は、グループIDとアーティファクトIDからなります。たとえば、Javaでよく使われるguavaというライブラリは、グループIDcom.google.guava
のアーティファクトIDguava
になります。ざっくり言えば、だいたいの他の言語よりも長いライブラリ名になると考えておけばいいでしょう。
補完を使う
eclipseのようなIDEでは、補完機能が何より大事です。補完機能は単に変数名を展開するだけでなく、型を特定してimport文を書かせたり、mainメソッドを生成したりすることができます。(通常はimportは手書きしません)
lombokを使う
10年前、Java6が出た頃と今とで何がいちばんJavaのソースコードを変えたかというと、個人的には言語仕様のバージョンアップよりもlombokが当たり前になったことが大きいですね。
昔は、JavaらしいGetter/SetterやtoString()
/hashCode()
/equals()
はeclipseやツールによって生成するものでした。今は、アノテーションを書いてコンパイル時にlombokをコンパイラに組み込むと、lombokがコンパイル時にこっそり生成してくれます。生成されたコードを人間が扱う必要はないので、ソースコードをクリーンに保つことができます。
Pleiades All in Oneには最初から組み込まれているのでプロジェクトで使えるようにライブラリを依存関係に組み込めばすぐに使えます。
apply plugin: 'java-library'
repositories {
jcenter()
mavenCentral()
}
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.+'
}
lombokがないとき
public class Example {
private String familyName;
private String lastName;
public String getFamilyName() {
return familyName;
}
public String getLastName() {
return lastName;
}
public void setFamilyName(String familyName) {
this.familyName = familyName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public boolean equals(Object o) {
...
}
public int hashCode() {
...
}
public String toString() {
...
}
}
lombokがあるとき
@Data
public class Example {
private String familyName;
private String lastName;
}
これらは同じコードになります。
なんだか黒魔術のようにも見えますが、これはJavaコンパイラの仕様に沿った正当な拡張であり、単なるコードジェネレーターなので、実行時にも何のペナルティもありません。(ほとんどのケースで、実行時にlombokは不要です)
詳しくはLombok featuresを見てください。生成前後のコードを見比べると何が起きているかわかりやすいと思います。
SpotBugsを使う
SpotBugsは旧名FindBugsで、Javaの標準的な静的解析ツールです。(正確には、FindBugsがエタったため、フォークしたもの)
SpotBugsを使うと、nullチェック、リソースリーク、APIのエラーチェックミスなどを検出してくれます。
SpotBugs/FindBugsは、ほとんどJavaにおける一般論のみを検出するため、数多のJava静的解析ツールの中でも標準でまあ使うよねという位置にいて、JavaScriptのeslint、PythonのPEP8(チェッカー)ぐらいの地位です。
これもPleiades All in Oneに入っているため、ある程度プログラムを書いたタイミングなどで使うことができます。
JSR305を使う
あなたとJava、Javaとnullという格言があるぐらい(ない)のJavaですが、nullになる可能性のあるものはいつかnullになります。ecjでもSpotBugsでも簡単なnullチェックを行えるようになっていますが、どちらもシンプルな解析によるもので、プログラムの意図や複雑なコードは汲み取ってくれません(だんだん高度にはなっています)。
JSR305というJava標準になり損ねた仕様に準拠したライブラリを使うと、ある変数がnullになる可能性があるのかないのかの意図をアノテーションで示すことができます。もちろんあくまでアノテーションなので、コンパイルエラーにはなりません。(ecjではエラーにもできますが、あまりお勧めしません)しかし、SpotBugsはJSR305に対応しているので、nullチェックに意図を混ぜ込むことができます。
また、@CheckReturnValue
で戻り値を使うことを強制したり、不変であることを示す@Immutable
やスレッドアンセーフな@NotThreadSafe
、そのリソースが閉じられることを示す@WillClose
など、nullチェック以外にも有益なアノテーションが用意されています。
apply plugin: 'java-library'
repositories {
jcenter()
mavenCentral()
}
dependencies {
compile 'com.google.code.findbugs:jsr305:3.0.+'
// 慣れてきたらspotbusアノテーションも必要になるかもしれません
// compile 'com.github.spotbugs:spotbugs-annotations:3.1.+'
compileOnly 'org.projectlombok:lombok:1.18.+'
}
// 戻り値を使わないメソッド呼び出しに意味がない
@CheckReturnValue
public String getLastName() { ... }
// nullになることがあるので、戻り値をチェックする必要がある
@CheckForNull
public String getFamilyName() { ... }
...
public void test() {
getLastName(); // 戻り値を使っていないのでSpotBugsに怒られる
String f = getFamilyName();
String t = f.substr(0); // nullチェックしていないのでSpotBugsに怒られる
}
強めに設定する
慣れてくると(あるいはコードが小さいと)、SpotBugsはあまり多くを語りません。そういうときはすべてのカテゴリーで軽微な問題も報告してもらえるよう設定してみましょう。
ちなみに、「報告する最小ランク」はバー右端、20が一番報告が多くなる設定です。逆ではないので注意が必要です。
また、SpotBugsはプラグイン(たとえばfb-contrib)で検知機能を増やせるので、検討してもよいかもしれません。
そのほかのトピック
これで環境を設定して、開発をスタートすることができるようになったと思います。フレームワークを使っての具体的な実装などは、本記事では触れませんが、知っておくべき事項を軽く紹介します。
Javadoc
今どきはどんな言語でもxxxDocは書いていると思いますが、Javadocは忘れないように押さえておきましょう。eclipseでは、メソッドの前で、/**
と入力してenter
を押せばだいたいいい感じのテンプレートで生成してくれます。後はコード補完に任せておけば、それなりに最低限のJavadocは書けるようになるはずです。
public class Library {
/** // ←ここだけ入力すると生成されます
*
* @param t
* @return
*/
public boolean someLibraryMethod(int t) {
return true;
}
}
コーディング規約
Javaでスタンダードなフォーマットはタブ/インデントの扱いを除けば、Google Java Style Guide(本家) | (非公式和訳)をきっちり読んでおくのがよいと思います。
リフレクションとアノテーション
どっちかというとlombokよりも黒魔術に近いように思うのですが、あっちこっちのフレームワークやライブラリで基礎技術として取り入れられてるのがリフレクションです。
今どきの言語だとたいていはできるので、すんなり受け入れられるはずですが、Javaにおいては、ライブラリによってリフレクション実装レベルが違うというちょっと困った状況もあります。
アノテーションはリフレクションを支える目的で使われることが多くなってきました。そのため、アノテーションを見ると複雑なコードに見えて、警戒してしまうかもしれませんが、アノテーション自体は単なるラベルなので、惑わされないようにしましょう。
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class Example {
private String familyName;
private String lastName;
public static void main(final String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
Example e = new Example();
e.familyName = "姓";
e.lastName = "名";
System.out.println(mapper.writeValueAsString(values));
// {"familyName":"姓","lastName":"名"}
}
}
Jacksonを使う
JacksonはJavaでJSONを扱うまあまあデファクトスタンダードなライブラリで、リフレクションを理解するには格好の材料です。
jackson-annotationsとともに使ってみてください。
https://github.com/FasterXML/jackson-annotations
今どきのJava
大学でメモ帳でJavaを書かされてトラウマというのはJavaあるある話だったりしますが、そこで学ばされてるJavaがJava2時代だったりするのもお約束です。
Javaについてあまり知らなかったとしても少し調べてみると、Java8で比較的大きな変更があったことはご存知だったりするかと思います。そのJava8のリリースは2014年3月で、もう4年以上も前です。もちろん22年以上のJavaの歴史からするとたった4年なのですが、今から始めるのであれば、最低限ここからスタートすべきです。Effective Javaも今やJava9対応です。
Javaにはそれなりの歴史があるので、Javaアプレットやiモード時の生き残りの(まま更新できなかった)人や情報が、残念なことにオンラインプログラミング講座や学校のような場所でしばしば見られます。プログラミングを学ぶという意味ではそれでもよいのかもしれませんが、Javaを学ぶという意味では害悪と言えます。
古いコード
次のようなコードを見かけたらそっ閉じしてください。
- VectorやStackを使う
- ジェネリクスがない(型引数を付けない)。
ArrayList<String>
と書かず、単にArrayList
と書く - オートボクシングしない
- オーバーライドしているのに
@override
アノテーションがない - 拡張for文を使っていない
こういうコードを書く人はJava SE 1.5以前の人なので、もはや古代人です。この頃のJavaと今のJavaとでは、風景がだいぶ異なります。この頃のJava知識はむしろ忘れたほうがいいです。
次のようなコードは少し注意しましょう。
このようなコードは、Java6/7時代の可能性があります。まったく役に立たないことはないはずですが、ラムダ式を活用していることをあまり期待できません。
JVM
JVM生態系で生きていない人がJavaに入門した場合の難関、あるいはJavaの敷居の高さは、JavaというよりJVMの知識、JVMというよりは近代的なプロセス仮想マシンの知識が不足して困るということだと思います。JVMのことを語るときには、世代別GCぐらいは知っていて当然という顔で語られることが多いです。これまで使ってきた言語次第ではそもそもGCって何なんだってことにもなります。
だいたいの人はJavaを使いたいだけで、別にJVMを語りたいわけではないはずですが、JVMは起動時に利用可能なメモリ量を決める必要があり、それによってはストップザワールドという別に叫ぶ必要のない現象が発生したりするので、知っていなければならないシチュエーションはたびたび発生します。
ひとまず、コマンドラインでどういう機能オプションがあるかから追っていくのがいいのではないでしょうか。
java -XX:+PrintFlagsFinal -version
Effective Java
必読本です。ある程度Javaを書けるようになったら読みましょう。日本語版第三版が10月に発行されています。
おわり
おわり