第 11 章: クラスとインスタンス

クラスとインスタンス

class は [種類] や [部類] という意味の英語である. また, instance は [実例] とか [具体的な事物] といいう意味の英語.

インスタンスは具体的な特定のもの.

同じクラスのインスタンスは共通の性質を持つ.

インスタンスを作るのが目的ではないクラスもある (System クラス, Math クラス)

“Hello!” という文字列は String クラス (java.lang.String) のインスタンスの一つ.

あるクラスにおいては:

フィールドは: 情報を保存する場所.
メソッドは: 情報を処理する方法.

フィールドとメソッド

フィールドとメソッド:

フィールドは変数のようなもの. メソッドは関数のようなもの. フィールドとメソッドがクラスの性質を決める.

Rectangle class:

class Rectangle{
  int width;
  int height;

  void setSize(int w, int h){
    width = w;
    height = h;
  }

  int getArea(){
    return width * height;
  }
}

インスタンスの作り方

インスタンスの作り方は, 配列の作り方と似ている. どちらも new を使っている. 比較してください:

Rectangle rect = new Rectangle(); // インスタンス
int[] array = new int[3]; // 配列

実は, メソッド宣言の中で, 使われるフィールド名やメソッド名の前には, [自分] を表すインスタンス this が省略されている.

setSize メソッドの宣言を省略せずに書くと, 次のようになる:

void setSize(int w, int h){
  this.width = w;
  this.height = h;
}

コンストラクタ

コンストラクタは普通のメソッドと違うのは次の 2 点:

(1) コンストラクタには戻り値の型がない (void でもない)
(2) コンストラクタの名前はクラス名と同じ (Rectangle)

コンストラクタの中で他のメソッドを呼び出してもよい..

引数を指定しないときのインスタンスの初期化を記述するのが引数なしコンストラクタである

インスタンスの作成のはじめで呼び出した, new Rectangle() は, 引数なしコンストラクタを呼び出すということ.

引数列が異なっていれば, 一つのクラスにコンストラクタが複数あってもかまわない.

デフォルトコンストラクタ:

コンストラクタが一つも宣言されていないクラスの場合は, 次のような引数なしコンストラクタが自動的に作成される:
Rectangle(){
  super();
}

初期化されていないフィールドの初期値は, 次のようになる:

boolean 型 false
整数型 0
浮動小数点数型 0.0
参照型 null

フィールドと変数の初期値:

初期化されていないフィールドの値は, その型に応じて初期化されるが, 初期化されていない変数の値は未定義になる.

インスタンスはどこに作られているのか?

Rectangle r1; // Rectangle 型の変数 r1 が確保される

Rectangle r1 = new Rectangl(); // Rectangle のインスタンスが作られ, 変数 r1 に代入される.

スタックとヒープ:

(1) 変数 r1 が確保されている領域をスタック (stack) と呼ぶ.
(2) Rectangle のインスタンスが確保されている領域をヒープ (heap) と呼ぶ.

Rectangle.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Rectangle{
    int width;
    int height;

    Rectangle(){ // 引数なしコンストラクタ
	setSize(10, 20);
    }
    Rectangle(int w, int h){
	setSize(w, h);
    }
    void setSize(int w, int h){
	width = w;
	height = h;
    }
    int getArea(){
	return width*height;
    }
    public static void main(String[] args){
	Rectangle r1 = new Rectangle();
	System.out.println("r1.width = " + r1.width);
	System.out.println("r1.height = " + r1.height);
	System.out.println("r1.getArea() = " + r1.getArea());

	Rectangle r2 = new Rectangle(123, 45);
	System.out.println("r2.width = " + r2.width);
	System.out.println("r2.height = " + r2.height);
	System.out.println("r2.getArea() = " + r2.getArea());
    }
}

Rectangle.java の実行結果は:

[wtopia java.lessb]$ java Rectangle
r1.width = 10
r1.height = 20
r1.getArea() = 200
r2.width = 123
r2.height = 45
r2.getArea() = 5535

クラスフィールドとクラスメソッド

Java では, 全インスタンスに共通の情報を保持する場所としてクラスフィールド (class field) と呼ばれるフィールドを宣言することができる.

