2016年度「プログラミング言語」配布資料 (2)

五十嵐 淳 (京都大学大学院情報学研究科 通信情報システム専攻)

2016年10月2日

[ 前の資料 | 講義ホームページ・トップ | 次の資料 ]

Java プログラムの動作を理解する

Java プログラムは,実際にはコンパイラによってJava仮想機械の命令に変換され,それがJava 仮想機械によって実行されるわけだが,Java仮想機械を持ち出すことなく,Java プログラムの実行過程を理解する・説明できるようになることは,非常に大事である.

同様に,何のプログラミング言語であっても,その実行過程を,実装方式や実行する計算機のハードウェアに依存しない抽象的なレベルで理解する・説明できるようになるのが望ましい.

変数,オブジェクト,参照

変数は,データを格納するための,名前がつけられた(メモリ)領域である.変数の働きを理解するためには,

  • 変数をどのように宣言するのか(宣言が不要な言語もある).
  • 宣言された変数は,プログラム文面のどの範囲で使用できるのか.(これを変数の 有効範囲(scope) という.)
  • 変数には何を格納するのか.
  • 変数に対してはどのような操作が許されているのか.
  • 変数に対応するメモリ領域はいつどのように確保されて,その内容が初期化されるのか

といったことを理解する必要がある.ひとつの言語に上の項目が異なる,何種類もの○○変数が登場することも多い.Java での代表的な変数はインスタンス変数,局所変数,クラス変数(static変数とも呼ばれる)である.

各種変数の共通事項

  • 変数は全て,どんなデータを格納するか,その型を指定して宣言する.
int x = 100;
BinarySearchTree insert(BinarySearchTree t1, int i) {...}
  • 変数が格納するのは整数(int),倍精度浮動小数点数(double) などの数値,または,オブジェクトへの参照(背番号,IDのようなもの)である.オブジェクトそのものは(メモリに領域が割り当てられるが)変数には直接格納されない.パラメータや返り値を通じてやりとりされるのもあくまでオブジェクトへの参照である.
  • 変数に対しては,そこに格納されている値(数値・オブジェクトへの参照)の 読み出し と,新しい値の 書き込み のふたつの操作が考えられる.

インスタンス変数

  • クラス本体内でメソッド・コンスタラクタと並んで宣言される.
  • 有効範囲はそのクラス内.(この説明は,実際にはちょっと微妙なところがあるのだが,ひとまず近似としては十分であろう.)
  • new によってオブジェクトが生成されると,インスタンス変数のための領域が確保され,初期化子やコンストラクタで,その内容の初期化が行われる.
  • そのオブジェクトが「使われなくなると」 ごみ集め(garbage collection) と呼ばれるプログラム実行系(Java なら Java 仮想機械)内の仕組みによって,その領域は解放される.

局所変数

  • メソッドのパラメータ,for文,文の並びの途中で宣言される.(private 修飾子はつけてはいけない.)
  • メソッドパラメータ,for文の場合,その直後のメソッド本体/ブロック { ... } が有効範囲,文の並びの宣言は,その直後からブロック終了までが有効範囲となる.
  • メソッド呼び出し,for文の実行などをきっかけとしてメモリ領域が確保され,ブロックの終了とともにその領域は解放される.

クラス変数

  • クラス本体内でメソッド・コンスタラクタと並んで宣言される.インスタンス変数と区別するため static 修飾子が必要.
  • 有効範囲はそのクラス.
  • プログラム実行開始とともに(これは本当はちょっと不正確),領域が確保され,初期化子によって内容が初期化される.

補足

インスタンス変数は多くの場合 private という 修飾子(modifier) を伴って宣言するが,後述のように,この修飾子そのものは,変数がインスタンス変数であることを 意味しない .インスタンス変数と局所変数を区別するのはその宣言の場所である.

オブジェクト生成

Java のオブジェクトは,概念的には,クラス名とインスタンス変数の値が集まったものである.

Javaでは new <クラス名>(<式1>,...,<式n>) という形の式1でオブジェクトを生成する.この式の評価は以下のような過程で行われる:

  1. <式1>, ..., <式n> を評価する(それらの値を \(v_1\), ..., \(v_n\) としよう).
  2. 指定されたクラス名とそのクラスのインスタンス変数用の領域が確保され(未初期化の)オブジェクトが作られる.そして,オブジェクトのIDが割り当てられる.
  3. コンストラクタのパラメータのための領域が確保され,\(v_1\), ..., \(v_n\) で初期化される.
    この時,(代入不可能な)特別な変数 this の領域も確保され,オブジェクトのIDが格納される.
  4. コンストラクタ本体の実行.実行終了とともにパラメータとthisのための領域は解放される.
  5. オブジェクトIDがnew式全体の値となる.

BinarySearchTree t1 = new Branch(new Leaf(), 10, new Leaf());

