コンピュータプログラミングII (11)

モノをひとまとまりとして捉える (4)

このスライドの使い方

テキスト等がはみ出した場合は、フォントサイズを小さくして調整する。 「S」 キーで小さく、「B」 キーで大きくなる。また、 下記のキーボード操作が使用できる。(一部キー操作は IE のみ対応)

「←」 or 「Page Up」 前のスライドに戻る
「→」 or 「Page Down」 or「スペース」 次のスライドに進む
「Home」 and 「End」 先頭(Home)または最後(End)のスライドへ移動
「C」 or 「contents?」 をクリック スライド一覧の表示
「F11」 or 「Ctrl+Shift+F (Win)」, 「Cmd+Shift+F (Mac)」 フルスクリーン表示と通常表示の切り替え
「F」 フッタの表示と非表示の切り替え
「A」 全スライド表示に切替 (印刷時に使う)
「S」「-」/「B」「+」 フォントサイズの大(B) 小(S)

本日のゴール

復習: Ball オブジェクトの生成

// constants
final int fps = 60;                 // 1秒間あたりのフレーム数
final float vx0min = -7;            // vxの初期値の下限
final float vx0max = 5;             // vxの初期値の上限
final float vy0min = 4;             // vyの初期値の下限
final float vy0max = 8;             // vyの初期値の上限
final float d = 40;                 // ボールの直径
final float hueMax = 360.0;         // 色相の最大値

// variables
Ball aBall;                          // ボールのオブジェクト1つを参照する変数

void setup() {
  frameRate(fps);
  size(480, 480);
  noStroke();
  colorMode(HSB, hueMax, 100, 100);
  fill(0, 0, 0);
  aBall = new Ball();
  aBall.setD(d);
  aBall.setX(random(d / 2, width - d / 2));
  aBall.setY(random(d / 2, height / 5));
  aBall.setVX(random(vx0min, vx0max));
  aBall.setVY(random(vy0min, vy0max));
}

オブジェクトの生成時に情報を与える

改善前

  aBall = new Ball();
  aBall.setD(d);
  aBall.setX(random(d / 2, width - d / 2));
  aBall.setY(random(d / 2, height / 5));
  aBall.setVX(random(vx0min, vx0max));
  aBall.setVY(random(vy0min, vy0max));

改善後

    aBall = new Ball(d,                       // d
                   random(d / 2, width - d / 2),    // x
                   random(d / 2, height / 5),            // y
                   random(vx0min, vx0max),             // vx
                   random(vy0min, vy0max));             // vy

もしくは

  float x = random(d / 2, width - d / 2);
  float y = random(d / 2, height / 5);
  float vx = random(vx0min, vx0max);
  float vy = random(vy0min, vy0max);
  aBall = new Ball(d, x, y, vx, vy);

オブジェクト生成時に引数で情報を与え、オブジェクトの状態を初期化する。

オブジェクトの初期化: コンストラクタ(constructor)

コンストラクタ: オブジェクトの初期化を担当

class Ball {
  ...
  Ball(float d, float x, float y, float vx, float vy) {
    this.d = d;
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
  }
  ...
}

上記の例ではアクセサメソッドと同じことしかしていないが、 一般にはオブジェクトの初期化時に行うべきことをすべて記述する。

コンストラクタの書き方

  クラスと同じ名前(引数の型 変数名, 引数の型 変数名, ...) {
    
    処理内容
    
  }
class Ball {
  float d;          // 直径
  float x;          // 中心のx座標
  float y;          // 中心のy座標
  float vx;         // 速度のx成分
  float vy;         // 速度のy成分
  ...
  Ball(float d, float x, float y, float vx, float vy) {
    this.d = d;
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
  }
  ...
}

コンストラクタとキーワード this

class Ball {
  float d;          // 直径
  float x;          // 中心のx座標
  float y;          // 中心のy座標
  float vx;         // 速度のx成分
  float vy;         // 速度のy成分
  ...
  Ball(float d, float x, float y, float vx, float vy) {
    this.d = d;
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
  }
  ...
}