クラスフィールドは, クラス変数 (class variable) と呼ばれることもある.

[スタティックフィールド], [クラス変数], [クラスフィールド] はすべて同じ意味.

クラスフィールドの宣言:

class Rectangle{
  static int counter = 0;
}

Rectangle.counter が使える.

クラスメソッドの宣言:

class Rectangle{
  static int counter = 0;
  ...
  static int getCounter(){ // クラスメソッド
    return counter;
  }
  ...
}

インスタンスメソッドの中では, this や super を使うことができるが, クラスメソッドはだめ.

クラスメソッドとインスタンスメソッド:

class GamePlayer{
  ...
  static int countAlivePlayer(){
    ...
  }
  boolean isAlive(){
    ...
  }
  ...
}

注意:

クラスメソッドは特定のインスタンスに関連していないので, そのクラスのインスタンスがまだ一つもない状態でも呼び出すことができる. ほかのクラスからクラスメソッドを呼び出すときには:
クラス名.メソッド名 (引数列)
という形式になる.

例えば, これまで作った Rectangle インスタンスの数は,
Rectangle.getCounter();
として得ることができる.

生きているプレイヤーの数は,
GamePlayer.countAlivePlayer()
で得られる.

Rectangle の面積を得るメソッド getArea を 2 通りのスタイルで書いてみる.

getArea をインスタンスメソッドとして書く:

class Rectangle{
  int width;
  int height;

  int getArea(){
    return width*height;
  }
  ...
}

getArea をクラスメソッドとして書く:

class Rectangle{
  int width;
  int height;

  static int getArea(Rectangle obj){
    return obj.width * obj.height;
  }
  ...
}

main メソッド:

クラスを一つのアプリケーションとして動作させるときの main メソッドは, static が付いているので, クラスメソッドになる. main を動かすときには, インスタンスを必要としないから.

public の意味:

public という修飾子は, [パッケージの外から呼び出せる] という意味.

コマンドラインの引数:

args.length -> 配列 args の長さ, コマンドラインパラメータの個数

args[0] -> 始めの引数
args[1] -> 次の引数
...
args[args.length - 1] -> 最後の引数

ShowArgs.java

1
2
3
4
5
6
7
8
public class ShowArgs{
    public static void main(String[] args){
	System.out.println("args.length = " + args.length);
	for(int i = 0; i < args.length; i++){
	    System.out.println(args[i]);
	}
    }
}

ShowArgs.java の実行結果は:

[wtopia java.lessb]$ java ShowArgs
args.length = 0
[wtopia java.lessb]$ java ShowArgs P1 P2 P3
args.length = 3
P1
P2
P3

クラスメソッドだけのクラス:

java.lang パッケージの Math というクラスは, 数学的な計算を行うためのクラスで, 平方根, 三角関数, 対数関数, 指数関数などを計算するためのメソッドを持っている. それらのメソッドは, 特定のインスタンスに関連づけられているわけではなく, いつも同じ計算をするだけなので, すべてクラスメソッドとして宣言されている.

Math クラスは, インスタンスメソッドを一つも持っていない. メソッドは, クラスメソッドしかない.

修飾子

final:

クラスやインタフェースなら拡張できない
インスタンスフィールドやクラスフィールドフィールドなら定数であることを表す
メソッドなら上書き定義 (オーバーライド) することを禁止する

abstract:

抽象クラスや抽象メソッドであることを表す

static:

クラスフィールドやクラスメソッドであることを表す

synchronized:

synchronized メソッドであることを表す

native:

Java 以外の言語で書かれたメソッド (ネイティブメソッド) であることを表す

public:

protected:

private:

final をフィールド宣言に付けると, そのフィールドに値を代入することはできなくなる. [定数] を作ることができる:

class Rectangle{
  final int INITIAL_WIDTH = 10; // 定数, C 言語においては #define
  final int INITIAL_HEIGHT = 20; // 定数, C 言語においては #define
  int width;
  int heigth;

  Rectangle(){
    width = INITIAL_WIDTH;
    height = INITIAL_HEIGHT;
  }
  ...
}

注意: final フィールドの名前はすべて大文字にする習慣になっている.

