職場で、今までC言語で長いこと仕事をしていた方にJava(Servlet)を教える機会があり、改めてJavaの特徴や理解しづらい部分を考えさせられたので、Javaの入門記事としてここにまとめてみようと思います。
なお、この記事は「プログラミング初心者」向けの記事ではなく、「C言語で普通に仕事をしている人」向けの記事です。そのため、プログラミング一般的な話(「変数とは?」等)はしません。
また、Javaの文法など、ググればすぐに出てくるような内容は省略します。良い参考サイトがある場合のみリンクを貼る程度としておきます。
この記事が、Javaという言語の特徴や分かりづらい部分を理解する手助けになれれば嬉しいです。
ソースコードを書く前に
まずは環境構築をしましょう。まっさらな状態のマシンでは、Javaのコンパイルどころか実行すらできない状態であることも珍しくありません。まずは、Javaを書いて実行するために必要な要素について説明します。
JDKとJRE
「Javaをダウンロード・インストールする」というとまず混乱の元になるのがJDKとJREです。JREだけ別にダウンロードすることもできますし、JDKをダウンロードするとその中にJREのディレクトリがあったりする、というのも分かりづらい原因になっています。
というわけで、ここで簡単に説明します。
JDK (Java Development Kit)
その名の通り、Javaの開発キットです。コーディングするときに使います。コンパイラやデバッガ、パフォーマンス分析ツールなどが含まれています。
ソースコードがコンパイルされるまでに必要なものだと覚えておくと良いでしょう。
(厳密にはJDKは後述するJREも含めたものまでを指しますが、覚えやすさという意味で「ソースコードがコンパイルされるまで」としています)
JRE (Java Runtime Environment)
やはりその名の通り、Javaの実行環境です。
Javaは後述するJVMによって、実行するマシンのOSなどの環境差分を吸収します。JREは、このJVMを実行するためのファイルなどを含んでいます。
コンパイルされたJavaを実行するためにはJREが必要、と覚えておけば良いでしょう。
というわけで、 JDK と JRE が何の頭文字を表しているかを知ってしまえばなんて事はないですね。これを覚えておけば、環境構築もスムーズにいくはずです。
Java SE と Java EE と Java FX
JDKをダウンロードしようと、OracleのJavaのページを開くと、Java SE, Java EE, Java FX といった項目が並んでいて、どれを選べば良いのか迷ってしまいます。
「JavaはJavaだよ!」と言いたいところですが、実は一言にJavaと言っても、用途によって必要となる種類が違います。ここでは、これらの違いについて説明します。
Java SE (Java Standard Edition)
Javaの最も基本的なAPIが含まれるJDKです。コンソール上で動作するアプリケーションやWindowアプリケーション等を開発する際はこれを使います。
Java EE (Java Enterprise Edition)
EEは、SEにServletプログラムを開発するためのAPIなどを追加したものです。ServletやJSPでサーバーアプリケーションを開発したい場合はこちらを選んでください。
Java FX
実は僕もJavaFXは全く触ったことがないので詳しい事は説明できないのですが、 Oracle の JavaFX Overview によると、グラフィックや音声メディア等を利用したリッチなGUIアプリケーションを開発するためのパッケージ群、だそうです。
画像やアニメーション、音声をメインとするGUIアプリケーションを開発したい場合はこちらを利用するようです。
ということで、一旦は「ServletをやるならEEを、そうでなければSEを」くらいに把握しておけば良さそうです。
ここまで、JDK / JRE, SE / EE / FX あたりの違いについては、以下の記事がわかりやすいです。
Javaのバージョン
Javaはなぜか、例えばバージョンが1.8のJavaのことを "Java8" と呼んだりします。
それによってJavaのメジャーバージョンが "8" であるかのような誤解を最初はしてしまいがちなのですが、実際はJavaのメジャーバージョンは "1" で、マイナーバージョンが "8" です。
(2016/08/12 修正)
Javaにはバージョンを表す言葉として「バージョン番号」と「バージョン文字列」があり、例えばJava SEのバージョン8であればバージョン番号は "Java SE 8"、対応するバージョン文字列は "1.8" となっています。最初はこの表記の使い分けによって混乱してしまいがちなのですが、同じバージョンに対して表記が2つ存在するのだと覚えておくと良いです。
詳しくは、以下のリンクを参照してください。
Java Platform, Standard Edition 8の名前とバージョン Oracle
(修正ここまで。@budougumi0617さん、ご指摘ありがとうございました!)
つまり、"Java 1.8" と "Java8" は同じものを指します。
特にIDEではこの表記が混在したりしていて混乱したりしますので、注意が必要です。
ソースコードと実行ファイル
JavaはC言語と違い、ソースコードをコンパイルしてもそれがそのまま実行ファイルとなるわけではありません。コンソールプログラムにしろServletにしろAndroidにしろ、Javaを実行するには JVM というVM(仮想マシン)が必要です。
仮想マシンと言っても、それほど難しく考える必要はありません。実行自体はコンパイルしたクラスを引数に java コマンドを実行するだけです。
例えば、HelloWorld.java というソースファイルをコンパイルして実行する流れは以下のようになります。
# ソースファイルのコンパイル
$ javac HelloWorld.java
# ディレクトリ内のファイルを表示(.classファイルが生成されている)
$ ls
HelloWorld.java HelloWorld.class
# 実行(.classはつけない)
$ java HelloWorld
Hello World!
C言語はコンパイルした時点でマシン語になりますが、JavaはJVMが読み取ることのできる .class という形式のファイルが出力されるのみです。.classファイルはテキストファイルである.javaファイルを単純にバイナリファイルに変換した程度、と思っておけば良いかと思います。
なぜこのような手順を踏むかというと、先ほどJREの項目で説明した通り、OSなどの実行環境の差分をJVMが埋めるためです。
C言語はソースコードを書いた人がそれを実行したい環境に合わせてコンパイルする必要がありますが、Javaではそのような必要はありません。実行環境を気にせずソースコードをコンパイルし、各々のマシンが各々の事情に沿ってインストールしたJVMで実行します。
プログラマは自分の書いたプログラムがどのマシンで動作するかなんて気にする必要は無いのです。このため、Javaはプラットフォーム非依存な言語であると言われます。
ちなみに、Scala や Kotlin など、「JVM言語」と言われる言語は、どれもコンパイルされると .class ファイルとなり、このJVMで実行される、という特徴を持ちます。
最終的な実行環境はJVMなので、Javaと混在させやすい(一度Javaで書いたソースコードを再利用しやすい)といったメリットがあったりします。
IDEについて
Javaの開発で最もよく使われるのは Eclipse です。他にも IntelliJ や NetBeans などの選択肢がありますが、IntelliJは有料、NetBeansはEclipseよりもややマイナーで、プラグインや情報量も少ないですので、ひとまず最初はEclipseから初めてみるのが良いでしょう。
Eclipseはオープンソースですので、誰でも無料で公式サイトよりダウンロードが可能です。
Windowsを使っている場合は、Pleiades という 日本語化 & 各種便利プラグイン & JDK6,7,8 & Tomcat6,7,8 が入っている、「全部入り」のAll in Oneパッケージが使用可能です。
多少ダウンロードに時間がかかりますが、とりあえずこれさえ落としておけばJava & Servlet開発に必要なものは一通り揃っているので、All in Oneを使うのも良い選択肢です。開発用PCの事情に合わせて使い分けてください。
ちなみに、勉強に時間が使える状況であれば、IDEを使わずにテキストエディタのみで簡単なプログラムを書いて、コマンドラインで実行してみることをオススメします。
Javaの文法や、コンパイル -> 実行の流れや仕組みがよく理解できます。
いざ、コーディング
環境が整ったら早速コードを書いてみましょう。基本的な文法や書き方は Java Tutorials 等にお任せし、ここではC言語の慣習や考え方との違いを中心に説明します。
とりあえず書いてみる
Javaでは必ずクラスを作らなければなりません。
"HelloWorld!"の表示だけだとしても、 int main() 関数を書くだけで良いC言語と違い、
HelloWorld.javaファイルを作成して 、class HelloWorld でクラスを定義し(クラス名は必ず.javaファイル名と同じにしてください)、public static void main(String args[]) で mainメソッドを定義し、最後に System.out.println("Hello World!"); を書き実行すると、ようやくHello World完了です。
Javaはオブジェクト指向に特化した言語
よく言われるように、Javaはオブジェクト指向に特化した言語です。 int や char などのプリミティブ型以外は、すべてはクラスとして定義され、役割や振る舞いが明確に分割されます。
それによって、一度書いたソースコードの再利用性を高めたり、複数人での開発効率を上げたり、さらにはオープンソースのライブラリを作りやすく、取り入れやすいという特徴を実現しています。
そのため、僕の知る限り HelloWorld するために必要な記述量は他の言語に比べて圧倒的に多くなってしまっていますが、システムが大きくなればなるほどクラス分けのメリットも大きくなります。クラスについての詳細は後述します。
クラスとインスタンス(とオブジェクト)
Javaについて調べていると、「クラス」「インスタンス」「オブジェクト」という言葉が頻繁に出てきて、混乱することがあるかもしれません。このあたりはJavaを理解する上でとても重要な要素なので、ここで簡単にまとめておきます。
クラス
クラスは、まさにプログラム上の定義そのものです。 class Food {} のように宣言し、その中にメンバ変数やメソッドなどを定義します。あえてC言語の文法に例えるなら、構造体が関数を持っているイメージでしょうか。
オブジェクト指向では、ひとつのクラスは人間が認識できるひとつのモノであることが望ましいとされています。
Food(食べ物)クラス、FoodListView(食べ物の一覧を表示する)クラス、CsvFileAccessor(CSVファイルにアクセスする)クラス、など、現実でのひとつの物体であったり、アプリケーション上のひとつの画面だったり、プログラム上のひとつの役割であったりするものをひとつのクラスとしてまとめます。
便利だからと言って、FoodAndListView(食べ物に関する値を定義し、そのリストを保持して表示するクラス)のように2つの役割を1つにまとめてしまわないように注意してください。
まとめてしまうと、例えば「Foodクラスのデータをサーバーに送信したい」、といった、まとめたうちの片方の概念しか必要としない要求が出てきた際に再利用できないクラスになってしまいます。
Javaではプログラムの実行効率よりも、役割分担 / 再利用性が優先されることが多いです。
インスタンス
インスタンスは、プログラムの実行中にクラスが new されたときに生成される(メモリ領域が確保される)ものです。
クラスで定義されているメンバ変数は、インスタンスごとに別のメモリ領域が確保されるため、例えば「Food クラスを2回 new してインスタンスを2つ生成し、片方のインスタンスの name 変数には "Ramen" を、もう片方のインスタンスの name 変数には "Curry" をセットする」といったように、Foodクラスを雛形として、それぞれ別の値を持ったインスタンスを作ることができます。
オブジェクト
「オブジェクト」という言葉を使う際は少し注意が必要です。
というのも、Javaの話の中で出てくる「オブジェクト」という単語は「インスタンス」の意味だったり「クラス」の意味だったり、もう少し漠然としたオブジェクト指向的な意味でのオブジェクトだったりと、人や場合によって意図するところが曖昧だからです。
Javaのプログラムそのものの話をする上では、「クラス」と「インスタンス」の違いが理解できていれば問題ありませんので、まずはその2つを覚えておくと良いかと思います。(この記事でも「オブジェクト指向」という用語以外では「オブジェクト」という言葉は使わないように意識しています)
コーディングする上で気をつけたいこと
ここまで説明した通り、JavaはC言語とは違った特徴や考え方を持っています。
つまり、読みやすさや実行効率、拡張性など、コーディングする上でのトレードオフを考える際に優先されるものもC言語とは違ってきます。
ここでは、Javaをコーディングする際に注意すべきことをまとめます。
なお、Javaをコーディングする際の注意事項については記事がとても勉強になります。併せて読んでみてください。(一部こちらの記事を参考に、この記事を書かせていただいています)
メソッドの設計
C言語では、ポインタで受け取った引数を関数内で編集し、戻り値として関数の実行結果コードを返す、という作りが良く使われるかと思います。(上記記事の「出力パラメータ」「処理状態返却」の項目)
しかし、Javaでそのようなメソッドの作り方はしない方が良いです。メソッドの呼び出し側は引数として渡したインスタンスがメソッド内でどう変更されるのか、そもそも何かを変更されるのか / されないのかをメソッドの実装を見て理解しないといけないからです。
Javaに限らず、引数で受け取ったインスタンスを直接変更してしまうメソッドを「破壊的な」メソッドと呼び、避けられる傾向にあります。
基本的には、引数で受け取ったインスタンスは変更しない、結果は必ずメソッドの戻り値として返却する、という作りを意識してください。
変数は使用する直前で定義する
C言語では、変数は関数の先頭で定義しなければなりませんでした。
しかしJavaにはそのような制約はありません。むしろ、使う直前に、必要最低限のスコープでのみ参照可能となるように定義するのが望ましいです。
スコープが限られていれば、後々ソースコードを読む人がその変数の使われ方を調べる際に必要最低限の範囲を調べれば良くなり、負担を最小限に抑えることができます。
(2016/08/12 追記)
変数宣言をブロックの先頭で行わなければならなかったのは、厳密には1989年策定のANSI-Cの仕様で、1999年に策定されたC99ではそのような制約はないそうです。それにより、この項目でJavaの特徴として書いたような「変数は使用する直前で宣言する」は、C言語でも同様に考えられているようです。
@SaitoAtsushi さん、ご指摘ありがとうございました!
(追記ここまで)
命名規則を遵守する
メソッド名や変数名など、Javaには明確な命名規則があります。
命名規則に反したからプログラムの挙動が変わる、ということはありませんが、Javaの開発者はこの命名規則でプログラムが書かれていることを前提にソースコードを読みます。
ソースコード中に "Food" という記述が出てきたらそれは必ずクラス名であると判断しますし、 "food" であれば変数名であると判断します。
これに反した書き方をしてしまうと、他の開発者がソースコードを理解する妨げになり、結果としてそれがバグの原因となることもありますので、常に意識してコーディングするようにしてください。
ポインタ
よく「Javaはポインタがないから楽だよ」とかって言われたりしますが、僕は半分正解で半分嘘っぱちだと思っています。
確かに文法としてのポインタはないため、ポインタを直接操作するようなことはできません。
しかし、インスタンスを生成して変数に格納すれば、その変数が示すものはそのインスタンスそのものではなくインスタンスへのポインタですし、それをメソッドの引数として渡すということは、C言語で言うポインタを引数に渡すこととほぼ同等です。
このことを理解せずにコーディングしてしまうと、例えば以下の例のようにインスタンスをメソッドに渡し、メソッド内でそのインスタンスが編集された場合にメソッドの呼び出し元のインスタンスが変更されている、といった挙動が理解できなくなってしまいます。
public static void main(String args[]) {
Food ramen = new Food("ramen"); // "ramen"という名前を持ったFoodインスタンスを生成
FoodPrinter.printDecoratedName(ramen); // 名前を綺麗に出力したい
System.out.println(ramen.getName()); // "ramen" ではなく "***** ramen *****" が出力される
}
/**
* 引数で受け取った食べ物の名前を、ちょっと飾り付けして出力するメソッド
*/
public static void printDecoratedName(Food food) {
food.setName("***** " + food.getName() + " *****"); //飾り付け
System.out.println(food.getName()); // -> ***** ramen *****
}
ポインタの概念が理解できていれば「こんなの参照を渡してるだけなんだから当たり前じゃん」という話なのですが、ポインタを理解しないままJavaを書いてしまうと良く踏んでしまうミスのひとつだったりします。
Javaだからポインタのことは考えなくて良い、ではなく、Javaだからこそ自分が今扱っている変数はメモリのどこを指しているのか(どの変数が指しているメモリと同じなのか)を意識して書くことが大切だと思います。
一旦まとめ
まだ書き足りない部分はありますが、ひとつ書いてしまうとあれも、これもとなってしまい記事が終わらなくなってしまうので、一旦ここまでとさせていただきます。
僕自身もまだJavaを勉強中であることや、C言語に至っては会社の研修で3ヶ月学んだ程度、ということもあり、内容に誤りやツッコミどころがある可能性があります。
そのような点がありましたら、是非コメントにてご指摘いただければと思います。コメント欄での議論も含めて、これからJavaを始める方への参考記事にできればと思います。
また、不明な部分についてもコメントいただければと思います。必要に応じて別の記事を書いたり、この記事に追記したいと思います。