属性(フィールド)の変数と同じ名前の変数を、メソッド内やコンストラクタ内(仮引数を含む)で宣言 → 変数名はより局所的に有効な変数を参照しているとみなされる。

例: これまでのプログラムにコンストラクタを導入

ボールを斜めに落とした時の動き (ball_02_oo3.pde)

ボールを斜めに落とした時の動きをアニメーションにする。 ボールは床と天井と壁でバウンドさせる。 床と天井と壁の弾性係数は elas (= 0.98等) とする。 画面サイズは高さ 480、幅 480 とする。

例: これまでのプログラムにコンストラクタを導入

オブジェクト生成時に引数で情報を与える。

// constants
final int fps = 60;                 // 1秒間あたりのフレーム数
final float vx0min = -7;            // vxの初期値の下限
final float vx0max = 5;             // vxの初期値の上限
final float vy0min = 4;             // vyの初期値の下限
final float vy0max = 8;             // vyの初期値の上限
final float d = 40;                 // ボールの直径
final float hueMax = 360.0;         // 色相の最大値

// variables
Ball aBall;                          // ボールのオブジェクト1つを参照する変数

void setup() {
  frameRate(fps);
  size(480, 480);
  noStroke();
  colorMode(HSB, hueMax, 100, 100);
  fill(0, 0, 0);
  float x = random(d / 2, width - d / 2);
  float y = random(d / 2, height / 5);
  float vx = random(vx0min, vx0max);
  float vy = random(vy0min, vy0max);
  aBall = new Ball(d, x, y, vx, vy);
}

void draw() {
  background(0, 0, 100);
  aBall.move();
  aBall.draw();
}

変数 x, y などに一度代入せず、実引数に random などの式を書いてもよい。

例: これまでのプログラムにコンストラクタを導入

class Ball {
  final float gravity = 9.8 / fps;    // 1フレームあたりの重力加速度
  final float elas = 0.98;            // 反発係数
  float d;                            // 直径
  float x;                            // 中心のx座標
  float y;                            // 中心のy座標
  float vx;                           // 速度のx成分
  float vy;                           // 速度のy成分
  Ball(float d, float x, float y, float vx, float vy) {
    this.d = d;
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
  }

例: これまでのプログラムにコンストラクタを導入

アクセサメソッド以外のメソッドは変更なし。

  void move() {
    x  += vx;          // ボールが速度ぶん移動する
    vy += gravity;     // 速度のy成分に重力加速度を加算する
    y  += vy;          // ボールが速度ぶん移動する
    if (x < d / 2) {
      vx = - vx * elas;
      x = d / 2;
    } else if (x > width - d / 2) {
      vx = - vx  * elas;
      x = width - d / 2;
    }
    if (y > height - d / 2) {
      vy = - vy * elas;
      y = height - d / 2;
    } else if (y < d / 2) {
      vy = - vy * elas;
      y = d / 2;
    }
  }

例: これまでのプログラムにコンストラクタを導入

アクセサメソッド以外のメソッドは変更なし。

  void draw() {
    fill(calcHue(vx, vy), 100, 100);
    ellipse(x, y, d, d);
  }
  float calcHue(float vx, float vy) {
    return (sqrt(vx * vx + vy * vy) * 16) % hueMax;
  }
}

練習: これまでのプログラムにコンストラクタを導入

これまでに作成したプログラムを書き換えてみよう。

複数のコンストラクタ

class Ball {
  ...
  /** すべてを指定するコンストラクタ。 */
  Ball(float d, float x, float y, float vx, float vy) {
    this.d = d;
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
  }
  /** 中心座標だけを指定するコンストラクタ。他はデフォルト値で初期化される。 */
  Ball(float x, float y) {
    this.d = 40;
    this.x = x;
    this.y = y;
    this.vx = random(-7, 5);
    this.vy = random(4, 8);
  }
  /** 引数をとらないコンストラクタ。すべてデフォルト値で初期化される。 */
  Ball() {
    this.d = 40;
    this.x = random(40 / 2, width - 40 / 2);
    this.y = random(40 / 2, height / 5);
    this.vx = random(-7, 5);
    this.vy = random(4, 8);
  }
  ...
}