メソッドの本体がないメソッドを抽象メソッド (abstract メソッド) と言う.

抽象メソッドを含んでいるクラスを抽象クラス (abstract クラス) と言う.

abstract 修飾子は, メソッドには付けられるが, フィールドには付けることはできない:

abstract class Player{
  abstract void play();
}

そのほかの話題

toString というメソッドを宣言すると, そのクラスのインスタンスの標準的な文字列表現を定めることができる.

toString メソッドは, クラス階層の最上位である Object クラスで実装されており, すべてのクラスに継承されている.

このため, すべてのクラスが潜在的には toString メソッドを持っていることになる.

自分で toString を実装するということは, すでに 持っている toString を上書き (オーバーライド) することになる.

注意:

Object クラスは他のオブジェクトとの同一性を調べる equals メソッドを持っている.

toString メソッド:

class Rectangle{
  ...
  public String toString(){
    return "[ width = " + width + ", height = " + height + " ]";
  }
}

そうすると, 以下のように記述することができる:

Rectangle r = new Rectangle(123, 45);
System.out.println(r);

インスタンス r が文字列として使われるとき, Java 言語では, r.toString メソッドを呼び出して, 戻り値の文字列をそのインスタンスの文字列表現として扱ってくれる.

toString メソッドでは, そのインスタンスのフィールドの値を文字列に変換して戻り値とするのが普通.

問題 11-1

11-1-1 ‘A’ は String クラスのインスタンス:

false: 'A' は char 型の定数. "A" と書けば String クラスのインスタンスになる.

11-1-2 プログラムは自分でクラスを作ることができる:

true

11-1-3 クラスは必ずフィールドとメソッドを持っている:

false: フィールドやメソッド, あるいはその両方を持たないクラスを作ることができる.

11-1-4 クラスの名前は大文字で始めるのが普通:

true

11-1-5 new はインスタンスを作る予約語:

true

11-1-6 メソッドは return 文を含んでいなければならない:

戻り値が void のメソッドなら (setSize メソッド), return 文を含まない場合もある.

11-1-7 コンストラクタの名前はクラスの名前と同じ:

true

11-1-8 メソッドの仮引数名はフィールド名と同じにすることができる:

true:
void setSize(int width, int height){
  this.width = width;
  this.height = height;
}

11-1-9 引数を持つコンストラクタを作ることもできる:

true

11-1-10 コンストラクタの戻り値は, そのクラスのインスタンス:

false: コンストラクタには戻り値はない.
new Rectangle() の値は, コンストラクタの戻り値ではない.

問題 11-2

Rectangle r = Rectangle(); はエラーになる. 便宜のために, Rectangle を testRect に変換した

testRect.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class testRect{
    int width;
    int height;

    public testRect(){
	this.width = 100;
	this.height = 20;
    }
    public static void main(String[] args){
	// testRect r = testRect(); // new が付いてないとエラーになる
	testRect r = new testRect(); // okay
    }
}

testRect.java の実行結果は:

[wtopia java.lessb]$ javac testRect.java
testRect.java:10: cannot find symbol
symbol  : method testRect()
location: class testRect
      testRect r = testRect(); // new が付いてないとエラーになる
                   ^
1 error

new が付いていると, うまくコンパイルできた.

問題 11-3

あるプログラムの中に含まれている識別子を分類する:

class A{
  int a = 123; // a は インスタンスフィールド
  static int b = 456; // b あクラスフィールド
  int c(int d){ // c はインスタンスメソッド, d は引数
    int e = 1; // e は局所変数
    return a+d+e;
  }
  static int f(int g){ // f はクラスメソッド, g は引数
    int h = 789; // h は局所変数
    for(int i = 0; i < h; i++){ // i は局所変数
      g++;
    }
    return g;
  }
}

問題 11-4

二次元座標の点を表すプログラムをコンパイルしたら, エラーになった.なぜ?:

class Point{
  int x;
  int y;
  static void setPosition(int x, int y){
    this.x = x;
    this.y = y;
  }
}
クラスメソッドの中で this を参照することができない.

修正後のプログラムは:

class Point{
  int x;
  int y;
  void setPosition(int x, int y){
    this.x = x;
    this.y = y;
  }
}

問題 11-5

GamePlayer.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class GamePlayer{
    public String playername; // playername はインスタンスフィールド

    public GamePlayer(String name){
	playername = name;
    }

    public String toString(){
	return "[player: " + playername + "]";
    }

    public static void main(String[] args){
	GamePlayer[] player = new GamePlayer[4]; // 配列オブジェクト型
	player[0] = new GamePlayer("FeiFei");
	player[1] = new GamePlayer("YangYang");
	player[2] = new GamePlayer("YangGuang");

	for(int i = 0; i < player.length; i++){
	    System.out.println(player[i]);
	}
    }
}

GamePlayer.java の実行結果は:

[wtopia java.lessb]$ java GamePlayer
[player: FeiFei]
[player: YangYang]
[player: YangGuang]
null

問題 11-9

Rectangle.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class Rectangle{
    final int INITIAL_WIDTH = 10;
    final int INITIAL_HEIGHT = 20;
    int width;
    int height;
    int x;
    int y;

    Rectangle(){ // 戻り値ない, public が付いていても大丈夫	
	this.width = INITIAL_WIDTH;
	this.height = INITIAL_HEIGHT;
	x = 0;
	y = 0;
    }

    Rectangle(int width, int height){ // 戻り値ない
	this.width = width; // this. が付いていなくても大丈夫だが, わかりやすいために付けたほうがよい
	this.height = height;
	this.x = 0;
	this.y = 0;
    }

    Rectangle(int x, int y, int width, int height){
	this.width = width;
	this.height = height;
	this.x = x;
	this.y = y;
    }

    void setLocation(int x, int y){
	this.x = x;
	this.y = y;
    }

    void setSize(int width, int height){
	this.width = width;
	this.height = height;
    }

    public String toString(){ // public が付いてないとエラーになる
	return "[" + x + ", " + y + ", " + width + ", " + height + "]";
    }

    Rectangle intersect(Rectangle r){
	int sx = Math.max(this.x, r.x);
	int sy = Math.max(this.y, r.y);

	int ex = Math.min(this.x + this.width, r.x + r.width);
	int ey = Math.min(this.y + this.height, r.y + r.height);

	int newwidth = ex - sx;
	int newheight = ey - sy;

	if(newwidth > 0 && newheight > 0){
	    return new Rectangle(sx, sy, newwidth, newheight);
	}else{
	    return null;
	}
    }

    public static void main(String[] args){ // public が付いてないとエラーになる
	Rectangle a, b, c, d, e, f;
	a = new Rectangle(0, 0, 20, 10);
	b = new Rectangle(5, 5, 20, 10);
	c = new Rectangle(20, 10, 20, 10);
	d = new Rectangle(-10, -20, 100, 200);
	e = new Rectangle(21, 11, 20, 10);
	f = new Rectangle(); // 引数がないコンストラクタ

	System.out.println("a = " + a);
	System.out.println("b = " + b);
	System.out.println("c = " + c);
	System.out.println("d = " + d);
	System.out.println("e = " + e);
	System.out.println("f = " + f);

	System.out.println( "a と a の重なり = " + a.intersect(a) );
	System.out.println( "a と b の重なり = " + a.intersect(b) );
	System.out.println( "a と c の重なり = " + a.intersect(c) );
	System.out.println( "a と d の重なり = " + a.intersect(d) );
	System.out.println( "a と e の重なり = " + a.intersect(e) );
	System.out.println( "a と f の重なり = " + a.intersect(f) );
    }
}

Rectangle.java の実行結果は:

[wtopia 11-9]$ java Rectangle
a = [0, 0, 20, 10]
b = [5, 5, 20, 10]
c = [20, 10, 20, 10]
d = [-10, -20, 100, 200]
e = [21, 11, 20, 10]
f = [0, 0, 10, 20]
a と a の重なり = [0, 0, 20, 10]
a と b の重なり = [5, 5, 15, 5]
a と c の重なり = null
a と d の重なり = [0, 0, 20, 10]
a と e の重なり = null
a と f の重なり = [0, 0, 10, 10]