メソッド呼び出し

メソッド呼び出し式は一般には

<式0>.<メソッド名>(<式1>,...,<式n>)

という形をしている(<式i>は変数であることもしばしばであるが,一般には 1+1 のような式が書け,場合によっては,メソッド呼び出し式が入れ子になることもある.

メソッド呼び出し式の評価は以下のような過程で行われる.

  1. <式0>, ..., <式n> を評価する(それらの値を \(v_0\), ..., \(v_n\) としよう).
  2. パラメータのための領域が新たに確保され,\(v_1\), ..., \(v_n\) で初期化される.
    この時,特別な(代入不可能な)変数 this も確保され,\(v_0\)が格納される.
  3. \(v_0\) は,あるオブジェクトへの参照である(はずな)ので,参照されているオブジェクトのクラスに定義されている <メソッド名> という名前のメソッドを選び,その本体を実行する.実行終了とともにパラメータとthisのための領域は解放される.
  4. return された値が式全体の値になる.

パラメータ変数のための領域を フレーム(frame)環境(environment) と呼ぶ.あるメソッドの実行中に使用できる変数は,このフレームに格納された変数のみである(インスタンス変数については後述).フレームは,メソッドの呼び出し毎に必ず 新たに 確保されることに注意したい.あるメソッド実行中に,例え同じ名前のメソッドが実行されることになっても,二度目の実行のための新たなフレームが用意されて,その下で実行が行われる.このため,二度目の実行中にパラメータ変数に代入を行ったとしても,最初の実行のフレームには影響が及ばない.

また,呼び出された側(callee)のフレームの(確保から解放までの)生存期間は,呼び出し元(caller)のフレーム生存期間に完全に含まれる,いわゆる入れ子構造になっているため,フレームはスタックを使って実装することが多い.

動的ディスパッチの本質はメソッドを選ぶステップにある.例えば,変数の型が BinarySearchTree のようなインターフェースの場合,その変数から参照されるオブジェクトのクラスは Leaf, Branch など複数の可能性がある.同じメソッド呼び出し式でも \(v_0\) が参照するオブジェクトのクラスによって違うメソッドが呼び出される.

局所変数

for(int i = 0; i < 10; i++) {
  ...
}

iのような局所変数は,現在実行中のメソッドのフレームを一時的に({}内の実行中だけ)拡張することで確保していると考えられる.局所変数の生存期間は{} の入れ子構造と対応するので,フレームをスタックで実装することと相性がよい.

Java では,ある局所変数の有効範囲内で同名の変数を宣言することはできない.

int foo(int x) {
  ...
  for(int x = 0; ...; ...) {...}
  ...
}

はコンパイル時にエラーになる.

インスタンス変数の正体

上で,メソッドの実行中に使用できる変数はフレーム内の変数のみだ,と書いた.フレームにはメソッドのパラメータなどの局所変数しか入っていない.では,インスタンス変数はどこへ行ってしまたんだ,と思うかもしれない.その秘密は,コンストラクタ・メソッド呼び出しでフレームに導入される特殊な変数 this にある.実は,インスタンス変数は全て this 経由で読み書きされているのだ!

例えば Branch クラスの insert メソッド中の

if (n == v) { ... }

という文で v はインスタンス変数だが,これは内部的には

if (n == this.v) { ... }

の略記として扱われている.Java には <式>.<インスタンス変数名> という「<式>の評価結果であるところの参照が指すオブジェクトのインスタンス変数」という意味になる構文が用意されている.this はフレーム中に,今,操作しようとしている Branch オブジェクトを指す変数として存在するので,うまくインスタンス変数の値が取り出せる,というわけである.

この <式>.<インスタンス変数名> という表記は,ほとんど変数のようなもので,上のような値の読み出しに使えるだけでなく,代入文で書き込みを行うこともできる.その例が,Branch のコンストラクタに現れる

this.left = left;

である.右辺は(内側の宣言が優先されるので)局所変数(コンストラクタパラメータの)leftの値(オブジェクト参照),左辺は,インスタンス変数の値を読み出しているわけではなく「this が指すオブジェクトの中の,インスタンス変数 left格納している場所 」を示している.

有効範囲が重なる同名の局所変数宣言(これは上に述べたようにエラーになる)と違って,インスタンス変数の有効範囲内で同名の局所変数を宣言するのはOK2となっているのは,このように this. 記法を使って使うことができるからかもしれない.


[ 前の資料 | 講義ホームページ・トップ | 次の資料 ]


  1. 正確には,式文という構文カテゴリーに属する.式文は式でありながら,セミコロンをつけることで文としても使用できるような式のことをいう.

  2. 逆に,局所変数の有効範囲内でインスタンス変数が宣言されることは構文上ありえないことに注意.


Copyright 五十嵐 淳, 2016, 2017