複数のコンストラクタ

class Ball {     // 前のスライドと同じ例
  ...
  /** すべてを指定するコンストラクタ。 */
  Ball(float d, float x, float y, float vx, float vy) {
    this.d = d;
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
  }
  /** 中心座標だけを指定するコンストラクタ。他はデフォルト値で初期化される。 */
  Ball(float x, float y) {
    this.d = 40;
    this.x = x;
    this.y = y;
    this.vx = random(-7, 5);
    this.vy = random(4, 8);
  }
  /** 引数をとらないコンストラクタ。すべてデフォルト値で初期化される。 */
  Ball() {
    this.d = 40;
    this.x = random(40 / 2, width - 40 / 2);
    this.y = random(40 / 2, height / 5);
    this.vx = random(-7, 5);
    this.vy = random(4, 8);
  }
  ...
}

thisによる他のコンストラクタの呼び出し

class Ball {
  ...
  /** すべてを指定するコンストラクタ。 */
  Ball(float d, float x, float y, float vx, float vy) {
    this.d = d;
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
  }
  /** 中心座標だけを指定するコンストラクタ。他はデフォルト値で初期化される。 */
  Ball(float x, float y) {
    this(40, x, y, random(-7, 5), random(4, 8));
  }
  /** 引数をとらないコンストラクタ。すべてデフォルト値で初期化される。 */
  Ball() {
    this(40, random(40 / 2, width - 40 / 2), random(40 / 2, height / 5),
             random(-7, 5), random(4, 8));
  }
  ...
}

暗示的コンストラクタ

暗示的コンストラクタ
コンストラクタが1つも用意されていないクラスでは、引数なしのコンストラクタが自動的に作られる。
  • この自動で作られるコンストラクタを暗示的コンストラクタ(implicit constructor)と呼ぶ。
  • いままでコンストラクタを自前で用意しなくても new クラス名() でオブジェクトの生成ができていたのはこの機能のおかげ。
  • 整数・実数の属性は0に、参照型の変数は null (何も参照していない状態)に初期化される。
1つでも自前でコンストラクタを用意すると、暗示的コンストラクタは作られなくなる。
  • 引数なしのコンストラクタは自前で用意しなければ存在しなくなる。
class Ball {              // コンストラクタがない
  ...
}

Ball aBall = new Ball();   // 自動で生成された引数なしのコンストラクタが呼び出される
class Ball {              // 引数ありのコンストラクタしかない
  ...
  Ball(float d, float x, float y, float vx, float vy) {
    ...
  }
  ...
}

Ball aBall = new Ball();   // 引数なしのコンストラクタが自動で生成されないのでエラー

同名のメソッド: オーバーロード

// これらはすべて異なるメソッド
int   sum(int a, int b)        { return a + b; }
float sum(float a, int b)      { return a + b; }    // b が float に変換される
float sum(int a, float b)      { return a + b; }    // a が float に変換される
float sum(float a, float b)    { return a + b; }
int   sum(int a, int b, int c) { return a + b + c; }

// これは上の先頭の例と戻り値の型が違うだけなので共存できない
void  sum(int a, int b)        { System.out.println(a + b); }

オーバーロードを使うことで「受け取る情報の数や型は違っても同じような内容の処理をする」 ということを表明することができる (同名なのに全然異なる動きをするようなメソッドをつくらないこと)。

復習: 暗黙の型変換と明示的型変換(cast)

float sum(float a, int b)      { return a + b; }    // b が float に変換される
float sum(int a, float b)      { return a + b; }    // a が float に変換される