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

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

2019年10月1日

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

Java

Java の主要概念の復習(超特急版)

オブジェクト
インスタンス変数の集まりからなるデータとそのデータに対する操作(メソッド)の集まり.
インスタンス変数
オブジェクトに格納されるデータを表すための変数.
メソッド
オブジェクト内のデータに対する操作.メソッドが呼出されると,呼出し元から与えられる引数とインスタンス変数を使って計算を行う.場合によってはインスタンス変数の値を代入によって更新する.最終的に何らかの計算結果を呼出し元に返す(ことが多い).
シグネチャ
メソッドの名前,引数の個数とそれぞれの型,返り値の型をまとめて,メソッドのシグネチャと呼ぶ.シグネチャさえわかれば,そのメソッドをどういう形式で呼出せばよいかわかる.
クラス
オブジェクトの定義を与えるプログラム構成単位.インスタンス変数の宣言,インスタンス変数の初期化処理を記述するコンストラクタ,メソッド定義が含まれる.
インターフェース

メソッドシグネチャをいくつか集めたものに名前をつけたもの.典型的には,同一シグネチャのメソッドを持つ(異なる)クラスのオブジェクトをまとめて扱う際に便利である.具体的には

  1. オブジェクトに共通するメソッドのシグネチャを列挙したようなインターフェース I を定義する.
  2. 各クラスは,そのインターフェースを実装するという宣言 (implements I) をつけて定義する.(そのクラスにはインターフェースに書かれたシグネチャを持つメソッドを定義する義務が生じる.)

こうすることによって,I 型の変数には,Iを実装したクラスのオブジェクトをどれでも格納することができ,その変数を通じて I で宣言されているメソッドを呼ぶことができる.この際,実際に呼ばれるメソッド定義は,その時に変数に格納されているオブジェクトのクラスに依存する(動的ディスパッチ(dynamic dispatch)).

Java プログラムの基本要素

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

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

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

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

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

各種変数の共通事項

int x = 100;
BinarySearchTree insert(BinarySearchTree t1, int i) {...}

インスタンス変数

局所変数(メソッドのパラメータを含む)

クラス変数

補足

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

オブジェクト生成

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

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

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

メソッド呼び出し

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

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

という形をしている(<式i>は変数であることもしばしばであるが,一般には式が書け,場合によっては,メソッド呼び出し式が入れ子になることもある.例えば,x.m1().m2() と書けば,これは括弧を補えば (x.m1()).m2() のことで,x.m1() の呼び出しの結果として返される参照が指すオブジェクトに対して m2 を呼ぶ,という意味になる.

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

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

冒頭でふれた 動的ディスパッチ の本質は呼び出すメソッドを選ぶステップにある.例えば,

public interface BinarySearchTree {
    void insert(int n);
}

public class Leaf implements {
    ...
}

public class Branch implements {
    ...
}

のような定義がされている時,BinarySearchTree 型の変数は LeafBranch クラスから作られたオブジェクトを格納している可能性がある.この場合,同じメソッド呼び出し式でも \(v_0\) が参照するオブジェクトのクラスによって違うメソッドが呼び出される.例えば,

BinarySearchTree bst = new Leaf(...);
bst.insert(5);

であれば,Leaf 中の insert が呼ばれ(もし Leafinsert の定義がなかったとしたらそもそもコンパイルできない),

BinarySearchTree bst = new Branch(...);
bst.insert(5);

であれば,Branch 中の insert が呼ばれる.一般には,変数にどちらのクラスのオブジェクトが格納されているかはわからないので,実行しながら決めることになる.

変数とフレーム

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

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

局所変数

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

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

Java では,ある局所変数の有効範囲内で同名の変数を宣言することはできない.(インスタンス変数の名前と同じなのは許されている.)

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

はコンパイル時にエラーになる.(同名の変数を二度宣言してもエラーにならず,使う際には「一番近くで」宣言された変数を参照する,という規則になっている言語も多い.Java がなぜエラーにしているかはわからないが,紛らわしくてバグの温床になるからだろうか.)

インスタンス変数の正体

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

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

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

という文を書いたとしよう.v はインスタンス変数だが,これは内部的には

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

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

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

this.left = left;

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

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


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


  1. このヒープという用語は,ヒープソートの「ヒープ」とは関係ない.↩︎

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

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


Copyright 五十嵐 淳, 2016, 2017, 2018, 2019, 2020