目次
Java は C++ をモデルとして作られたオブジェクト指向プログラミング言語である。本格的なプログラミングを目指すとかなりの作業量になるが、科学計算で、その結果をグラフで表示したい、というような用途に限定すれば、C++ よりはとっつきやすいかもしれない。ここではそのような目的で、必要な事項を解説する。
プログラミングの経験から気がついた点を中心にまとめた覚え書きのようなものである。書かれていない内容については、java というワードを付けて不明用語、あるいは不明操作をグーグル検索すれば、それなりのページが見つかるはずなので、そちらを参照のこと。
適当なエディタを使いプログラムを作って、javax, java というコマンドをコマンドプロンプト画面で走らせれば結果を得ることができるが、その一連の作業を容易に遂行できるような統合環境を提供するソフトを利用すると、効率が良い。
Eclipse はそのような統合環境の一つで、広く利用されている優れものである。
「日本語 eclipse」で検索すると(おそらく)トップに表示される mergedoc.osdn.jp というアドレスを持つページをクリックすると 「Pleiades - Eclipse プラグイン日本語化プラグイン」 のページが表示されるので、使用環境に合った JAVAのダウンロードボタンをクリックする。
ちなみに、「Eclipseエクリプス」は「日蝕」の意味。「Pleiadesプレアデス」はギリシャ神話に出てくるアトラスの7人の娘のこと、プレアデス星団(日本では、あの、昴、すばる)という星座の名前にもなっている。「Ganymedeガニメード」は木星の惑星の名前にもなっているが、ギリシャ神話でゼウスがワインの給仕とした取り立てた美少年のこと。ゲーテが詩を書き、シューベルトがその詩にメロディを付けるなど、芸術家の心を魅了する存在らしい。
デスクトップのアイコンをクリックすると、最初に「ワークスペース」の場所を指定するダイアログが表示されるので、「参照」ボタンを押し、作業する場所とファイル名を設定する。当面はデフォルト設定のままでも構わない。しばらくすると(結構時間がかかる)プログラム入力画面が表示される。
public static ...
」にチェックマークをつけ、「完了」ボタンをクリックする。MyFirstClass
)が記入されたタブを持つウィンドウで、次のようなプログラムが入力されたものが表示される。package myProject;
public class MyFirstClass { public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ } }
プログラムを入力してエラーで終わると、そのエラー情報が表示されるが、そのとき、エラー箇所を行の通し番号で知らせる。デフォルトでは行番号が表示されていないので、行数が増えた時に不都合である。行番号を表示させるには、「ウィンドウ/設定」メニューを選択し、「設定」ウィンドウで、「一般/エディター/テキストエディター」で「行番号の表示」にチェックマークを入れる。
エディタの表示文字を大きくするには、行番号表示の設定画面で、最後の行にある「色とフォント」文字をクリックし、「Java」を展開して「テキストフォント」をクリックし、「編集」ボタンをクリックする。
Java のプログラムの単位はクラスである。プログラムを実行すると、その中のメインメソッドと呼ばれるプログラムが起動して処理が始まる。簡単なプログラム例から始める。
public class MyFirstClass { public static void main(String[] args) { System.out.println("Hello Java!"); } }
3行目を除いて、この画面通り入力しなければいけない。もっとも、Eclipse を使って、上述の説明通りの手順に従えば、Eclipse が自動的に生成してくれる。という画面が表示されたら、public static void main ...
続く { の後で改行し、以下を入力する。
3行目は、Eclipse のコンテンツアシストをうまく利用すると入力が効率化される。「system
」(最初のエスは小文字でも良い)と入力して「.」(ピリオド)を入力すると、プルダウンメニューが現れ、次の入力文字列候補が表示されるので(コンテンツアシスト)その中から↓↑キーを使って「out
」の文字列を選び、Enterキーを押すと「System.out
」という表示になる(system
のエスが自動的に大文字になる)。このようにして、「.」を押すたびに、入力した単語が期待されるキーワードと一致していると、その後に続く単語の候補を示してくれる。ピリオドを押しても何も表示されない場合は入力ミスの可能性がある。
入力したプログラムの細部を解説する。行番号をつけたものを再掲する。
0 // 最初のプログラム
1 public class MyFirstClass {
2 public static void main(String[] args) {
3 System.out.println("Hello Java!");
4 }
5 }
public
」「class
」はシステムの用意した予約語、「MyFirstClass
」はプログラマーが付けた名前で、「クラス名」という。JAVAのプログラムはクラス単位に認識され、必ず名前を付ける必要がある。クラスの名前を大文字で始めるのが慣例。args
」を除いてすべてこのまま書かなければいけない。新規クラスを生成した時に「メソッドスタブ云々」にチェックマークを入れてあれば、自分で入力しなくても自動的に生成される。... main( ... ) { ... }
」という固まりを「main メソッド」といい、プログラムをランさせたときに、最初に実行される部分。Cでは関数と呼んでいたものを、Javaではクラスと言う。3行目:「System.out.println(...)
」は、文字列を表示させるメソッド(命令)である。「System.out
」は標準出力を表す予約語で、決められた場所(コンソールウィンドウ)へ表示したい場合に使う。3行目がこのプログラムで唯一の命令文で、命令文の最後には「;
」(セミコロン)を付ける。 {}
」を使う。他のかっこは別の意味に使われる。Eclipseで上のプログラムを入力して実行させると、下のウィンドウに「Hello Java
」という文字列が表示される。文字列の表示されたウィンドウをコンソールウィンドウという。
プログラム例、その2:次に、入力したデータを加工して表示する、というもっとも基本的な動作を記述するプログラム例を示し、必要な事項を順番に説明する。
0 // 基本計算のプログラム
1 import java.util.*;
2 public class DollarYen {
3 public static void main(String[] args) {
4 Scanner keyboardInput = new Scanner(System.in);
5 double dollar;
6 int yen, price;
7 System.out.print("1ドルは何円ですか? ");
8 dollar = keyboardInput.nextDouble();
9 System.out.print("ドルの買い物金額(端数は切り捨て)を入力して下さい ");
10 price = keyboardInput.nextInt();
11 yen = (int)(dollar * price);
12 System.out.println("それは円に換算すると "+yen+" 円です");
13 }
14 }
System.out.print...
はデータをコンソールへ出力(表示)するための命令文である(詳しくはここで説明)Javaの扱うデータには、数や文字を扱う基本データ型(プロミティブ)とそれ以外のデータ型(参照型と呼ばれる)がある。基本データ型には、浮動小数点数を扱う double
型、float
型、整数を扱う int
型、byte
型、short
型、long
型、文字を扱う char
型、論理値を扱う
boolean
型がある。
int
型は4バイト32ビットを使って表される 232 通りのパターンを -2-31 以上 231 未満の整数として扱う。byte
型、short
型、long
型は、int
型のバイト数をそれぞれ、1,2,8バイトとしたものである。その範囲で治らない数を表現するために、浮動小数点数を導入する。0.5以上1未満の数 m と整数 n を使って、m x 2n と表現したものを浮動小数点数表示という。m は仮数部、n は指数部と呼ばれる。double
型は8バイトを使い、仮数部を52ビットで表現している。float
型は4バイトを使い、仮数部は20ビットしかない。計算の精度を考えると、float
型は使うべきではない。
char
型は2バイトを使って(16進数4桁の)文字コードを表現するのに使われるが、short
型と同じように、2バイトで整数を表していると言っても良い。実際、符号のない整数として使うことができる。boolean
型は1ビットで表現され、真、偽を意味するtrue, false
だけが値として許される。Cのように、0はfalse
と同じ、ということはない(「while(1)
」は「while(true)
」と書かなければいけない)。
数値を扱うデータ型の間には次のような包含関係がある。左へ行くほど汎用性が高くなる。
double ⊃ float ⊃ long ⊃ int ⊃ (short, char) ⊃ byte
計算式や代入文で違う型同士の演算あるいは操作をする場合、この包含関係に従って、上位のデータ型に合わせて計算が実行される。
文字列データは char
型データの配列と見なされ、String
型という参照型のデータになる。
データを記憶させるために変数を用いる。変数は、命令文で書かれる前に、どのデータ型を記憶させるかということを明示的に宣言しなければいけない。変数名は英文字アルファベットで始まり、英数字とアンダーバー( _ )の組み合わせで作る(日本語も使えるが、入力の手間を考えると、非推奨)。
宣言文は、データ型の名前とスペースに続けて、変数名を書く。2個以上同じ型を持つ変数名はカンマ区切りで並べて同時に宣言することができる。基本データ型、String
型の場合、変数名に続けて等号を書いて定数を続けると、変数の宣言と同時に初期値を設定することができる。それ以外のデータ型(先のプログラム例の4行目 Scanner
のように)の変数の初期値を設定する場合は、「new
データ型名+引数リスト」とする。
自分でデータ型を定義することもできる。例えば、次は4次元の点というデータ型 Point4D
を定義している。
class Point4D() {
double x, y, z, w;
Point3D(double x, double y, double z, double w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w
}
}
初期値定義付き宣言文は、たとえば:
Point4D zz = new Point4D(0,1,2,3);
変数に初期値を与えた時のように、変数の後に等号を書いて、続けて定数、あるいは値の決まる式などを書いたものを代入文といい、等号の右辺で決まる数を左辺の変数に記憶させるという意味がある。等号は数学で使う等号だが、両辺が等しいという意味ではない。
代入文の右辺に、計算式を書くことができる。四則演算記号は「+」「-」「*
」「/」とし、掛け算記号「x」割り算記号「÷」は使わない。int
型数同士の割り算の結果は商となり、あまりを計算するために「%
」という演算記号がある(例 7/4 は 1、7%4 は 3、7.0/4 は 1.75)。Excelでよく使うべき乗の記号「^
」は別の意味に使っているので、べき乗としては使えない。
演算記号が混在する場合、順序を変えるためにかっこを使うが、使えるのは「( )」だけで、「{ }」や「[ ]」は使えない。
演算結果のデータ型は使われデータのデータ型によって決まる。型の異なるデータ同士の演算は、もし変換可能ならば、汎用性の高いデータ型に自動変換される。
異なるデータ型を使っているということを明示的に示すために、キャストという方法がある。変数、あるいは式の前に((int)
のように)データ型の名前をカッコで囲んで書くと、変数のデータ型を一時的に変えることができる。これはキャストと呼ばれる。特に、double
型数を int
型変数に代入するなど、包含関係の下位のデータ型に変換する場合は、キャストがないと警告メッセージが出されるので、その煩わしさを避けるために、キャストを使うことが推奨される。数式の中には四則演算だけでなく、平方根や指数関数対数関数、あるいは三角関数などの数学関数が利用できる。その使い方はExcelと同様で、関数名に続いて、独立変数の値を かっこの中に書く。ただし、関数名の前に「Math.
」という文字を付ける。使える関数をまとめる。
Math.sqrt(double) |
平方根 |
Math.exp(double) |
指数関数 |
Math.log(double), Math.log10(double) |
対数関数(底がeか10かの違い) |
Math.sin(double), cos(double), tan(double) |
三角関数 |
Math.abs(double), Math.abs(int) |
絶対値 |
Math.floor(double), Math.ceil(double) |
切り捨て、切り上げ |
Math.max(double, double), Math.max(int, int) |
最大値 |
Math.min(double, double), Math.min(int, int) |
最小値 |
Math.pow(double, double) |
べき乗 |
Math.random() |
一様乱数(0以上1未満のdouble 型) |
Math.PI |
円周率πを表す定数 |
Math.E |
ネピアの数を表す定数 |
double
型の引数を必要とするところに int
型を書いても自動的に変換してくれる(Math.sqrt(2)
は Math.sqrt(2.0)
と解釈してくれる)。
数学関数のように、独立変数の値を与えて関数値を計算して返す関数を自分で定義することができる。Java では関数のことをメソッドと呼ぶ。幾つかの例を挙げる。メソッドについての詳しい説明はここを見よ。
1変数関数の例:
public static double trunc(double x) {
if(x < 0) return 0;
return x;
}
2変数関数の例(2変量正規分布密度):
public static double normal2(double x, double y) {
double z = Math.sqrt(1 - rho*rho);
return exp(-(x*x - 2*rho*x*y + y*y) / (2*z*z)) / (2 * Math.PI * z); }
再帰的呼び出しを持つ関数の例(階乗):
public static int factorial(int n) { if(n <= 0) return 1; return factorial(n-1) * n; }
実行時にキーボードからデータを入力するために、Scanner
クラスのメソッドを利用する。そのために、次のような宣言文を書く。
Scanner keyboardInput = new Scanner(System.in);
keboardInput
は Scanner
型変数であることを宣言したもので、等号以下は、その初期値である。Scanner
とは、システムが用意したデータ入力のための様々な機能を組み込んだクラス、System.in
は標準入力を意味するメソッド名で、この宣言文を実行することにより、keboardInput
という名前により、キーボードからのデータ入力が可能になる。
Scanner
クラスで定義されている nextXXX()
メソッドを使う。next()
は改行が入力されるまでの文字列全体を入力し、nextInt()
は int
型数、nextDouble()
は double
型数を入力する。
数を入力する場合は、スペース区切りで複数個を同時に入力することができる。改行が入力されたあとにメソッドが実行され、最初のデータが入力される。読み残されたデータは入力バッファに一時的に記憶され、次の入力要求があった場合は入力バッファから優先的に読み取られる。
コンソールウィンドウに結果を表示するために、System.out.printXXX()
メソッドを使う。
System.out.print()
は引数を文字列に変えてそのまま表示し、System.out.println()
は、文字列を表示した後改行する。
引数が計算式の場合は、結果の数値を文字列に置き換えて表示される。文字列と数値を同時に表示したい場合は「+」を使って繋げる。数値が式で計算されている場合は、式全体を「( )
」で囲む必要がある。
double
型数を表示させる場合、必要でない小数点以下の末尾の数字をカットするために、String.format
メソッドを利用するとよい。たとえば、String.format("%8.3f", xxx)
とすると小数点4桁以下を四捨五入した数が表示される。
そのほかに、Cプログラムのprintf()
を意識して、「System.out.printf()
」というメソッドも利用できる。Cで動くprintf
文ならば、その前に「System.out.
」を付ければそのままJavaの命令文になる。( )
の中には、書式と呼ばれる文字列と変数の並びを書く。
他のプログラミング言語と同じように、条件により命令文の幾つかを実行しないことを指示する条件付きジャンプ命令がある。
プログラムは、何も指定しない限り、上から順番に命令文が実行される。ある条件が満たされた場合だけ指定された命令文を実行したり、スキップしたい場合に、if
構文を使う。
ある条件を満たす場合にある命令文(複数の場合もあり)を実行したい場合は「if( ... ) { ... }
」、ある条件を満たす場合にある命令文(群)を実行し、満たさない場合は別の命令文(群)を実行したい場合は「if( ... ) { ... } else { ... }
」という構文を使う。
パターン1:if(条件式) {命令文群}
パターン2:if(条件式) {命令文群1} else {命令文群2}命令文群は「
;
」で終わる命令文を一つ以上つなげたもの。命令文が一つしかない場合は両側のかっこを書かなくても構わないが、構文を明示的に示すためには書いておいたほうが良いことが多い。
命令文群は空でも構わないが、その場合は、空の命令文群を書かなくても済む別の書き方があるはず、なるべく無駄なことはやらないほうが良い。
命令文群2にパターン1あるいはパターン2の構造の命令文が入ると、多重の条件分岐が実現できる。
例
if(time < worldRecord) System.out.println("新記録達成!");
else if(time == worldRecord) System.out.println("惜しい!タイ記録でした");
else System.out.println("記録は出ませんでした、残念でした");
(単純な)条件式は、大きい、小さい、等しい、それらの組み合わせ、によって表現され、以下の記号を使う。これらは「関係演算子」 と呼ばれる。「true
」は 「真(正しい)」、「false
」は「偽(正しくない)」を意味する
型定数。
boolean
記号 | 用例 | 意味 |
> |
a > b |
a が b より大きければ true 、さもなければ false |
< |
a < b |
a が b より小さければ true 、さもなければ false |
== |
a == b |
a と b が等しければ true 、さもなければ false |
>= |
a >= b |
a が b 以上ならば true 、さもなければ false |
<= |
a <= b |
a が b 以下ならば true 、さもなければ false |
!= |
a != b |
a が b と等しくなければ true 、さもなければ false |
単純な条件式を「論理演算子」と呼ばれる「かつ」「または」「でない」を使って組み合わせることにより、複雑な条件を表すこともできる。
記号 | 用例 | 意味 |
&& |
a > b && b > c |
a > b > c ならば true 、さもなければ false |
|| |
a > b || a > c |
a が b、c の大きい方より大きければ true 、さもなければ false |
! |
!(a == b && b == c) |
a、b、c がどれか一つでも違えば true 、さもなければ false (「a !=b || b != c 」と書くのと同じ) |
パターン2の特別なケースで、命令文群が同じ変数への代入文の場合、たとえば、「x ≧ 0
ならば y=a+b
、 さもなければ y=a-b
」というような場合、それを単一の代入文で済ませることができるような、書き方がある。
変数 = 条件式 ? 式1: 式2
「? :
」は3項演算子と呼ばれる。
例
y = (x > 0) ? x : 0 // y は x と 0 の大きい方
sign = (x > 0) ? 1 : ((x < 0) ? -1 : 0) // x が正ならば 1、0 ならば 0、負ならば -1
switch
構文、break
文整数値を取る変数の値によって処理を変えたい場合、「if ... elseif ... elseif ...
」という構文によって実現できるが、それを簡単に表記する構文として switch構文がある。
switch(変数)
case(値1) : {命令文群1}
case(値2) : {命令文群2}
(以下これの繰り返し)
default : {命令文群0}
「変数」の値が「値1」の場合は「命令文群1」から実行し、変数の値が「値2」の場合は「命令文群2」から実行する。「変数」の値が、case(...)
に書かれている値以外の場合は、「default :
」の後の「命令文群0」を実行する。
変数の値が「値1」の場合には「命令文群1」「だけ」を実行したい場合は、命令文群1の最後に「break;
」という命令文を書く。その場合は、命令文群1が終了すると、「 命令文群0」の次の命令文を実行する。「break;
」がない場合は命令文群2に進む。
ある条件を満たす場合だけ、ある命令文群を繰り返し実行する、という場合に while
構文を使う。
パターン1:while(条件式) {命令文群}
パターン2:do{命令文群} while(条件式);
「パターン1」は、まず「条件式」をチェックして、それが true
ならば「命令文群」を実行して、最初の条件式チェックにもどる。命令文群の実行によって条件式が false
になるまで、実行文群を実行し続ける。「条件式」が最初から false
ならば、「命令文群」は一度も実行されない。
「パターン2」は、最初にまず命令文群を実行し、その後に「条件式」をチェックをするという点がパターン1と違う。命令文群の実行によって「条件式」が false になるまで命令文群を実行し続けることは同じ。
「パターン1」の場合、最初に条件式がチェックされるので、その条件式がtrue
かfalse
か判定できるようにしておかなければいけない。何かを実行しないと条件式が確定しない、という場合はパターン2を使う。パターン2の場合、最後の「;
」を落としやすいので注意。
命令文群の途中でそれまでの計算結果に基づいて実行中止、あるいは継続 を決めたいという場合には「continue;
」と「break;
」を使う。
「continue;
」文は、それ以降の命令文群をスキップして条件式の評価に進む、という場合に使う。
「break;
」 文は、それ以降の命令文群をスキップし、条件式評価を無視して繰り返しループを脱出する、という場合に使う。
多くの場合、if
文と併せて「if(...) continue;
」あるいは「if(...) break;
」の形で使われる。
同じようなパターンを決められた回数だけ繰り返すというように、while
構文には決まり切ったパターンでしかもよく使われるものがありる。for 構文は、それらを符丁のようにパッケージ化したもの。
例えば、1からnまでの総和を(公式を知らないとして)求めたい場合、変数 x=0
と k=1
を用意して、k
を s
に足す、ということを k
に1ずつ足していって、k
が n
に等しくなるまで繰り返す、という繰り返し計算が必要である。while
構文を使って書くと、例えば:
x = 0;のようになるが、同じ作業をk = 1;
while(k <= n) {
x = x + k;
k = k + 1;
}
x = 0;
for(k=1; k<=n; k++) {
x += k;
}
と書くことができる。一般的な for
構文は次のようになる。参考のために while
構文と並べて 書いておく。
for(初期化; 条件式; 命令文){ 命令文群 }
初期化; while(条件式) { 命令文群; 命令文}
for
構文の実行手順は次の通り:まず、「初期化」が実行され、その次に「条件式」が試される。もし それが真 true
ならば「命令文群」が実行され、次いで「命令文」 が実行される。その後再び「条件式」が試され、...(以下同様)。もし「条件式」 が偽 false
ならば、for
構文の直後の命令文に移行する。
()
内の「命令文」は大抵は単一の命令文だが、複数の命令文を書いても構わない。ただしその場合、「命令文群」とは違い、命令文と命令文の 間は「,
」 で区切らなければいけない。また、「初期化」と書いてあるが、これはそのような命令文が使われることが多い、ということで、そうしなければいけないという制約はない。「初期化」にも複数の命令文を書くことができるが、その場合も、命令文と命令文の間は「,
」で区切らなければいけない。例えば、上の例では「
と書くことができるが、あまり推奨しない。for(x=0, k=1; k<=n; x+=k, k++);
」
「k++
」は「k = k+1
」という代入文と同じ。同じような記号として「k--
」がある( k = k-1;
と同じ)。「s += k
」は「s = s+k
」という代入文と同じ。
「初期化」「条件式」「命令文」はいずれも、そこに書いてあればそのように解釈される、ということで、その一部を書かない、ということも許される。もっとも極端なケースは、すべて書かない「for( ; ; ) {
命令文群}
」という構文で、条件式が書かれていないということは、いつまでも命令文群を繰り返すということになる。
同じことを「while(true) {
命令文群}
」と書くこともできる。この場合、「条件式」にあたる「true
」は省略することはできない。
for
構文でよく見られる「for(k=0; k<n; k++) ...
」に現れる変数 k は制御変数と呼ばれる。制御変数も変数なので、この命令文の前に宣言文が必要である。しかし、もしこの命令文でだけ使われるのであれば、「for(int k=0; k<n; k++) ...
」のように、その場で宣言することが許される。
この場合、注意しなければいけないのは、初期化で宣言された変数は、for
ループを抜けたときに未定義状態になるということである。例えば、for
ループの途中で break 文でループを脱出した時、その瞬間に k は未定義状態になるので、その時の k の値を使って何かをしようと思っても上手くいかない。実際、コンパイル時に未定義変数が使われている、というエラーメッセージが表示される。
変数名の後に、「[]
」でくくった整数を添えたものを配列変数といい、変数を配列名という。配列変数は同じデータ型のデータを複数記憶する場合、変数宣言を簡略化するために使われる。特に、不特定多数のデータを扱う場合に必須の要素である。
あるデータ型の配列を使う場合、そのデータ型に「[]
」を添えたものを新たなデータ型と考えて、そのデータ型を持つ変数を定義すると、その変数が配列名になる(int[] A;
のように)。配列の大きさを指定するには new を使って、「new int[100]
」とすれば良い。普通は宣言文の初期値設定として「int[] A = new int[100];
」のように書く。これにより、A[0], A[1], A[99]
の100個の変数が定義されたとみなされる。添え字は 0 から始まり、定義された個数マイナス1までの整数値が期待されるが、システムがその範囲を監視することはしない。
配列が宣言されると、基本データ型ならばデータのバイト数、それ以外ならばアドレスを記録するバイト数(4バイト、あるいは6バイト)に、宣言された個数を掛けたバイト数の領域が確保され、その先頭のアドレスが変数名に記憶される。添え字の数字と先頭アドレスからその添え字のついた変数名のアドレスが計算され、データにアクセスすることができるようになる。範囲外の添え字を持つ配列要素も同じように計算されてアドレスを特定されるので、そのアドレスに何が記憶されていようとおかまいなくそのアドレスのデータに対して指定された処理を行う。
int
型配列、double
型配列に初期値を何も与えない場合は、すべて 0 が初期値として与えられる。配列変数の定義と同時に初期値を与えることも出来る。「int[] channel = {1,3,4,6,8};
」と書くと、大きさ5の int
型配列が定義され、その初期値として、右辺に書かれた数値が設定される。
String
型変数は「[]
」を添えないでも配列名として扱われる。「" "
」でくくった文字列を初期値として与えると、その文字列が収まるバイト数分の大きさの配列が確保される。
配列を使ったプログラムの例として、素数を求める「エラトステネスのふるい」のプログラムを載せる。
// 素数表
public class PrimeNumbers { public static void main(String[] args) { byte[] a = new byte[5000]; int n = 10000; int[] prime = new int[2000]; for(int i=3; i<n; i+=2) a[i/2] = 1; for(int i=3; i*i<n; i+=2) if(a[i/2] == 1) for(int j=i*i; j<n; j+=2*i) a[j/2] = 0; int m = 1; prime[0] = 2; System.out.printf("素数表:\n 2"); for(int i=3; i<n; i+=2) if(a[i/2] ==1) { prime[m++] = i; System.out.printf("%5d", i); if(m % 10 == 0) System.out.println(); } System.out.println("\n全部で " + m + "個あります"); } }
new
で初期値を定義するようなデータ型の配列を定義することもできる。最初に new
で配列を確保し、配列の各要素ごとに new
を使ってインスタンス化(初期化)する必要がある。
配列宣言:
Car[] myCar = new Car[100]; // 配列を確保 ... for(int i=0; i<100; i++) { myCar[i] = new Car(); // 各変数の初期化 } class Car { // データ型の定義 String model; String owner; int 排気量; int year; }
配列は最初にその大きさを定義するが、実行時に配列の大きさを決める事が出来るクラスとしてArrayList
がある。ArrayList<Integer>
のように、<...>
によってデータ型(データクラス)をそえたものをクラス名のように扱う。
データ型として、int, double, char, boolean
を使う場合は、Integer, Double, Char, Boolean
としなければいけない(ラッパクラスという)。String, Point, Dimension, ...
など大文字で始まるデータクラスはそのままの形を使う(直前の例ならば、ArrayList<Car>
)。
たとえば、「ArrayList<Integer> iArray = new ArrayList<Integer>();
」を実行すると、大きさ10のint
型配列が確保される。add
メソッドにより配列の末尾に新たな要素が追加されるが、確保されている領域では不足する場合は、自動的に追加される。データを取り出す場合は、get
メソッドを使い、配列の添字に対応するインデックスと呼ばれる0から始まる通し番号を指定する。インデックスが範囲を超えた場合、IndexOutOfBoundsException
という例外事象が発生する(ので、 try ... catch
構文の中で実行する必要がある)。
add(Object ob) |
新たな要素を追加する |
add(int i,Object ob) |
指定した位置に新たな要素を挿入する |
set(int i,Object ob) |
指定した位置の要素を指定した要素に置き換える |
get(int i) |
(i+1)番目の要素を取り出す |
size() |
配列の大きさ |
contains(Object ob) |
リストの中に要素obが含まれていればtrue |
indexOf(Object ob) |
指定した要素obの位置(findと同じ、見つからなければ -1) |
lastIndexOf(Object ob) |
指定した要素obの最後の位置(find backward、見つからなければ -1) |
remove(int i), remove(Object ob) |
i番目の要素を削除、あるいは最初の要素obを削除(find & remove) |
removeRange(int i,int j) |
iからjまでの要素を消去 |
clear() |
配列要素の全消去 |
あるサービス窓口にランダムに客が到着し、一人ずつ順番にランダムな時間サービスを受けて退去するとき、客の待ち時間を調べる。到着時刻とサービス時間が分かればサービス開始時刻と退去時刻、次の客の到着時刻、が分かるので、これらを順番に計算する。次の到着時刻と次の退去時刻を比べ、到着時刻が早かったら、客の到着時刻、サービス開始時刻、退去時刻を ArrayList
に記憶し、次の到着時刻を更新する。退去時刻が早かったら、退去した客の待ち時間を記録に残し、次の退去時刻を更新する。ArrayList
に記録するデータ(客の番号、到着時刻など)をまとめて、一つのデータ型を定義している。このとき、内部クラスとして定義する場合は static
属性が必要である。
public class MyArrayList { public static void main(String[] args) { ArrayList<Customer> que = new ArrayList<Customer>(); ArrayList<Double> wait = new ArrayList<Double>(); int ID = 0; double ARATE = 0.8, SRATE = 1.0; double nextArrival = -Math.log(Math.random()) / ARATE; double nextDeparture = 10000; double clock = 0; while(clock < 20) { if(nextArrival < nextDeparture) { // 次のイベントは到着 clock = nextArrival; // 現在時刻の更新 nextArrival -= Math.log(Math.random()) / ARATE; Customer q = new Customer(++ID, clock, clock, clock - Math.log(Math.random())*SRATE); if(que.size() > 0) q.start = que.get(que.size()-1).dept; que.add(q); // レコードの追加 System.out.printf("arrival at %6.2f : \n", q.arrv, que.size()); nextDeparture = que.get(0).dept; } else { // 次のイベントは退去 System.out.printf("departure at %6.2f : \n", que.get(0).dept); wait.add(que.get(0).dept - que.get(0).start); que.remove(0); // レコードの削除 clock = nextDeparture; // 現在時刻の更新 if(que.size() > 0) { nextDeparture = que.get(0).dept; } else { nextDeparture = 100000; } } } double sum = 0; for(int i=0; i<wait.size(); i++) { sum += wait.get(i); } System.out.printf("平均待ち時間は %6.3f\n", sum/wait.size()); System.out.println("time over"); } // データクラスの定義(static 属性が必要) static class Customer { int ID; double arrv, start, dept; Customer(int ID, double arrv, double start, double dept) { this.ID = ID; this.arrv = arrv; this.start = start; this.dept = dept; } } }
滞在時間を記録するには、通常の double
型配列でも十分だが、ArrayList
を使えば、添字を気にせず、add
メソッドで追加し、最後に size
メソッドを呼べば、データの個数が分かるので使いやすい。
数学関数のような関数を自分で定義することができる。C++で関数と呼んだものを Java ではメソッドという。0個以上引数を受け取り、まとまった仕事を実行して0個また1個の(数値とは限らない)値を返す。メソッドの一般的な形式は次の通り。
(修飾子) 戻り値の型 メソッド名(引数の並び) { メソッドの定義プログラム }
「修飾子」は public, static
などの属性を書く。 「戻り値の型」は、return
に続けて値が置かれる場合はその値の型を書き、それがない場合は void
と書く。引数は、データ型と変数名をスペースで区切ったペアとし、複数ペアある場合はカンマで区切る。データ型は変数ごとに指定しければいけない。メソッドの定義プログラムは、1個以上の命令文、あるいは宣言文、をセミコロン(と改行)で区切って並べる。複数の命令文を { }
でまとめたコードブロックの場合は、 }
の後のセミコロンは不要。
public static
属性を持つ main()
メソッドはコンパイル後最初に実行されるメソッド、という特別な意味を持つ。
メソッドを実行させるためには、
メソッド名(引数の並び);
という命令文を実行すれば良い。このとき、引数の並びはメソッドの定義で指定された引数の並びと個数、型が一致したもので、(型は指定せず)その値だけを書く。2個以上ある時はカンマで区切る。ここで書かれた引数は、メソッドの定義で書かれた引数と区別するために実引数と呼ばれ、メソッドの定義で書かれた引数は仮引数と呼ばれる。仮引数は変数名でなければいけないが、実引数は変数名でも定数でも、はたまたメソッド実行文であっても、仮引数に対応する型の値が確定するものであれば、なんでも構わない。実引数が仮引数の型と合わない場合、調整される場合もあるが、合わせておいたほうが間違いがない。
配列データを実引数とする場合は配列名だけを書く。文字列データは1文字を要素とする配列とみなされるので、文字配列名だけを書く。配列の長さは、配列名に続けて「.length
」と書くことで参照できるので、引数とする必要はない。
メソッドの中で使用される変数のうち、メソッド内で宣言された変数は、そのメソッドの中だけで有効で、メソッドを終了すると未定義状態になる。宣言なしに変数を使用する場合は、そのメソッドを含むクラスの変数名として定義されていなければいけない(C++ のグローバル変数に対応する)。
同じメソッド名で引数の並び(だけ)が違うメソッドを同じクラス内で定義することができる。この定義の仕方はオーバーロードと呼ばれる。違う型のデータに適用したり、一部の引数を定数化したりするが作業内容は同じ(ようなもの)という場合に使用され、プログラムの可読性のために有効な手法である。
仮引数に int... a
と書き、実引数に int
型数を n 個並べると、a[0], a[1], ... a[n-1]
が実引数としてメソッドに渡されたことになる。この場合、n はメソッドを呼び出すたびに変わっても良い。これを可変変数という。可変変数は、通常の引数と共存できるが、必ず、最後の引数として指定しなければいけない。
メソッドの中から自分自身を呼び出すことができる。このような参照の仕方は再帰呼び出しと言われる。
public static int factorial(int n) { if(n <= 0) return 1; return factorial(n-1) * n; }
if 文は再帰の連鎖を打ち切るために必要で、それがないと暴走する。再帰呼び出しするとき、メソッドの処理は途中なので、その作業内容を確保したまま次の呼び出されたメソッドの処理を開始するので、再帰の連鎖が長くなると場合によってはパンクしてしまうこともありうる。あまり大きな処理プログラムに再帰呼び出しは使わないほうが良い。
Javaのプログラムの基本単位はクラスclassで、1個以上のクラスを集めたものをパッケージpackage としてまとめることができる。単純な課題であれば、一つのクラスで足りるが、そうでない場合は、まとまった仕事単位にクラスを定義し、それらをパッケージにまとめ、有機的に結びつけることで課題解決のプログラムを作るというプログラミングのスタイルは作業の効率化のために有効である。
クラスは0個以上のデータの宣言文とメソッドからなる。クラスは、変数名と同じ規則に従った名前(クラス名)を指定して次のように定義する。宣言文により変数には初期値が設定される。明示的に指定した場合はその値、それがない場合は、数値、char
型は0、論理変数は false
、それ以外は null
となる。
class クラス名 {
0個以上の宣言文
0個以上のメソッド
}
あるいは
public class クラス名 {
...
}
public
は他のパッケージから参照したい場合に付ける修飾子である。
Java のアプリケーションプログラムのパッケージには、(プログラムを起動するために)public static void main()
というメソッドを含むクラスがちょうど1つだけ含まれていなければいけない。
クラスは一から作る場合もあるが、すでに定義されている似た仕事をするクラスの一部を書き換えて新たなクラスとする場合が多い。
class クラス名1 extends クラス名2{
...
}
のように始まるクラスの定義は、「クラス名1」で定義されているメソッド以外は「クラス名2」で定義されているメソッドをそのまま取り込む、ということを意味している。両方のクラスで同じ名前のメソッドがある場合は「クラス名1」のメソッドが優先される。このとき、「クラス名2」のメソッドは「クラス名1」のメソッドにオーバーライドされたという。
「クラス名1」を親クラス(スーパークラス)、「クラス名1」を子クラス(サブクラス)と言い、子クラスは親クラスを継承したという。継承を表すために、子クラスの定義の1行目に「extends
親クラス名」と書く。
Javaには、その目的のために汎用のクラスが多数定義されており、プログラミング作業の軽減に役立っている。(後述のインターフェースを除き)二つ以上の親クラスを継承することはできない。
例えば、次は、Java が定義している Canvas
クラスを継承した子クラスの例である。フィールドはなく、コンストラクタと呼ばれる MyCanvas
メソッドと、paint
メソッドが定義されている。paint
メソッドは親クラスで定義されているメソッドでもあるが、子クラスでオーバーライドされている。
class MyCanvas extends Canvas { MyCanvas(Dimension size) { this.setSize(size); } public void paint(Graphics g) { g.drawRect(10, 10, this.getWidth()-20, this.getHeight()-20); g.drawString("MyCanvas", 20, 40); } }
main() メソッドを含まないクラスは、変数とメソッドの定義集のようなもので、それ自体として実行することはできない。そのメソッドを実行するためには、他のクラス次のような宣言文を使って実体を与える必要がある。
クラス名 変数名 = new クラス名(引数リスト);
二つの「クラス名」は同じものである。基本データ型の変数と同じように、この変数も「クラス名」というデータ型を持つ。この変数は「クラス名」のインスタンス(名)(一例、実体、分身?)と呼ばれる。
「クラス名」で定義されているメソッド・変数は、インスタンス名の後に続けて「.
」+メソッド名、あるいは「.
」+変数名で呼び出すことにより利用することができる。
「クラス名」型の配列変数を定義することもできる。この場合は、配列であることを宣言した後、配列の各要素(それがインスタンスになる)に対して new を使って実体を与えなければいけない。
クラス名[] 変数名 = new クラス名[10];
for(i=0; i<10; i++) {
変数名[i] = new クラス名(引数リスト);
}
ただし、public static void という属性を持つ main() メソッドを持つクラスは特別な意味がある。main と呼ばれるメソッドを持つクラスと、アプレットと呼ばれる Applet
クラスを継承したクラスは、インスタンスを作成することなく、それ独自で実行することが出来る。
次のプログラムは、アプレットの例で、先に定義した MyCanvas
クラスのインスタンスを生成して実行させたものである。こうすることによって、MyCanvas
クラスの MyCanvas, paint
メソッドが実行され、ブラウザに長方形と文字列が表示されるようになる。
public class Test extends Applet { public void init() { add(new MyCanvas(this.getSize())); } } class MyCanvas extends Canvas { MyCanvas(Dimension size) { this.setSize(size); } public void paint(Graphics g) { g.drawRect(10, 10, this.getWidth()-20, this.getHeight()-20); g.drawString("MyCanvas", 20, 40); } }
次のプログラムは、他で定義されているクラスの変数を、インスタンスを作らずにクラス名を直接使って呼び出して利用する例である。この場合、変数は必ず static 属性の指定が必要である。
class Test { public static void main(String[] args) { int n = (int)(Math.random() * 7); System.out.println("今日は " + Days.DOTW[n] + "曜日です"); } } class Days { public static String[] DOTW = {"日","月","火","水","木","金","土"}; }
クラスのインスタンスはオブジェクト(実体)と呼ばれ、オブジェクトの機能を組み合わせてプログラムができあがるところから、Java はオブジェクト指向言語と呼ばれている。
上の MyApplet
クラスにある先頭の public
は、アクセス修飾子と呼ばれ、他のパッケージからもアクセス可能であることを示す。こうすることで、実行時に最初に実行されるクラスであることを指定している。Days
クラスの DOTW
フィールドの先頭にある public
アクセス修飾子は、パッケージ内の他のクラスからアクセス出来ることを指定している。
クラスのメソッドとして、コンストラクタと呼ばれるクラス名と同じ名前を持つ特別なメソッドがある。クラスのインスタンスが作られるときにそのメソッドが呼び出されて実行される。いわば、初期化作業を集めたもの、と言ってよい。コンストラクタはメソッドなので、引数リストを指定できる。インスタンスを作るときは、その引数リストに対応する実引数を指定しなければいけない。戻り値を設定できないので、メソッド名の前に「戻り値の型」は書かない。
初期設定する変数の仮変数として、同じ変数名を使用することがある。その場合、変数に値を設定する代入文の左辺と右辺が同じ名前になって混乱する。そのわかりにくさを避けるために(このクラスの、という意味で)this キーワードがある。変数名を x としたとき、this.x = x; と書くと、仮変数の x の値をこのクラスの変数 x に代入する、という命令になる。
引数リストの異なる、複数のコンストラクタを定義することができる。これを(メソッドの場合と同じように)コンストラクタのオーバーロードという。
先に定義した MyCanvas
では、キャンバスの大きさを決める setSize
という、親クラスで定義されているメソッドを実行して、引数で渡された大きさを設定している。
変数の宣言文、あるいはメソッドの修飾子として private を指定すると、その変数、あるいはメソッドは、そのクラスの内部だけでしか参照できなくなる。変数を private 指定することで、その変数内容を勝手に変更できないようにすることができ、データを保護することができる。(そのクラス内部でだけ意味のある)メソッドを private 指定することで、クラスのアクセス環境を単純化することになり、使い勝手が向上することが期待できる。このような考え方はカプセル化と呼ばれる。
メソッドのないクラスは、レコードとも呼ばれ、新しいデータ型を定義するために使われる。たとえば、名前、住所、職業などの個人データを一つのクラスにまとめ、一つの名前で参照できるような場合である。
class PersonalRecord {
String name, address, tel, occupation;
}
使う場合は:
class ... {
PersonalRecord taro = new PersonalRecord();
...
System.out.println(taro.name + " の電話番号は " + taro.tel);
}
メソッドがあっても、コンストラクタだけ、というクラスもある。例えば、次は3次元の点というデータ型を定義したクラスの例である。
class Point3D {
int x, y, z;
Point3D(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
}
使う場合は:
class ... {
Point3D origin = new Point3D(0,0,0);
...
}
クラスは多くの場合、他のクラスと独立に作られるが、あるクラスの中だけでだけ使用されるクラスはそのックラスの中で定義することができる。これを内部クラスという。ウィンドウの「閉じる」ボタンのクリックを処理する、マウスの動きによって発生するイベントを処理するなどの場合によく使われる。
Java は共同作業によって大きなプログラムを作ることを想定しており、その作業環境を整えることを考えてクラスと継承という仕組みを作った。その目的で、多くの汎用クラスがシステムによって提供されているので、それらを利用することで、プログラムの枠組みが標準化され、作業が効率的に行われるように工夫されている。さらに、継承により、すべての作業を1から始めることなく、必要なパーツを組み合わせることで素早く目標を達成することが出来るようになっている。
しかし、継承という便利な仕組みには複数の親クラスを継承することが出来ない、という大きな制約がある。この制約をなくすための工夫がインターフェースである。インターフェースはクラスの一種で、変数の宣言文とメソッドからなるが、次のような制約がある。
extends
の代わりに)「implements
+ インターフェース名」と書く。3番目と4番目の制約から、インターフェースを共有すれば、同じ名前を使って作業する環境が整うことになる。親クラスと異なり、インター フェースは複数を同時に実装することが出来るので、作業目的毎に作成しておけば管理しやすくなる。次のプログラムは、前に出したクラスのフィールドを直接 参照する例を、インターフェースを使って書き直したものである。変数の属性として必要だった public static
が不要になっている。
class Test implements Days { public static void main(String[] args) { int n = (int)(Math.random() * 7); System.out.println("今日は " + Days.DOTW[n] + "曜日です"); } } interface Days { String[] DOTW = {"日","月","火","水","木","金","土"}; }
クラスと同様、様々な汎用インターフェースがシステムによって用意されている。次は、ボタンクリックの処理プログラムとして ActionListener
と呼ばれるインターフェースを実装して、actionPerformed
メソッドをオーバーライドした例である。ボタンをクリックすると actionPerformed
メソッドが実行され、ボタンを識別すると、架空の曜日を生成して表示する。
public class Test extends Applet implements ActionListener, Days{ JLabel lb = new JLabel("..."); public void init() { JButton bt = new JButton("架空の曜日"); bt.addActionListener(this); add(bt); add(lb); } public void actionPerformed(ActionEvent e) { if(e.getActionCommand() == "架空の曜日") { int n = (int)(Math.random() * 7); lb.setText("今日は " + Days.DOTW[n] + "曜日です"); } } } interface Days { String[] DOTW = {"日","月","火","水","木","金","土"}; }
インターフェースを実装した場合は、すべてのメソッドを定義しなければいけないという制約がある。例えば、ウィンドウの「閉じる」ボタンをクリックしたときに発生する WindowEvent
を処理するために WindowListener
というインターフェースを使うが、そこには普段は使わないメソッドがいくつも用意されていて、それらに空のメソッドを全部定義しなければならないのは煩わしい。そのために、インターフェースを実装したクラスをあらかじめ定義しておき、そのクラスを継承したサブクラスを作るようにすれば、インターフェースを実装したのと同じ効果が得られる。この目的で作られたクラスはアダプターと呼ばれる。MouseLitener
に対する MouseAdapter
などがそれである。