3. クラス
Java アプリケーションは必ず 1 つ以上のクラスから構成されます。たとえ "Hello, world"
の一文を表示するだけの簡単な処理であっても、それが Java のアプリケーションである限り、必ずいずれかのクラスに属しています。
3.1. クラスの基本構造
3.1.1. Hello, world
単に "Hello, world"
と表示するだけの Java アプリケーションは、以下のクラスで構成されます。クラス名 (Hello
) とパッケージ名 (app
) には Java で使用できる任意の名前を指定可能です。
/*
* Hello.java
*/
package app;
/**
* はじめての Java プログラムです。
*/
public class Hello {
/**
* プログラム起動時に最初に実行されるメソッドです。
*
* @param args コマンドライン引数
*/
public static void main(String... args) {
// 標準出力に "Hello, world" を表示します。
System.out.println("Hello, world");
}
}
上記のクラスは、static
メソッド main
を持ち、その中で Java SE の標準 API である System.out
クラスの println
メソッドを呼び出して、"Hello, world"
という文字列を表示しています。
3.1.2. コメント
前節で示したソースコードのうち、以下に該当する部分はコメントであり、プログラムの動作には影響しません。Java には以下の 3 種類のコメントが用意されています (いずれも前節のソースコードに含まれています)。
// ~
から始まり、その行の末尾まで - 1 行コメント (BCPL 由来、C++ 標準)/* ~ */
で囲まれた部分 (複数行に渡っても可) - ブロックコメント (PL/I 由来、C 標準)/** ~ */
で囲まれた部分 (複数行に渡っても可) - ドキュメンテーション・コメント
1 と 2 は通常のコメントで、他のプログラミング言語のコメントと同じものです。Java の構文は可読性が高いため、これらのコメントを書く機会は少ないでしょう。コメントを書かないと理解できないようなプログラムは、設計や構成管理などに何らかの問題を抱えていると言えます。
3 はドキュメンテーション・コメント (Javadoc コメント) と呼ばれるものです。この形式のコメントでクラスの仕様を記述しておくと、前章で紹介した JDK の javadoc
ツールを用いて整形されたドキュメントを生成することができます。通常のコメントは少なければ少ないほど良いですが、ドキュメンテーション・コメントについては十分な量を記述するように心がけましょう。
- ドキュメンテーション・コメントの書式については JDK のマニュアル を参照してください。
- ドキュメンテーション・コメントの良い書き方については、英国の Java Champion である Stephen Colebourne のブログ記事 Javadoc coding standards に丁寧な説明があります。原文は英語ですが、Stephen が開発を指揮した Date and Time API の Javadoc で実践されている内容ですので、一見の価値はあります。
以下、コメントの使用例として不適切な例を挙げます。現時点でプログラムの内容はわからなくても構いませんが、このようなコメントは決して使用してはいけません。
/*
* 修正履歴
* YYYY/MM/DD 初版 名前 概要
* YYYY/MM/DD 0.1版 名前 概要
* YYYY/MM/DD 0.2版 名前 概要
* YYYY/MM/DD 0.3版 名前 概要
*/
public class OldCode {
private int num1;
private String s1;
private String s2;
public OldCode(int num1, String s1, String s2) {
this.num1 = num1;
// this.s1 = s1;
// this.s2 = s2;
// START YYYY/MM/DD 名前
this.s1 = s2;
this.s2 = s1; // TODO このあたりの意図がよくわからない
// END YYYY/MM/DD 名前
}
// public int inclement(int num2) {
public int getNum1Add (int num2) {
// num1 と num2 の和を返す
return num1 + num2;
}
}
上記で挙げたようなコメントは国内の開発プロジェクトではよく見かけますが、コメント本来の用途を考えた場合にはすべて誤った使い方と言えます。実際に海外 (米国・英国) では上記のようなコメントが使用されることはありません。
- ソース・ファイルの先頭にコメントで変更履歴を書く → 現在はバージョン管理システム (SVN、Git) が普及し、そちらの方で変更履歴を差分ごと管理できます (必要であれば過去の版にも戻せます)。バージョン管理システムの使用が当たり前となっている Java では二重管理となり意味がありません。
- 変更箇所の開始と終了をコメントとして記載する → バージョン管理システムは過去のすべての差分を保持するため、このようなコメントは冗長です。特に変更箇所がネストしたときに開始と終了が交錯してしまい、ソース・コードのレビュー時にレビュワーがひどい目を見ます。
- 「// this.s1 = s1;」「// this.s2 = s2;」などコメントアウトしたコードが残り続ける → コメントアウト自体はデバッグ時によく用いられる手法ですが、デバッグ終了後は消した方が無難です。バージョン管理システムを使用していれば、過去のコードに戻すことは容易です。
- 「TODO このあたりの意図がよくわからない」 → 意図がわからないプログラムは絶対に書いてはいけません。
- 「num1 と num2 の和を返す」 → プログラムに書いてあることをそのままコメントで復唱するのはやめましょう。さらに、改修時にプログラムとコメントに差異が生じた場合には、どちらが正しいか判断がつかなくなります。
3.1.3. クラスの基本構造
クラスは、大雑把には以下のような書式で定義することができます。
package package_name ;
[ import package_name . { ClassName | * } ; [ import ... ; ] ... ]
// ClassName : クラス名
[ public ] [ final ] class ClassName {
// コンストラクタ定義
[ constructor [ constructor ... ] ]
// フィールド定義
[ field [ field ... ] ]
// メソッド定義
[ method [ method ... ] ]
}
package package_name
(package_name
: パッケージ名) はこのソースファイルで定義するクラスが所属するパッケージを表すもので、package
宣言と呼びます。package
宣言は、デフォルト・パッケージ (無名パッケージ) を使用する場合には不要ですが、デフォルトパッケージの使用は推奨されていないため、ほぼ必須と考えた方が良いでしょう。
package
宣言に続き、クラスのパッケージ名を省略するための import
宣言が続きます。import
宣言は以下のいずれかの書式で、複数宣言することができます。
import package_name . ClassName ;
- クラス
ClassName
だけパッケージ名package_name
を省略する
- クラス
import package_name . * ;
- 指定したパッケージの全クラスのパッケージ名
package_name
を省略する
- 指定したパッケージの全クラスのパッケージ名
アプリケーションで実装するクラスは public
から書き始める形で定義しますが、ライブラリの内部実装など特殊なクラスでは public
を付加しない場合があります。public
を付加しないクラスは同じパッケージ内のクラスからのみアクセス可能となります。これは次節以降で取り上げるフィールド、メソッド、コンストラクタの「スコープ」と同じものですが、クラスに関しては public
または省略 (別名をパッケージ・プライベートと言います) の 2 種類のみ選択可能です。
クラスは継承することが可能です (クラスを継承する書式は後述します) が、final
を付加することでそれ以上の継承を禁止することができます。クラスの継承を禁止するケースとして、以下のものが挙げられます。
- 初期化時にクラスの状態を設定した後は変更できなくする「イミュータブル (不変)」なクラスにする場合。Java SE 標準 API では文字列を表す
String
クラスや日付を表すLocalDate
クラスなど。 - すべて static メソッドだけで構成されたユーティリティクラスとする場合。Java SE 標準 API では数学関数を集めた
Math
クラスやコレクションを操作するCollections
クラスなど。
クラスには状態を表す「フィールド」、操作を表す「メソッド」、インスタンスの初期化を行う「コンストラクタ」をそれぞれ定義することができます。
3.2. フィールド
フィールドはクラスの状態、すなわちデータ構造を表現するための変数です。フィールドは、static
フィールドを除き、インスタンスごとに独立した値を保持します。フィールドの定義は、基本的には以下の書式に従います。
[ public | protected | private ] [ static ] [ final ] Type field [ = initValue ] ;
フィールド名については慣習としてクラス名と同じくキャメル・ケースが用いられますが、クラス名の先頭が大文字に対して、フィールド名の先頭は小文字 (例: fieldName
) になります。
3.2.1. フィールドのスコープとその選び方
スコープは、他のクラスからのアクセス許可について宣言したもので、public
、protected
、private
または省略 (別名をパッケージ・プライベートと言います) のいずれかとなります。スコープの一覧を下表に示します。
スコープ | アクセス許可の範囲 |
---|---|
private |
宣言したクラス内のみ |
protected |
宣言したクラスとそのサブクラス |
public |
すべてのクラス |
(省略) | 同じパッケージのすべてのクラス |
フィールドの操作は、そのクラスのメソッドからのみ行うのが原則であり、フィールドのスコープは必然的に private
となります。それ以外のスコープは言語仕様上は存在しますが使用すべきではありません (例外は後述します)。そして、フィールドに対する読み取りや書き込みはメソッドなどを介して行います。このようにフィールド (インスタンスの状態) を外部から遮蔽することを「カプセル化」といいます。カプセル化は、フィールドに対する不必要なアクセスを制限することにより、その状態を正常に保つことを目的としています。
Java のプログラミングではフィールドに対する読み取りや書き込みに特化したメソッドを用意することが多く、これをアクセサ・メソッドと呼んでいます。アクセサ・メソッドのうち読み取りに特化したものを getter メソッド、書き込みに特化したものを setter メソッドとして区別しています。フィールドに対して読み取りと書き込みの両方を許可する場合は getter メソッドと setter メソッドを、フィールドに対して読み取りのみを許可する場合には getter メソッドのみを、それぞれ用意します。
getter メソッドという呼び名は、メソッド名が
get
(対応するフィールドがboolean
の場合はis
) で始まることに由来します。同様に、setter メソッドという呼び名は、メソッド名がset
で始まることに由来します。これらは JavaBeans という仕様で定められた命名規約が普及したものです。
なお、Visual Basic にはフィールドに対する読み取り/書き込みを制限する言語仕様である「プロパティ」が備わっています。Java にはプロパティのような仕様が存在しないため、アクセサ・メソッドで代用しています。
3.2.2. static および final 宣言子
static
を付加した場合は、そのフィールドは static
フィールドとして宣言されます。static
フィールドはインスタンスを生成しなくても読み書き可能なため、意図しないタイミングで値を設定されるなどバグの原因となります。static
フィールドに関してはカプセル化の効果も期待できません。そのため static
は単独では使用せず、final
と組み合わせて読み取り専用とします。
final
を付加した場合は、設定した初期値を変更できません。初期値は上記の例のようにインラインで指定するか、後述のコンストラクタによる初期化時に設定することになります。final
を付加したにもかかわらずいずれの方法でも初期値を指定しなかった場合は、コンパイルエラーとなり実行できません。
static
とfinal
を組み合わせた場合の初期値は、インラインで指定するか、特殊なstatic
ブロック内で設定します。static
ブロックについては割愛します。
static
と final
の組み合わせに public
スコープを加えると、すべてのクラスから参照可能となる定数のようなフィールドが実現できます。以降、便宜上「定数フィールド」と呼ぶことにします。フィールドのスコープは原則 private
としますが、定数フィールドとして扱う場合には例外的に public
とします (要否は別として、protected
または private
スコープの定数フィールドも定義することは可能です)。
// 定数フィールドの宣言 (Java SE 標準 API; BigDecimal クラスの ROUND_UP 定数)
// インスタンスを生成しなくてもアクセス可能 & 変更不可
public static final int ROUND_UP = 0;
定数フィールド名には、上記
ROUND_UP
のようにすべて大文字のスネーク・ケースが使用されることもあります。これは C の名前付き定数が大文字のスネーク・ケースで命名されていた慣習を踏襲したものです。Java 標準の API に含まれる定数フィールドのうち初期から存在するものは C 風の命名で統一していましたが、現在ではキャメル・ケースによる命名も混在するようになってきています。
定数フィールドとするインスタンスはイミュータブル (不変) なクラスであるべきです。プリミティブ型とそのラッパークラス、列挙型、String
、BigDecimal
などはいずれもイミュータブルです。ミュータブルな (可変) なクラスでは定数フィールドにしたとしてもメソッド呼び出しでインスタンスの状態が変更される可能性があるためです。既存のコードで定数フィールドにされていることが多い SimpleDateFormat
や DecimalFormat
は、実際にはミュータブルなクラスであり、定数フィールドでありながら意図しないタイミングで状態が変わってしまう (最悪の場合には破壊されてしまう) 危険性がありますので注意してください。
3.2.3. フィールドへのアクセス
フィールドへのアクセスはフィールド・アクセス式と呼ばれます。フィールド・アクセス式の書式は以下のいずれかとなります。
- 書式 (1):
instance . field
instance
: インスタンス、field
: フィールド名
- 書式 (2):
ClassName . field
ClassName
: クラス名、field
:static
フィールド名
フィールド・アクセス式はメソッドの処理記述内で使用されます。他のクラスのフィールドへのアクセス可否は、対象となるフィールドに設定されたスコープによります (例: private
スコープであれば宣言されたクラスのメソッドからのみアクセス可)。また、final
が付加されたフィールドは読み取りのみ可能であり、それ以外は読み取り・書き込みともに可能です。
フィールド・アクセス式の読み取りは、文においてフィールド・アクセス式が評価されたときに行われます。また、フィールド・アクセス式への書き込みは、フィールド・アクセス式に対する代入式が評価されたときに行われます。文と式、式の評価については次章にて取り上げますが、具体的には以下のようなコードになります。
// (1) フィールド・アクセス式の読み取り (static フィールド)
// フィールド・アクセス式 BigDecimal.ROUND_UP をメソッド System.out.println の引数として設定 (= 式の評価)
System.out.println( BigDecimal.ROUND_UP )
// (2) フィールド・アクセス式への書き込み
// フィールド・アクセス式 instance.fieldValue に文字列リテラル "Hello" を代入 (= 代入式の評価)
instance.fieldValue = "Hello";
3.3. メソッド
メソッドは、クラスの状態に対する操作を表します。メソッドは、static
メソッドを除き、インスタンスのフィールドやメソッドに対して作用します。メソッドの定義は、基本的には以下の書式に従います。
// method : メソッド名、arg : 仮引数、ReturnType : 戻り値の型
[ public | protected | private ] [static] [final] ReturnType method ( [ arg [, arg ... ] ] ) {
// メソッドの処理記述
}
メソッド名もフィールド名と同じく、先頭を小文字としたキャメル・ケースとする慣習があります。Java ではフィールドやメソッドといったクラスのメンバーの名前を、先頭を小文字とするキャメル・ケースで統一する傾向にあります。クラスのメンバーであっても先頭大文字のキャメル・ケースを使用する慣習を持つ Visual Basic や .NET Framework との文化の違いです。
メソッドの処理記述は { }
で囲まれた部分で「ブロック」と呼ばれます。ブロックにはローカル変数宣言や各種制御文などを記述することができます。詳細は 4 章のブロックの項目を参照してください。
3.3.1. メソッドのスコープとその選び方
スコープは、他のクラスからのアクセス許可について宣言したもので、フィールド同様に public
、protected
、private
または省略 (別名をパッケージ・プライベートと言います) のいずれかとなります。
スコープ | アクセス許可の範囲 |
---|---|
private |
宣言したクラス内のみ |
protected |
宣言したクラスとそのサブクラス |
public |
すべてのクラス |
(省略) | 同じパッケージのすべてのクラス |
メソッドのスコープには原則として public
または protected
を選択します。他のスコープを使う必要性は、特にアプリケーションではないでしょう。
public
メソッドはすべてのクラスから呼び出されることから、クラス間でのデータの受け渡しや処理を受け持ちます。そのため、明確に仕様を決めて周知しておくことが重要です。public
メソッドの仕様記述にはドキュメンテーション・コメント (Javadoc コメント) が有益であるため、必要な情報はすべてドキュメンテーション・コメントで仕様化しておきましょう。public
メソッドの安易な変更は他のクラスやメソッドに悪影響を及ぼし、コンパイル時や実行時にエラーを引き起こす原因ともなるため、現に慎んでください。
protected
メソッドは宣言したクラスとそのサブクラスでのみ呼び出し可能です。変更時の影響は宣言したクラスとそのサブクラスに限定されるため、public
メソッドほど厳格に運用する必要はありません。ただし、protected
メソッドであってもドキュメンテーション・コメントが充実していると、サブクラスを実装する際に大きな助けとなります。
private
メソッドは宣言したクラス内でのみ呼び出し可能です。そのため、外部には公開されないブラックボックスとなり、少なくとも仕様化した処理の記述には向きません。C++ の inline
関数のようにクラス内部での処理の共通化に一役買う場面もありますが、どちらかというとソースコードの可読性を低下させるデメリットの方が大きいと思われます。少なくとも新規のコードで private
メソッドは使うべきではありません。
パッケージ・プライベートのメソッドは、主にライブラリやフレームワークを開発する際に利用されます。ライブラリやフレームワーク全体で利用するがアプリケーションからは呼び出して欲しくない内部実装のメソッドについて、パッケージ・プライベートを選択する場合があります。アプリケーションではまず使うことのないスコープでしょう。
3.3.2. 引数と戻り値
メソッドには入力にあたる「引数」と、出力に当たる「戻り値」があります。引数はパラメータと呼ばれることもあります (他の言語における呼び名が Java でも定着した事例です)。Java のメソッドでは任意の個数 (0 個以上) の引数と、0 個または 1 個の戻り値を持ちます。引数はさらに、メソッド定義で仮置きする「仮引数」と、メソッド呼び出し側で設定する値である「実引数」とに分類できます。
Java のメソッドの仮引数は、以下のルールに基づいて宣言します。
- 仮引数の書式
[ final ] Type var
(Type
: データ型 (クラスまたはプリミティブ型)、var
: 仮引数名)- 仮引数のリスト: 仮引数を
,
(カンマ) で区切ったもの - 仮引数のリストの例:
int year, int month, int dayOfMonth
- 仮引数はメソッド内ではローカル変数 (4 章) 扱いとなります。
- 仮引数のリスト: 仮引数を
- 最後の仮引数を
[ final ] Type... var
とすることで「可変長引数」を実現できます。- 可変長引数は対応する実引数を 0 個以上任意個指定できます。
- 可変長引数となる仮引数は、内部的には配列 (
[ final ] Type [] var
) として扱われます。実引数で配列を指定することも可能です (配列については 11 章参照)。
- 仮引数のリストはメソッド名に続く
( )
内に記述します。引数を持たないメソッドについては( )
内に何も記述しません。ただし、Visual Basic と異なり( )
を省略することはできません。
戻り値には名前がなく、データ型 (戻り値の型) のみを指定します。戻り値を持たない場合はデータ型を void
とします。
Java では仮引数のリスト (のデータ型) が異なる同じ名前のメソッドを複数定義することができます。これをメソッドの「オーバーロード」といいます。なお、仮引数のリストは同じで戻り値の型が異なる同じ名前のメソッドは定義することができないため注意してください。メソッドのオーバーロードは、操作の意味合いは同じだが引数のパターンが異なるものを同じメソッド名でまとめるために使用します。
言語仕様上は仮引数のリスト (のデータ型) が異なればメソッドのオーバーロードは可能ですが、操作の意味合いを無視してオーバーロードを濫用すると、可読性を大幅に低下させることになりますので注意してください。
メソッドのオーバーロードは主にライブラリ向けの機能であり、アプリケーションで定義する機会はあまりありませんが、アプリケーションからライブラリのオーバーロード・メソッドを呼び出す機会は多いため、覚えておきたい機能です。
(例) メソッドのオーバーライドが許される場合
public void doProcess(int value) { ... }
// OK : 仮引数のリスト (のデータ型) が異なる、仮引数の個数を変えている
public void doProcess(int value1, int value2) { ... }
// OK : 仮引数のリスト (のデータ型) が異なる、仮引数のデータ型を変えている
public void doProcess(double value) { ... }
(例) メソッドのオーバーライドが許されない場合
public void doProcess(int value) { ... }
// NG : 仮引数のリスト (のデータ型) が同じ、仮引数名だけ変えてもダメ
public void doProcess(int i) { ... }
// NG : 仮引数のリスト (のデータ型) が同じ、戻り値の型が異なるだけではダメ
public int doProcess(int value) { ... }
3.3.3. static および final 宣言子
static
を付加した場合は、そのメソッドは static
メソッドとして宣言されます。static
メソッドはインスタンスを生成しなくても実行可能ですが、代わりにインスタンスのメソッドやフィールドに直接触れることもできないため、その用途は比較的限られます。
static
メソッドの主な用途は「Factory Method パターン」というテクニックを用いたインスタンス生成メソッド (ファクトリ・メソッド) を用意する場合です。実装の複雑なクラスでは後述のコンストラクタだけでは初期化処理が煩雑になる (場合によっては完結しない) ため、static
メソッドに一連の初期化処理を閉じ込めるテクニックが多用されます。具体的には、インスタンスを生成せずに実行可能な static
メソッドを用いて、インスタンスの生成と追加の処理 (メソッド呼び出し) を行います。
(例) ファクトリ・メソッドの例 (Java SE 標準 API より、LocalDate
クラス)
public static LocalDate of(int year, int month, int dayOfMonth) {
// ファクトリ・メソッド; インスタンスを生成しなくてもアクセス可能
// ここで LocalDate のインスタンスを生成する
// インスタンス生成後に private メソッド呼び出しによる追加の処理が可能
...
}
public static LocalDate ofYearDay(int year, int dayOfYear) {
// ファクトリ・メソッド (異なる書式)
// ここで LocalDate のインスタンスを生成する
// ファクトリ・メソッドはコンストラクタと異なり名前の制約を受けず、必要なだけ定義可能
// 実際、LocalDate をはじめ java.time パッケージの API はファクトリ・メソッドを豊富に用意している
...
}
final
を付加した場合は、サブクラスでメソッドをオーバーライドできなくなります。アプリケーションのクラスは継承することが少ないため、メソッドも積極的に final
を付加するべきとする意見もあります。ただし、実際には Java EE 標準 API の一部が内部処理としてアプリケーション独自のクラスをさらに継承し、オーバーライドしたメソッドの中から元のメソッドを呼び出すようなコードを自動生成する場合があります。
Java EE の CDI でインターセプター (共通化された前処理・後処理) を使用するとほぼ確実にそのような内部処理が行われます。
そのため、クラスの継承を行わない、あるいは継承してもメソッドのオーバーライドを禁止して大丈夫という確信がない限り、final
は付加しない方が安全です。なお、クラス自体に final
を付加した場合はクラスの継承自体が禁止されますので、各メソッドへの final
付加は意味をなさなくなります。
3.3.4. main メソッド
Java アプリケーションのエントリポイントは、任意のクラスに定義された main
メソッドです。main
メソッドの書式は Java の言語仕様であらかじめ決められていて、下記のようになります。
public static void main(String[] args) {
// 処理記述
}
String[] args
にはアプリケーション実行時のコマンドライン引数が格納されます。これには別解があり、引数のリストの宣言 String[] args
を可変長引数形式 String... args
に置き換えることもできます。String... args
の方が新しい書式であり、意味的にもコマンドライン引数の姿に近いために好まれる書き方でもあるのですが、コンパイルするといずれの書式も String[] args
に変換されてしまうため、最終的には好みの問題になります。
アプリケーションの複数のクラスが main
メソッドを持つ場合、そのアプリケーションには複数のエントリポイントが定義されていることになります。アプリケーションのエントリ・ポイントは 1 つに集約するように設計することが多いため、複数のクラスが main
メソッドを持っているアプリケーションは設計に違反したコーディングがなされている可能性があります。ただし、設計段階で意図的に複数のエントリ・ポイントを持たせているケースも皆無ではないため、設計を再確認した方が良いでしょう。
複数のクラスで main
メソッドを定義できることを応用して、各クラスに main
メソッドを用意して、そこに単体テスト・コードを記述することもできます。この方法には実行するコードと単体テスト・コードを同じクラスにまとめることができるというメリットがあります。ただし、現在では JUnit や TestNG といった単体テストを専門に行うフレームワークが普及しているため、main
メソッドに単体テスト・コードを記述することはほとんどなくなっています。
3.3.5. メソッドへのアクセス
メソッドへのアクセスには 2 種類ありますが、通常使用するものはメソッド呼び出し式と呼ばれるものです (もう 1 つはメソッド参照式で、こちらは 12 章にて取り上げます)。メソッド呼び出し式の書式は以下の通りとなります。
- 書式 (1):
instance . method ( [ arg [, arg ...] ] )
instance
: インスタンス、method
: メソッド名、arg
: 実引数
- 書式 (2):
ClassName . method ( [ arg [, arg ...] ] )
ClassName
: クラス名、method
:static
メソッド名、arg
: 実引数
- 書式 (3)-1:
instance . method1 ( [ arg [, arg ] ] ) . method2 ( [ arg [, arg ] ] )
instance
: インスタンス、method1
,method2
: メソッド名、arg
: 実引数
- 書式 (3)-2:
ClassName . method1 ( [ arg [, arg ] ] ) . method2 ( [ arg [, arg ] ] )
ClassName
: クラス名、method1
:static
メソッド名、method2
: メソッド名、arg
: 実引数
メソッドの戻り値は何らかのインスタンスとなっている場合が多いため、上記 (3) 式で示されるように、(1) 式または (2) 式の後に . メソッド名 ( [ 実引数リスト ] )
が続くこともあります (戻り値が void
にならない限り繰り返すことが可能です)。
メソッド呼び出し式はメソッドの処理記述内で使用されます。他のクラスのメソッドへのアクセス可否は、対象となるメソッドに設定されたスコープによります (例: public
スコープであればすべてのクラスのメソッドからのみアクセス可)。メソッドには自分自身に対するメソッド呼び出し式を記述することも可能で、それをメソッドの再帰呼び出しといいます。
メソッド呼び出し式の実行は、文においてメソッド呼び出し式が評価されたときに行われます (メソッド呼び出し式は単独で式文を構成し評価が可能です)。文と式、式の評価については次章にて取り上げますが、具体的には以下のようなコードになります。
// (1) メソッド呼び出し式
// 仮引数なしのため、実引数も設定しない
instance.run()
// 仮引数 String str に対して実引数 "Hello" を設定
sb.append("Hello")
// (2) メソッド呼び出し式 (static メソッド)
// 仮引数 int year, int month, int dayOfMonth に対して実引数 2016, 9, 9 を設定
LocalDate.of(2016, 9, 9)
// (3) メソッド呼び出し式 (メソッドチェーン)
// 上記 (2) の結果 (LocalDate クラスのインスタンス) に対してさらにメソッド呼び出し式 (1) を適用
LocalDate.of(2016, 9, 9).withDayOfMonth(10)
3.4. コンストラクタ
コンストラクタはクラスのインスタンスを初期化する際に呼び出される特別な処理です。コンストラクタの定義はメソッドに似ており、基本的には以下の書式に従います。
// ClassName : クラス名、arg : 仮引数
[ public | protected | private ] ClassName ( [arg [, arg ...] ] ) {
// コンストラクタ呼び出し
// this : 同じクラスのコンストラクタ、super : スーパークラスのコンストラクタ、argument : 呼び出すコンストラクタの実引数
[ this ( [arg [, arg ...] ] ) ; | super ( [ arg [, arg ... ] ] ) ; ]
// 処理記述
}
コンストラクタの名前はクラス名そのものです。スコープや引数はメソッドと同じ扱いで、オーバーロードが可能な点もメソッドと共通しています。ただし、メソッドと異なり戻り値を持つことはありません。
3.4.1. デフォルト・コンストラクタ
クラスの定義において、コンストラクタは省略することができます。その場合は、暗黙の裡に以下のようなコンストラクタが生成されます。これをデフォルト・コンストラクタといいます。コンストラクタが 1 つでも明示的に定義されている場合には、デフォルト・コンストラクタは生成されません。
public ClassName() {
}
クラスを継承する際には、スーパークラスが以下のいずれかの条件を満たす場合に限り、サブクラスのコンストラクタを省略することができます (デフォルト・コンストラクタが生成されます)。
- スーパークラスでコンストラクタが定義されていない (デフォルト・コンストラクタが生成される)。
- スーパークラスで継承可能なスコープ (
public
またはprotected
、同一パッケージの場合はパッケージ・プライベートも可) の引数なしコンストラクタが定義されている。
3.4.2. コンストラクタ内の処理について
コンストラクタ内では、まず始めにクラス内、またはスーパークラスのいずれかのコンストラクタを呼び出さなければなりません。ただし、前節に示したサブクラスのコンストラクタを省略できる条件を満たす場合のみ、スーパークラスのコンストラクタの呼び出しを省略することができます。
クラス内のコンストラクタの呼び出しは this ( [ arg [, arg ...] ] ) ;
、スーパークラスのコンストラクタの呼び出しは super ( [ arg [, arg ...] ]) ;
となります。いずれもメソッド呼び出し式と類似しますが、コンストラクタの処理記述の先頭で、いずれか 1 つしか書けないことに注意してください。
コンストラクタ呼び出し後の処理は概ねメソッドと変わりませんが、異なる点として final
が付加されたフィールドの値が初期化可能です (フィールド宣言時に初期化されている場合を除く)。コンストラクタの処理でメソッドを呼び出すこともできますが、コンストラクタ実行中はインスタンスがまだ生成途中であることから、回避した方が賢明でしょう。
3.4.3. コンストラクタへのアクセス
コンストラクタへのアクセスには 2 種類ありますが、通常使用するものはインスタンス生成式と呼ばれるものです (もう 1 つはメソッド参照式で、こちらは 12 章にて取り上げます)。インスタンス生成式の書式は以下の通りとなります。
- 書式 (1):
new ClassName ( [ arg [, arg ...] ] )
ClassName
: クラス名、arg
: 実引数
- 書式 (2):
new ClassName ( [ arg [, arg ...] ] ) . method ( [ arg [, arg ... ] ] )
ClassName
: クラス名、method
: メソッド名、arg
: 実引数
- 多くは書式 (1) のように単独で使用されます。
- 書式 (2) のようにメソッド呼び出しを続けることも可能で、以降メソッド呼び出し式として扱われます。
インスタンス生成式はメソッドやコンストラクタの処理記述内、あるいはフィールドの初期値で使用されます。他のクラスのコンストラクタへのアクセス可否は、対象となるコンストラクタに設定されたスコープによります (例: public
スコープであればすべてのクラスのメソッド・フィールド・コンストラクタからアクセス可)。
インスタンス生成式の実行は、文においてインスタンス生成式が評価されたときに行われます (インスタンス生成式は単独で式文を構成し評価が可能です)。文と式、式の評価については次章にて取り上げますが、具体的には以下のようなコードになります。
// (1) インスタンス生成式
// 仮引数なしのため、実引数も設定しない
new StringBuilder()
// 仮引数 String str に対して実引数 "Hello" を設定
new StringBuilder("Hello")
// (2) インスタンス生成式 + メソッド呼び出し式 (メソッドチェーン)
new StringBuilder().append("Hello")
3.5. インスタンスの破棄
Java VM はガベージ・コレクションを実装しており、不要になったインスタンスを破棄してヒープメモリを解放する仕組みとなっています。インスタンスが不要になる条件は、そのインスタンスがどこからもアクセスできない状態になっていることです。ほとんどのインスタンスはアプリケーションが終了するまでに不要となっているため、プログラミング時にインスタンスの破棄を意識することはあまりありません。インスタンス破棄のコード (デストラクタ) を記述しなければならない C++ と比較して、Java はメモリ管理に関してプログラマに優しくできています。
ただし、アプリケーション終了時に何らかの理由でインスタンスが不要となっておらず、ヒープメモリが解放されない「メモリリーク」が発生する場合もあり得ます。メモリリークは特に大規模システムで用いられる Java EE で重要視される問題であり、目視での発見がきわめて困難であることから、ツールを利用することになります (JDK にはメモリリークの発見に有用なツールがいくつか含まれています)。