オブジェクト & 3次元CG(その2) |
今回はやっと、Java の Java らしい一面である「オブジェクト」という概念について学ぶ。これは、BASIC, FORTRAN, C といった「手続き型」プログラミング言語や、LISP などの「関数型」プログラミング言語、あるいは Prolog などに代表される「論理型」プログラミング言語とは言語設計の思想がまったく異なる、「オブジェクト指向型」と総称されるプログラミング言語を特徴付ける本質的な一面である。
オブジェクトというものの基本が理解できたら、すでに基本を学んだ「配列」と「文字列」についても、オブジェクトという観点から再度見なおしてみよう。これらはデータ型ではなく、オブジェクトの一種である(これらは、「参照型」という概念で統一される)。
もっと詳しいことは ここの後半(§1.3) と ここ。
オブジェクト指向プログラミング(object-oriented programming)では、プログラムが対象としようとしている世界に属す「もの」(=オブジェクト、object)を中心に据えて、それらの特徴を記述することがプログラミングである。
例えば、レストランという世界を考えよう。レストランでは個々の客、個々のウェイトレス、個々のコックなどがそれぞれオブジェクトであり、個々の客やウェイトレスやコックは誰も決められた一定の仕事(動作)をする(言い換えれば、それぞれに特徴的な独自の機能=役割を持っている)。例えば、客は料理の注文をし、ウエイトレスは客からの注文をコックに伝えたり、調理が終った料理を客に出したりし、コックは料理を作る。このような機能(動作)のそれぞれを Java ではメソッド(method)という。
一方、こういった仕事をするためにはいろいろな種類の道具や材料(コックは鍋や包丁など、ウエイトレスはメニューや注文伝票やPDAなど)が必要であるし、個々のウエイトレスや個々の客や個々のコック(すなわち、個々のオブジェクト)を区別する名前や年齢、性別などの性質・特性(属性、attribute)を持っている。このような属性を保持するもの(変数)を Java ではフィールドという(他のオブジェクト指向言語ではデータメンバーとかメンバー変数とか、単にメンバーとか、スロットともいう)。
こういったオブジェクトの属性・性質、すなわち、仕事・動作(機能・能力)や道具(情報・データ)をきちんと決めることによって、抽象的概念としての「客」とか「ウエイトレス」とか「コック」といったものを特徴付けることができる。 オブジェクト指向言語では、
クラスは一種のテンプレート(template、雛形、鋳型)であると考えることもできる。 各クラスの具体的オブジェクトは、その鋳型を使って生成する。このようなことをインスタンス化(instantiation)という。
Java のデータ型の基本についてはすでに学んだ:
基本型 論理型 boolean 数値型 整数型 byte, short, int, long 文字型 char 浮動小数点型 float, double 参照型 省略(ここを参照のこと) null型 同上先ずここでは、Java のクラスという概念をデータ型の一般化として捉える視点からオブジェクトについて説明する。
Java をはじめとするオブジェクト指向型言語では、データ型という概念はもっと一般化されてクラスというものになり、個々のデータ型の値を取る '変数' という「もの」もオブジェクトという概念に一般化される。すなわち、オブジェクトは、変数のように単に一定のタイプのデータを保持できるというだけでなく、複数のタイプのデータを同時に持つことができるし、一定の機能(つまり、何らかの処理を実行できる能力)もいくつか併せ持っている。したがって、オブジェクトは単なる「変数」以上に一般的な「もの」であるが、抽象的な概念ではなく、具体的に存在する「もの」である。
例えば、文字列 "abc" や "uvwxyz" はそれぞれオブジェクトである。一方、文字列全体(あるいは別の言い方をすれば、文字列という概念)のことを Java では String クラスと言う。つまり、 "abc" や "uvwxyz" などの個々の文字列はどれも String クラスのオブジェクトである。このように、クラスは、ある一定のタイプのデータを保持できると同時に一定の機能を有したオブジェクト全体を呼ぶ呼称であり、そういったオブジェクトとは何かということを特徴付ける抽象的な概念である。
変数など(実体がある) ⇒ オブジェクト データ型(抽象的概念) ⇒ クラス データ型の宣言 ⇒ オブジェクトの定義と生成
我々はすでに前回見たように、Java アプリケーションにおける main 関数の引数としてや入出力の際に String クラスを使っている。String クラスは文字列を扱うクラスであり、
java.langの中の1つのクラスとして定義されている。 java.lang パッケージは Java のパッケージの中でも最も基本的なクラスの定義を含んでいるものなので、わざわざ
import java.lang.*;と書かなくても暗黙に取り込まれる(よって、書かなくてもよい)ことになっている。
@ String クラスのオブジェクトの宣言の仕方
String はクラスであり、クラスはデータ型の一般化であるから、int 型の変数 n を
int n;と宣言するのと同様に、s が String クラスのオブジェクトであることを "宣言" するには
String s; // @のように書く(オブジェクトは変数の一般化であることに注意)。
A String クラスのオブジェクトの初期化/生成の仕方
String s;のように、すでに宣言されている String オブジェクト s の実体を生成する(すなわち、s を初期化して、具体的文字列を値として持たせる)ためには、配列の生成・初期化と同様に
new演算子を使って次のように書く:
s = new String("abcd"); // Aこれも、やはり配列の初期化の場合と同様に、new を省略して、
s = "abcd"; // A’と書いてもよい。
B String クラスのオブジェクトの宣言と初期化・生成を同時に行なうには
やはり配列の場合と同様に、宣言@と初期化・生成A(またはA’)を一緒にして
String s = new String("abcd"); // Bと書くことや、さらに new を省略して
String s = "abcd"; // B’と書くことも許されている。
● 文字列についての詳細は、「Javaの基本(9) オブジェクト超入門」 文字列 の項を参照のこと。 を参照のこと。
これまで何度も述べてきたように、オブジェクト指向言語の観点から言うと、クラスは、なんらかの一定のまとまりのある "もの" がどんなものであるかを抽象化したもの(すなわち、概念を具体化したもの)である。
一方、クラスは "データ型" の概念を一般化したものでもある。
データには文字(char)、整数(byte, int, long)、実数(float, double)、論理値(boolean)、参照(ポインタ)などのような基本的なものから、それらをもとにして定義される配列(同じ型のデータの集まり)や文字列(String, 文字の並び)のようなものだけに限らず、さらに複雑な構造のものがある。
例えば、型が異なるデータの集まり(一般に、そのようなものをレコード(record)と呼ぶ)は配列という概念を拡張したものであるし、スタック、キュー、リスト、木、グラフ、等々のいわゆるデータ構造(data structure)と呼ばれるものは、レコードという概念をさらに拡張したもの(データ構造=データの集合+その上の操作の集まり)であり、こういったものはすべて "データ型" という概念の一般化である(データ型という概念を抽象化したものなので、抽象的データ型(abstract data type)とも言う)。
一般に、レコードを構成する要素のことをフィールド(項目)と呼ぶが、クラスの構成要素の一つであるフィールドという名前(後述する)はここに由来する。
クラスは、このように2つの側面(同じものを違う方向から見たもの)をもつ。
一般に、クラスとはどう書くべきものなのかをここであらためて述べる。クラスの一般的な形は
クラス修飾子 class クラス名 { フィールド宣言 コンストラクタ宣言 メソッド宣言 }である。
すでに学んだように、クラス修飾子は public, abstract, final のいずれか。修飾子は、クラス名の前に付けてクラスの性格の一端を指定するほか、フィールドやメソッドの前にも付けて同様にその性格の一端を指定する。
修飾子の種類とそのおよその意味を次の表に示す。
修飾子の種類 | キーワード | 意味(機能) |
---|---|---|
アクセス修飾子 | なし | 同一パッケージ内でのみ利用可能 |
private | 同じクラス内でのみ使用可能 | |
protected | 同一パッケージとサブクラスで使用可能 | |
public | どこからも使用可能 | |
メソッドタイプ | static | クラス固有のメソッドとする |
abstract | 実装を含まない抽象的メソッドとする | |
final | サブクラスでオーバーライド(上書き変更)不可のメソッドとする | |
synchronized | オブジェクトの同期を行うメソッドとする | |
native | 特定のマシン固有の言語で提供されるメソッド |
この中では、private, public, static, final だけわかれば十分である。 クラスのアクセス修飾子は public, abstract, final のいずれかだけである。
フィールドの宣言は
データ型 変数名 ; (1) 変数の宣言や
データ型[] 配列名 ; (2) 配列の宣言や
クラス名 変数名 ; (3) クラス型変数の宣言を列挙したものである(順序、個数は任意)。「変数の宣言」と「配列の宣言」は
int n = 123; float x = 45.67e-3;や
double[] a = new double[10]; int[] b = int[]{1, 23, 456}; char[] c = {'a', 'b', 'c', 'd', 'e'};のように、生成や初期値設定まで行なってもよい。
3つ目の宣言
クラス名 変数名;により、この変数は指定されたクラスのオブジェクトを指す変数となる(取る値はクラスのオブジェクトへのポインタ)、要するにオブジェクトの名前のようなものとなる(インスタンスとオブジェクトは同義語であることに注意)。
(1)や(3)のような、フィールドの1つとして宣言される変数(例えば x とする)は、オブジェクト(=インスタンス。例えば Obj とする)を生成するとそのオブジェクトのフィールドとして Obj . x のように使うことができる(ただし、異なるオブジェクト同士では異なる変数である。つまり、Obj1, Obj2 が同じクラスのオブジェクトであっても Obj1.x と Obj2.x は異なるものである)ので、インスタンス変数とも言う(後述のクラス変数と比べよ)。
上のような宣言の書き方は、変数のデータ型宣言と同じ書き方であることに注意したい(すでに述べたように、クラスはデータ型という概念を拡張した概念である)。
オブジェクトを指す変数を宣言しても、その変数は具体的な(=実体のある)オブジェクトを指しているわけではない。具体的なオブジェクトを生成することによって、その変数は初めて実体をもつ。
オブジェクト(=インスタンス)の生成の仕方
オブジェクトの生成はインスタンス化ともいい、次のように書く:
変数名 = new クラス名 ();フィールドにも修飾子 public, private, static, final を付けることができる。
public final int PI = 3.141592;のように書く。
メソッドの一般的な形は
修飾子 結果型 メソッド名 ( 型 仮引数 , ... , 型 仮引数 ) ブロックである。
クラス名 .メソッド名(実引数)のように使う(同一クラス内のメソッドの場合には「クラス名 .」の部分は省略してもよい、というか普通は書かない)が、
オブジェクト名 .メソッド名(実引数)のように、オブジェクトを生成してから、そのオブジェクトのメソッドとして使わなければならない。このことは後述する。
(1) public static int sum ( int[] a, int first, int last ) { int s = 0; for (int i=first; i<last; i++) { s += a[i]; } return s; } (2) public static void nothing() { } // 何もしない (3) private static int floor(double x) { // double 型の変数(の値)を int 型 へ変換(整数化)するメソッド return (int)x; } (4) static double power(double x, int n) { // xn を計算して返すメソッド double p = 1.0; for (int i=0; i<n; i++) p *= x; return p; } (4) public static void printArray(int[] a) { // 引数は配列、値を返さない for (int i=0; i<a.length; i++) { System.out.print(a[i] + " "); } }
コンストラクタはオブジェクトを初期化するための特別なメソッドである。
クラスメソッド (class method) インスタンスメソッド (instance method)
クラス型変数の宣言: クラス名 変数名;
オブジェクトの生成: クラス名 変数名 = new クラス名 (コンストラクタへの実引数);
(例) メソッドのタイプ別利用法
(1) 同じクラス内のクラスメソッドを呼び出す例
public class ex1 { // クラス ex1 内のクラスメソッド main public static void main(String[] args) { int n = 123; int sum = add(n, 45); // クラスメソッド add の呼び出し // add と main は同じクラス内なので、クラス名を付けなくてもよい System.out.println(n + "+45 = " + sum); } // クラス ex1 内のクラスメソッド add public static int add(int x, int y) { return x+y; } } (2) 他のクラスのクラスメソッド(String クラスのメソッド valueOf)を呼び出す例
import java.lang.*; // String クラスの定義はこのパッケージ内にある //(ただし、この import は不要) public class ex2 { public static void main(String[] args) { String str; str = String.valueOf(123); System.out.println(str); } } (3) 他のクラスのインスタンスメソッド(String クラスのメソッド toUpperCase)を呼び出す例 public class ex3 { public static void main(String[] args) { String str, STR; // String クラスのオブジェクト str と STR を生成する str = new String("komoji"); // まとめて、String str = new String("komoji"); でもよい // String str = "komoji"; でもよい STR = str.toUpperCase(); System.out.println(STR); // KOMOJI と出力される } } (4) クラスメソッド・インスタンスメソッドを定義して使う例 public class EX4_1 { EX4_1 { } // これは、このクラスのコンストラクタであるが // この例のように「何も定義しない」コンストラクタは // 書かずに省略してもよい public int DOUBLE(int x) { // インスタンスメソッド return 2*x; } public static int TRIPLE(int x) { // クラスメソッド return 3*x; } } public class EX4_2 { public static void main(String[] args) { EX4_1 xx; xx = new EX4_1(); EX4_1 xxx = new EX4_1(); // オブジェクト xxx の生成 int n = xx.DOUBLE(123); // オブジェクト xx のメソッドDOUBLE int m= xxx.DOUBLE(321); System.out.println(EX4_1.TRIPLE(n)); // クラス EX4_1 のクラスメソッド TRIPLE を呼び出している } } (5) オブジェクトを生成して使う例(ソーティング)
ソーティングを行なうアルゴリズムを3つ定義したクラス SORTER を考えよう(ソースコードは ここ)。 この例では、コンストラクタは無い(コンストラクタのある例は次回考える)。
課題13でやること |
~/public_html/infomath6/
2.今回やること
前回、チューリップを描いたときに、後ろに位置するチューリップをそれより手前に位置するチューリップよりも後で描くと(それらが重なる場合には後ろのチューリップが手前のチューリップを上書きするという変なことが生じる(この例 では、花の間隔が大きいので、そのようなことは最初の1個所でしか起こっていない)。これをなくすためには、チューリップの根の位置の y 座標をソートして、y 座標が大きいものを先に描くようにするとよい。上記の SORTER クラスのソーティングメソッドを使ってこれを行なってみよう (「回転なし」の ソースコード1 または ソースコード2 を使うとよい)。
完成した Java のソースファイルを task13.java とし、それを表示する html ファイルを task13.html とし、ディレクトリ ~/public_html/infomath6/ に置くこと。
3.プラスα
今回のプラスαは、3次元CGに関することのうち、3次元空間内における回転についてである。
次の左側のたんぽぽの花は正面を向いているものであるが、多くの場合、右側のたんぽぽのように上向きになっているであろう。この右側のようなたんぽぽを描くためには、x 軸の周りに回転させることが必要になる。
x 軸の周り(あるいは、y 軸の周り)に theta ラジアン回転させたときの座標変換は次の式で与えられる(x, y, z は旧座標。theta は回転角度(単位はラジアン))。
3次元CGで x 軸の回りに theta ラジアン回転させたとき 新 x 座標 x 新 y 座標 y*Math.cos(theta)+z*Math.sin(theta) 新 z 座標 -y*Math.sin(theta)+z*Math.cos(theta) 3次元CGで y 軸の回りに theta ラジアン回転させたとき 新 x 座標 x*Math.cos(theta)-z*Math.sin(theta) 新 y 座標 y 新 z 座標 x*Math.sin(theta)+z*Math.cos(theta) 3次元CGで z 軸の回りに theta ラジアン回転させたとき 新 x 座標 x*Math.cos(theta)+y*Math.sin(theta) 新 y 座標 -x*Math.sin(theta)+y*Math.cos(theta) 新 z 座標 zで与えられる。
x 軸の回りに theta 度回転 新 x 座標: Rotate3X_x(double x, double y, double z, double theta) 新 y 座標: Rotate3X_y(double x, double y, double z, double theta) 新 z 座標: Rotate3X_z(double x, double y, double z, double theta) y 軸の回りに theta 度回転 新 x 座標: Rotate3Y_x(double x, double y, double z, double theta) 新 y 座標: Rotate3Y_y(double x, double y, double z, double theta) 新 z 座標: Rotate3Y_z(double x, double y, double z, double theta) z 軸の回りに theta 度回転 新 x 座標: Rotate3Z_x(double x, double y, double z, double theta) 新 y 座標: Rotate3Z_y(double x, double y, double z, double theta) 新 z 座標: Rotate3Z_z(double x, double y, double z, double theta)