「ソフトウェア演習(B)Java言語」
Computer Programming III (Java)
ベーシックコース (Basic Course)

第2回 Java文字列(日本語等を含む)+多次元配列、クラスとアクセス制御

入力テキストデータを行単位に書き出すプログラム例 (Line-by-Line Output Example fo Input Text)

前回、プログラム1-2で、テキストファイルから文字単位で読込み、 そのまま出力するプログラムを紹介しました。 今回は、復習として、まず行単位にテキストデータを読込み、 これをそのまま出力するプログラムを見ていきます。
LineIO.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
/**
	プログラム2-1:行単位のテキストファイルのIOプログラム例
	ファイル(端末)から一行ずつ入力してそのままプリントする
*/
import java.io.*;

public class LineIO {/* クラス名:LineIO(ファイル名=LineIO.javaの拡張子以外と一致) */
    public int fileIO(File fileIn, File fileOut) {
        try {
            /* 入力ストリームは、標準入力かファイル入力かでスイッチ */
            BufferedReader br =     /* バッファリングして読み込む */
                (fileIn==null) ? new BufferedReader(new InputStreamReader(System.in))
                : new BufferedReader(new FileReader(fileIn));
            /* プリントストリームは、標準出力かファイル出力かでスイッチ */
            PrintStream ps = (fileOut==null) ? System.out : new PrintStream(fileOut);
            
            String line; /* 行単位の文字列を保持 */
            while ((line = br.readLine()) != null){
                ps.println(line);/* プリントストリームに書き出す */
            }
            if (fileIn != null) br.close();/* 読み込んだファイルのクローズ */
            if (fileOut != null) ps.close();/* プリントストリームのクローズ */
        } 
        catch (Exception e) {   /* エラーをつかまえる */
            System.err.println(e);  /* エラーメッセージ出力 */
            return 1;   /* 終了コード 1 */
        }
        return 0;
    }
    public static void main(String[] args){

        try {
            File fileIn=null, fileOut=null;
            LineIO cio = new LineIO();/* クラスのインスタンス作成 */
            if (args.length == 2){/* expects $ java(u) LineIO A B */
                fileIn = new File(args[0]);/* 入力テキストデータ */
                fileOut = new File(args[1]);/* 出力プリントデータ */
            }
            else if (args.length == 1)/* expects  $ java(u) LineIO A (>B) */
                fileIn = new File(args[0]);
            else if (args.length == 0) ;/* expects $ java(u) LineIO B) */
            else {
                System.err.println("java LineIO [inFile] [outFile]");
                System.exit(2);
            }
            int rc = cio.fileIO(fileIn, fileOut);/* クラス内の関数の呼出し */
        }
        catch (Exception e) { e.printStackTrace(); }
    }
}
コンパイルは、

$ javacu LineIO.java

実行例としては、I/Oともにファイルの場合は

$ javau LineIO input.txt Output.txt

とします。この場合が35行目にあるようにargv.lengthが2となります。 入力だけファイルから与え、出力は標準出力の場合は

$ javau LineIO input.txt

とし、さらに結果をOutput.txtに切り替えたいときは、

$ javau LineIO input.txt > output.txt

で動作します。 入力を標準入力、出力を標準出力としたいときは、

$ javau LineIO < input.txt > output.txt

とします。なお、入力だけ標準入力、出力はファイル名とする場合には、上記のプログラムでは対応していません。

この例の中身を説明すると、入力でファイルまたは標準入力を使う場合、 標準入力のSystem.in InputStreamクラスであることを知っている必要があります。 12行の中で、InputStreamクラスを引数とする InputStreamReaderクラスのオブジェクト生成を行っています。 しかし、InputStreamReaderクラスのコンストラクタでは、ファイル名からインスタンスを 作ることはできません。 そこで、13行目にある FileReaderクラスを使っています。 ただし、幸いなことに、 BufferedReaderクラスのコンストラクタでは、 InputStreamでも、FileReaderでも、引数としてオブジェクトを生成できますので、 18行名にある行単位の読込では、BufferedReaderクラスのreadLineメソッドで読むことができます。 出力は、FileWriterのようなクラスを使ってもいいのですが、 ここでは、System.outに合わせ、 PrintWriterクラスを使っています。 書き出しでは19行目にあるようにPrintStreamクラスのprintln関数を使っています。 これはSystem.out.printlnと同じもので、出力先がファイルもありえる点だけが違っています。

テキストデータの種類

現在、Web上には、膨大なテキストデータがあります。 それらは、Webのニュース、Twitter、掲示板、ブログ、 統計データなど様々な形態をとります。 このようなテキストデータを3つに大別してデータ処理することが多いです。 構造化データは、代表的なものが関係データベースに含まれるデータ、 あるいは表形式(ExcelやCSV等を含む)のデータで、 各レコードに現れるテキストフィールドの数が一定のものです。 半構造化データは、 HTMLに代表されるWebページのように、 タグがついているが表形式には完全に対応できないテキストデータを意味します。 非構造化データとは、 タグもなく、単にテキストデータが含まれるデータを意味します。

半構造化データをタグに応じて処理するためには、 XMLフォーマットを仮定して処理するのが半構造化データの扱いで最もポピュラーな手法です。 こちらは、Java言語では、javax.xmlパッケージなどを利用して処理できます。 近年のWebアプリケーションやデータマイニング、人工知能の応用分野では、 XMLのほか、もう少しシンプルに名前と値のペアで半構造(階層構造)を作れるJSON形式でのデータ提供も増加してきています。 代表例は、深層学習のフレームワークのひとつであるCaffeで採用されているprototxtで、 ニューラルネットワークの構造やハイパーパラメータをJSON形式で記述して与えます。 Java言語では、JSON形式に関しては、Jacksonと呼ばれるライブラリを主に使って (AndroidのJavaではJSONのI/Oを行うクラスが標準装備されています) (パージング)することが一般的です。

一方、以下では非構造化データとして文字列を扱うプログラム例を示します。

Javaでの非構造化テキストに対する文字列処理

非構造化テキストに対する文字列処理では、 いかに「単語」を取り出すかにおいて、 テキストの書かれる言語により処理が多少異なります。 具体的には、英語に代表される西欧の言語では、 スペースや句読点で単語が区切られます。 たとえば、前回紹介したStringクラスのsplitメソッドで単語を切り出すことができます。 一方、日本語など東洋の言葉では、 句読点はありますが、スペースで単語が区切られることはなく、 文字列から単語を切り出すのは構文解析(あるいは形態素解析)が必要となります。 代表的な形態素解析器には、 MeCab, ChaSen, Jumanなどが知られています。 これらのツールを使えば、文から単語や品詞情報を得ることができます。 Javaで作ったMeCabによる形態素解析例がありますので、 自然文から品詞情報が取れる様子を体験してみてください。

Stringクラス

Java言語では、Stringクラスにより、文字列処理を行うことができます。 Stringクラスには、文字列処理を行う様々な関数が用意されています。 Stringは、それ自体オブジェクトですが、 生成する場合、他のオブジェクトとは少し違った手法で生成できます。 すなわち、

String s = "こんにちは";

と書いて

String s = new String("こんにちは");

と同じ意味を表すことができます。 これはまた、文字配列で以下のように定義した場合とも同じになります。

char [] data = {'こ','ん','に','ち','は'};
String s = new String(data);

連結演算

Java言語のStringには、C言語ではなかった文字列の連結 が+(プラス演算)で簡単に実現できます。 演算子の優先順位としては、通常の加算の+(プラス演算)と同じです。 連結演算の使用例として、 たとえば、

String s = "こんにちは"+"世界のみなさん";



String s = "こんにちは世界のみなさん";

と同じです。 もちろん、このような文字列リテラルの代わりにString変数s1とs2があって、 これらに値が入っているとき

String s = s1 + s2;

のように連結演算を利用することができます。 以下に連結演算を利用したサンプルを示します。
AddString.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
	プログラム2-2: Stringクラスでの文字連結
*/
import java.util.Scanner; /* Scannerクラスを利用 */
public class AddString {
	/* main関数*/
	public static void main( String[] args ) {
		Scanner input = new Scanner( System.in );/* System.in(端末から)*/

		System.out.print( "第一の文字列を入力してください  > " ); /* プロンプト */ 
		String s1 = input.next(); /* 第一引数を文字列として読込む (next関数) */

		System.out.print( "第二の文字列を入力してください  > " ); /* プロンプト */ 
		String s2 = input.next(); /* 第二引数を文字列として読込む (next関数) */

		String sum = s1 + s2; /* 連結演算の実行  */
		System.out.printf( "連結結果 = %s\n", sum ); /* 結果の表示 */
	} /* main関数のおわり */
} /* AddStringクラスのおわり */

部分文字列の取り出し、検索

文字列操作で、連結と同時によく使用するのが文字列の一部を取り出したり、 特定の文字列が含まれているかどうか検索することです。 具体例で示しましょう。
SubString.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
/*
	プログラム2-3: Stringクラスでの文字の一部検索
*/
import java.util.Scanner; /* Scannerクラスを利用 */
public class SubString {
	/* main関数*/
	public static void main( String[] args ) {
		Scanner input = new Scanner( System.in );/* System.in(端末から)*/

		System.out.print( "文字列を入力してください  > " ); /* プロンプト */ 
		String s1 = input.next(); /* 第一引数を文字列として読込む (next関数) */

		System.out.print( "検索したい部分文字列を入力してください  > " ); /* プロンプト */ 
		String s2 = input.next(); /* 第二引数を文字列として読込む (next関数) */

		if (s1.indexOf(s2)>=0){/* 文字列s1にs2が含まれれば0以上を返す */
			int position = s1.indexOf(s2);/* s2文字列が開始する位置 */
			int length = s2.length();/* 文字列s2の長さ */
			String sub = s1.substring(position,position+length);/* 部分文字列の取り出し */
			int lastIndex = 0;
			int count = 0;
			while (lastIndex != -1){/* 何回現れるかカウントする */
    		           lastIndex = s1.indexOf(sub,lastIndex);
    		           if (lastIndex != -1){
        	             count ++;
        	             lastIndex += sub.length();
    		           }
			}
			System.out.println(s1+" に部分文字列『"+sub+"』が"+count+"回、含まれます"); /* 結果の表示 */
		}
		else
			System.out.println(s1+" に部分文字列『"+s2+"』は含まれません"); /* 結果の表示 */
	} /* main関数のおわり */
} /* SubStringクラスのおわり */
これを実行した結果例を以下に示します。
$ javau SubString
文字列を入力してください  >  読書の秋だね。食欲の秋でもあるよ。
検索したい部分文字列を入力してください  > 秋
読書の秋だね。食欲の秋でもあるよ。 に部分文字列『秋』が2回、含まれます

$ javau SubString
文字列を入力してください  > 読書の秋だね。食欲の秋でもあるよ。
検索したい部分文字列を入力してください  > 空き
読書の秋だね。食欲の秋でもあるよ。 に部分文字列『空き』は含まれません

$ javau SubString
文字列を入力してください  > #好きなんだ
検索したい部分文字列を入力してください  > 好き
#好きなんだ に部分文字列『好き』が1回、含まれます

      SubStringクラスの実行例
さて、部分文字列に関しては、 まず、その文字列があるかどうか検索することが必要になります。 これは16行目にあるs1.indexOf(s2)という関数で行えます。 具体的にはs2がs1に含まれれば、 0以上の値(その文字列がs1中に含まれる位置(先頭は0)を返してくれます。 そうでなければ、-1を返します。 この例では事前に部分文字列を入力させるので、自明ですが、 そうでないとき、 部分文字列を取り出すには19行目にあるs1.substring(position,position+length)のような関数で取り出します。 この意味は、 positionで示される位置にある文字からposition+lengthにある位置の文字までを文字列として取り出します。

Java言語での文字列ソーティング例

Java言語には、システムが準備している便利なソーティングを行う関数があります。 それを紹介する前に、 もっとも素朴なソーティング法である「交換ソーティング法」をみていきましょう。 「交換ソーティング」では、データの大小を比較する演算と、 データを交換する機構が必須となります。 C言語では、交換を行うために以下のような関数を利用しました。

/* C言語で文字列(文字型へのポインタ)aとbを入れ替える関数 (成功例)*/
void swap(char **a, char **b){
    char *tmp;
    tmp = *a;
    *a = *b;
    *b = tmp;
    return;
}
これを用いてswap(&a, &b)のように、文字列を入れ替えることができました。 Java言語ではどのようにすればいいでしょうか? Javaには、ポインタ型がないので、 上述のような方法では、データを交換できません。 しかし、交換したいも文字列データは、通常配列に入っていると考えれば、 以下のようなコードで交換することができます。
public void swap(String[] data, int i, int j ){
	String tmp;
	tmp = data[i];
	data[i] = data[j];
	data[j] = tmp;
}
これを利用すると、以下のようなソーティングプログラムが 可能です。
SwapString.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
/*
	プログラム2-4: Stringクラスでの文字列の交換を利用したソーティング
*/
public class SwapString {
    public void swap(String[] data, int i, int j ){/* i番目とj番目のデータを交換 */
        String tmp;
        tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }
    public void sortWords(String[] words){ /* バブルソート */
        if (words == null){
            System.err.println("データがnullです");
            return;
        }
        for ( int i = 0 ; i < words.length ; i++ ){
            for ( int j = i+1 ; j < words.length ; j++ ){
                if (words[i].compareTo(words[j]) > 0){/* lexicographic order */
                    swap(words, i, j);/* データの交換 */
                }
            }
        }
        return;
    }
    /* main関数 */
    public static void main(String[] args) {
        String [] month = {/* 1月から12月までの英語 */
            "January", "February", "March", "April",
            "May", "June", "July", "August",
            "September", "October", "November", "December"
        };
        String [] season = { "春", "夏", "秋", "冬" };
        SwapString ss = new SwapString();
        ss.sortWords(month);/* ソートの呼び出し(1) */
        /* 結果の書き出し (1)*/
        for (int i = 0 ; i < month.length; i++)
            System.out.println(month[i]);
        ss.sortWords(season);/* ソートの呼び出し(2) */
        /* 結果の書き出し (2)*/
        for (int i = 0 ; i < season.length; i++)
            System.out.println(season[i]);
    }
} /* SwapStringクラスのおわり */

文字列から他のデータ型への変換

文字列から、他のデータ型への変換は、頻繁に登場しますので 表形式でまとめておきます。

データ型 関数 意味
バイト(byte) Byte.valueOf(s).byteValue() 文字列sをバイトに変換
バイト(byte) Byte.parseByte(s) 文字列sをバイトに変換
文字(char) s.charAt(i) 文字列sのi番目の文字を取り出す
文字(char) Character.valueOf(s).charValue() 文字列sを文字に変換
文字(char) Character.parseChar(s) 文字列sを文字に変換
整数(short) Short.valueOf(s).shortValue() 文字列sをshort型整数に変換
整数(short) Short.parseShort(s) 文字列sをshort型整数に変換
整数(int) Integer.valueOf(s).intValue() 文字列sをint型整数に変換
整数(int) Integer.parseInt(s) 文字列sをint型整数に変換
整数(long) Long.valueOf(s).longValue() 文字列sをlong型整数に変換
整数(long) Long.parseLong(s) 文字列sをlongg型整数に変換
実数(float) Float.valueOf(s).floatValue() 文字列sをfloat型実数に変換
実数(float) Float.parseFloat(s) 文字列sをfloat型実数に変換
実数(double) Double.valueOf(s).doubleValue() 文字列sをdouble型実数に変換
実数(double) Double.parseDouble(s) 文字列sをdouble型実数に変換



日本語文字例処理例

日本語は、最初に述べたように、単語同士がスペース区切りされないため、 単語を取り出すのは、形態素解析(MeCab, ChaSen, 等) を利用しない限り、容易ではありません。 しかし、C言語と異なり、char型が2バイトで、 Unicodeとして扱えますので、すべての日本語は英語と同様な処理ができると期待されます。 中でも、(全角の)「ひらかな」と「カタカナ」が、 Unicode中にどう分布しているかは、知っていて損はないです。 全角の< a name="カタカナ">「カタカナ」に関しては、 以下のように「ァ」からはじまり、「ヶ」までがUnicode中で連続しています。

ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾ
タダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポ
マミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶ
同様に、全角「ひらかな」に関しては、 以下のように、「ぁ」から始まり、 「ん」までがUnicode中で連続しています。
ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞ
ただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽ
まみむめもゃやゅゆょよらりるれろゎわゐゑをん
全角ひらかなを全角カタカナに変更したい場合は、 以下のように Stringクラス を用いて、「ひらかな」と「カタカナ」のUnicodeでの出現順序を利用して変換することが可能です。
H2K.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
/*
	プログラム2-6:全角ひらかなを全角カタカナに変換
*/
public class H2K {
  /*
   * ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞ
   * ただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽ
   * まみむめもゃやゅゆょよらりるれろゎわゐゑをん
   * 
   * ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾ
   * タダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポ
   * マミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶ
   */
  public static String ZenkakuH2K(String ss) {
	String s = new String(ss);/* 入力文字列をコピー */
	String output = "";
	for (int i = 0; i < sb.length(); i++) {
		char c = s.charAt(i);/* i番目の文字の取り出し */
		if (c >= 'ぁ' && c <= 'ん') {/* 全角ひらかなの範囲であれば */
			output = output + (char)(c - 'ぁ' + 'ァ');/* 全角ひらかなを全角カタカナに変換 */
		}
	}
	return output;
  }
  public static void main(String[] args) {
	System.out.println(ZenkakuH2K("ぷろぐらみんぐ"));
  }
} /* H2Kクラスのおわり */

英単語の切り出し例

以下では、Stringクラスを用いたデータ処理の応用例として、 読み込んだテキストからアルファベットを行単位で見い出し、 これをハッシュマップで管理して、単語と単語の出現頻度を書き出すプログラム例です。 Java5.0で導入されたジェネリクスやfor/inループなどを利用した例です。 これらの中身や、個々のクラスや関数の意味は、今後の講義の中で解説していきますので、 まずは、プログラムの書き方に慣れることを目的で利用してみてください。 なお、幾つかのクラスや関数は、サーバにあげたマニュアルも参照してください。 ここで使用しているのは、 Mapクラス、 TreeMapクラス、 Map.Entryインタフェースです。
WordToken.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
85
86
87
88
89
90
91
92
93
94
95
/*
  プログラム2-7: 英単語のカウンタクラスを作ってみる
  WordToken.java (トークンと出現回数を管理するクラス)
*/
import java.util.*;
import java.io.*;
import java.text.Format;/* フォーマットのため */
import java.text.DateFormat;/* 日付のフォーマット用 */

public class WordToken {
  public String[] myName = {/* プリントヘッダー*/
        "******************************",
        "作成者:青野雅樹:01162069",
        "日付:",
        "入力ファイル名:",
      "******************************"
  };
  public void myPrint(String name){/* mainの最初に呼ばれる */
        Date now = new Date();/* Dateクラスのオブジェクト生成 */
        Format fmt= /* 日付を日本語ロカールに従ってフォーマット */
            DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.LONG);
        for (int i = 0 ; i < myName.length ; i++){
            System.out.print(myName[i]);
            switch (i) {
                case 2: System.out.print(fmt.format(now)); break;/* 日付 */
                case 3: System.out.print(name); break;/* データファイル名 */
            }
            System.out.print('\n');
        }
  }
  public String getEnglishTokens(String line){/* lineに含まれる英単語候補を返す */
    if (line == null || line.length()<=0) return null;
    int length = line.length();/* 行の長さ */
    String token = "";/* トークンを保持 */
    String output="";/* 行の中で見つかったトークン列を保持 */
    int i = 0;
    while ( i < length ){/* 文字列の長さ以内の間 */
      token = "";
      char c = line.charAt(i);/* i番目の位置の文字を取り出す */
      while ( ('A' <= c  && c <= 'Z') || ('a' <= c && c <= 'z')) {/*アルファベット */
	if ( 'A' <= c && c <= 'Z' )
	  c = (char)(c - 'A' + 'a');/* 小文字に変換:整数⇒文字型へはキャストが必要 */
        token = token + c;/* 足し算で文字を連結 */
        i++;
        if (i >= length) break;/* 文字列の長さ以内かどうかcharAt関数前にチェック */
        c = line.charAt(i);
      }
      if (token.length() >= 1)
        output = output + " " + token;/* 単語を連結 */
      i++;
    }
    return output;
  }
  public static void main(String[] args){
    String fileName = args[0];/* 第一引数 */
    if (args.length != 1){
      System.out.println("java(u) WordToken [ファイル名]");
      System.exit(0);
    }
    WordToken wt = new WordToken();/*クラスのオブジェクトのインスタンスを作る */
    /* TreeMap: キーが文字列, 値がIntegerクラス, 結果がソートされる */
    Map <String, Integer>wordset = new TreeMap <String, Integer>();
    try {
      wt.myPrint(args[0]);
      FileReader fr = new FileReader(args[0]);/* FileReaderクラスのオブジェクト生成 */
      BufferedReader br = new BufferedReader(fr);/* BufferedReaderクラスのオブジェクト生成 */
      String line;/* 行単位の読み込んだ文字列を保持 */
      String tokens;/* 行中のトークンを保持 */
      while ((line = br.readLine()) != null){/* 行単位で処理 */
        tokens = wt.getEnglishTokens(line);
        if (tokens == null) continue;
        String[] result = null;/* split関数の結果を保持する文字配列 */
        result = tokens.split("\\s+"); // トークンを分離
        if (result.length > 0){
          for (int i = 0 ; i < result.length; i++){/* トークンの数だけ繰り返す */
            String token = result[i];
            if (token.length()<1) continue;
            if (wordset.containsKey(token)){/* tokenがすでにあるかどうか */
              int value = wordset.get(token);/* Mapクラスの値をゲット */
              wordset.put(token, value+1);/* カウントアップさせる */
            }
            else 
              wordset.put(token, 1);/* Mapに新規に単語とカウント(値)を加える */
          }
        }
      }
      for (Map.Entry <String, Integer> e : wordset.entrySet()){ /* for/inループで出力 */
        System.out.println(e.getKey()+" "+e.getValue());/* キーを値をプリント */
      }
    }
    catch(Exception e){
      e.printStackTrace();/* エラー処理 :トレース*/
    }
  }
}
プログラム2-2を、上記のプログラム2-7の入力データとして実行した結果例を以下に示します。


$ javacu WordToken.java

$ javau WordToken WordToken.java
******************************
作成者:青野雅樹:01162069
日付:2019年10月8日 18:56:49 JST
入力ファイル名:WordToken.java
英単語を頻度つきで書き出す
******************************
a 5
args 5
br 2
break 3
bufferedreader 3
c 11
case 2
catch 1
char 2
charat 3
class 1
containskey 1
continue 2
date 3
dateformat 4
e 5
else 1
entry 1
entryset 1
exception 1
exit 1
filename 1
filereader 3
fmt 2
for 4
format 3
fr 2
full 1
get 1
getdatetimeinstance 1
getenglishtokens 2
getkey 1
getvalue 1
i 17
if 9
import 4
in 1
int 5
integer 4
io 1
java 6
length 11
line 10
long 1
main 2
map 4
myname 3
myprint 2
n 1
name 2
new 5
now 2
null 5
out 6
output 4
print 4
println 2
printstacktrace 1
public 5
put 2
readline 1
result 5
return 2
s 1
split 2
static 1
string 15
switch 1
system 7
text 2
token 13
tokens 4
treemap 2
try 1
u 1
util 1
value 2
void 2
while 3
wordset 6
wordtoken 5
wt 3
z 3
WordTokenクラスの実行例



多次元配列、クラスとアクセス制御

Javaでの多次元配列

Java言語では、型名のあと、角括弧を与え、配列を定義します。 たとえば、文字の一次元配列はchar [ ]のようになります。 同様に、文字の配列の配列は、char [ ][ ]のようになり、 これが2次元配列と、ほぼ同様に利用することができます。 さらに、 文字の配列の配列の配列でchar [ ][ ][ ]のように角括弧を3回つなげると3次元配列となり、 このように複数の角括弧があるものを「多次元配列」と呼びます。

多次元配列を実際に使う場合は、new演算子でメモリを割り当てる必要があります。 たとえば2x2の行列では、 以下のように定義します。

double [ ][ ] matrix = new double [2][2];


また、100人の受験者の5科目の試験結果を 整数配列に保持したいような場合、

int [ ][ ] result = new int[100][5];


のように宣言して、データを保持させれば実現できます。 注意しないといけないのは、C言語の多次元配列と違って、 100x5=500個の整数が連続したメモリに確保される保証がない、ということです。 上の例では、以下のことがJavaインタープリタの起動後に実行されます。 これは以下のようなコードと同じです。
int [ ][ ] result = new int[100][];
for (int i = 0 ; i < 100 ; i++)
	result[i] = new int[5];
C言語でいえば、new演算子はないので、
int **result;
result = (int **)malloc(100*sizeof(int*));
for (i = 0 ; i < 100; i++)
	result[i] = (int *)malloc(5*sizeof(int));
と同等です。また、C++言語でいえば、
int **result;
result = new int*[100];
for (i = 0 ; i < 100; i++)
	result[i] = new int[5];
と同等です。 なお、多次元配列では、一般的に、 一番左の要素の数さえ決まっていれば、いいことになっています。 このことから容易に推察できるように、上述のJava言語では、

result.length

は、100となり、

result[i].length

(0 <= i <= 99) は、5となります。 一般に、多次元配列の中でも2次元配列は、要素が決まると、 「長方形」の大きさ(上述の例では、100x5)のサイズが確保されることが多いです。 これが常にそうではないことを以下の例で紹介します。
MultiDimensionalArray.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
/*
プログラム2-8: 多次元配列クラス
*/
public class MultiDimensionalArray {

   public static void main( String[] args ) {

		int [][] result = new int[100][];/* 2次元配列:2次元目は無指定 */
		for (int i = 0 ; i < 100 ; i++)/* 動的にnewでメモリ確保 */
			result[i] = new int[5];
		System.out.println("配列resultの大きさ = "+result.length);
		for (int i = 0 ; i < 5 ; i++)
			System.out.printf("配列result[%d]の大きさ = %d\n",i,result[i].length);
		System.out.print("\n");

		/* 別な2次元配列:2次元目は初期化で指定 */
		int [][] products = { 
		{0},
		{0, 1},
		{0, 2, 4},
		{0, 3, 6, 9},
		{0, 4, 8, 12, 16}};
		System.out.println("配列productsの大きさ = "+products.length);
		for (int i = 0 ; i < products.length ; i++)
			System.out.printf("配列products[%d]の大きさ = %d\n",i,products[i].length);

	} /* main関数のおわり */
} 
これを実行すると以下のようになります。
$ javacu MultiDimensionalArray.java

$ javau MultiDimensionalArray
配列resultの大きさ = 100
配列result[0]の大きさ = 5
配列result[1]の大きさ = 5
配列result[2]の大きさ = 5
配列result[3]の大きさ = 5
配列result[4]の大きさ = 5

配列productsの大きさ = 5
配列products[0]の大きさ = 1
配列products[1]の大きさ = 2
配列products[2]の大きさ = 3
配列products[3]の大きさ = 4
配列products[4]の大きさ = 5

Javaの2次元配列を含むプログラム実行例
これより、わかるように動的に、異なるサイズを2次元目以降の配列で確保することができますので、 必ずしも、多次元配列では、 全体として長方形のメモリ領域が確保されるわけではないことが明らかだと思います。

配列の初期値

C言語やC++言語では、配列の宣言で中身が初期化される保証はありません。 一方、 Java言語では、配列をnew演算子で定義すると、そのサイズ分だけ、型に応じて配列の中身が初期化されます。 まとめると、以下の表のようになります。最初の4行は、Javaでは、 基本データ型(primitive type)と呼ばれます。原始型は予約語でいずれも小文字ではじまります。 C言語やC++言語にも、ほぼ同様の型があるので、容易に想像つくかと察します。 なお、表にある参照型(reference type)とは、Java言語では、クラス変数、 配列型(原始型では、new演算子が適用される前の状態)、enum型などを意味します。

配列の初期化でセットされる値
型(配列) 初期値
boolean型false
char型'\u0000'
整数型(short, int, long)0
実数型(float, double)0.0
参照型(例:String)null

Javaでの配列に関する便利な機能

Java言語では、たとえばaという名前の変数の配列を定義すると、配列のインデックスは値として0からa.length-1まで定義でき、最初の要素にa[0]で、最後の要素にa[a.length-1]でアクセスすることができます。

ある配列のオブジェクトをごっそりコピーしたいときは、 clone()関数を使って、

int [] prime = { 2, 3, 5, 7, 11, 13, 17, 19};
int [] copy = (int []) prime.clone();
のように、書くことができます。 また、System.arraycopy()関数を使うと、どのインデックスから何個、 別な配列の、どのインデックスにまとめてコピーすることができます。
int [] prime = { 2, 3, 5, 7, 11, 13, 17, 19};
int [] array = new int[10];
System.arraycopy(prime, 1, array, 3, 5);
for (int i = 0 ; i < array.length ; i++)
		System.out.printf("array[%d] = %d\n",i,array[i]);
なお、上例のarraycopy()を実行した結果は、
array[0] = 0
array[1] = 0
array[2] = 0
array[3] = 3
array[4] = 5
array[5] = 7
array[6] = 11
array[7] = 13
array[8] = 0
array[9] = 0
と表示されます。最初の3つは、インデックスが0~2までが、初期化された値のままで、 そのあと、コピー元のデータのインデックス1から5つだけデータがまとめてコピーされます。


クラスとアクセス制御

クラス

クラスとは、 いろいろな変数と関数(メソッド)の集合体に他なりません。 C言語の構造体(struct)でも、いろいろな変数をまとめることはできました。 しかし、関数を定義することはできませんでした。

一方、クラスには、本来、意味(セマンティックス)や概念があり、 「何か」を行う場合の単位となることが暗黙に期待されています。

概念には、上位概念や下位概念が定義でき、 それぞれスーパークラス(基底クラス)サブクラス(派生クラス)に相当します。

たとえば、「(四輪)自動車」というクラスを作成したとします。 上位には、「(何らかの動力で動く)車」という概念があり、 下位には、「ガソリン車」「ハイブリッド車」「電気自動車」などの概念があり、さらに、 個々のメーカーの自動車の名称をその下の概念として定義することができます。 「ハイブリッド車」のインスタンス(事例)として「プリウス」があります。 また、「大学」という概念には、上位語として「学校」や「教育機関」などがあり、 下位語として「国立大学」「私立大学」「公立大学」などがありえます。 さらに「国立大学」のインスタンス(事例)として「豊橋技術科学大学」があります。 このように、現実社会の様々な(階層的な)概念を(本来)自然に表現するのがクラスです。 なお、Java言語では、すべての".java"ファイルでそのファイル名と同じクラスがファイル内で定義されていることが期待されています。

クラスは、変数(メンバ変数)と関数(メンバ関数、メソッド)から成りますが、 クラスを定義することは、多くの場合、そのクラス特有のデータ型を定義することが伴います。 一方、データ型を定義するとは、そのデータ型の宣言と、それを具現化するための変数名を与えることに対応します。 たとえば、charという原子データ型で変数をchar c='豊';と定義すると、 Unicodeの2バイト文字領域が確保され、値として'豊'のUnicodeでの値が割当てられます。 データ型は、原子データ型だけでなく、Java言語が事前に用意したシステムクラスや、 ユーザが定義したクラス名とすることができます。 たとえば、Carクラスをユーザが定義したとき、

Car car = new Car();

のように具体的にcarという名前の変数をクラスCar型で定義するとします。 このcarのことをオブジェクトと呼び、 このようにして生成されたクラスのオブジェクトをインスタンスと呼びます。 このように、Java言語では、new演算子でオブジェクトを生成でき、生成されたものを、 そのクラスのインスタンスと呼びます。

なお、new演算子はシステムクラスやユーザクラス以外にも使うことがあります。 その典型例は、配列を作成する場合です。 容易に想像できると思いますが、new演算子で定義されることから、 配列もオブジェクトに分類されます。 int, float, charなどの原子データ型を単独でnewすることはできません。 つまり、int count;のような変数を宣言した場合、countのことをオブジェクトとは呼びません。 しかし、

int[ ] countArray = new int[1000];
のように配列を定義した場合、 countArrayはオブジェクトになります。

違いはわかりましたか?

配列の説明で、配列にはclone()という関数を適用できるといいました。 実は、Java言語では、clone()関数を適用できるものは、オブジェクトクラスを 暗黙に継承していると定義し、オブジェクトクラス(クラス名=Object)を継承しているものすべてが オブジェクトとなりえます。すなわち、new演算子や、初期化を通してオブジェクトとして具現化されます。 なお、上述のnew Car()の部分のCar()は、 無引数のコンストラクタの呼出しになります。 もし、Carクラスにコンストラクタがない場合、システムは暗黙で無引数のコンストラクタがあるかのようにふるまいます。 ただし、引数のあるコンストラクタが存在する場合は、 無引数のコンストラクタは明示的に定義しないといけないようになっています。

2次元平面での「点」を例にとって、 クラスの例を示します。
2次元平面の点を表すPointクラスのイラスト
まず、図のように「点」というオブジェクトを考えます。 この図より、「点」には、(x,y)という座標値と原点からの距離が定義できます。 そこで、座標値の2変数をクラスの変数として保持させ、原点からの距離dは、 関数で計算させるとします。以下が、そのクラスの実装例です。
Point.java
1 
2 
3 
4 
5 
6
7
8
9
10
11
12
13
14
/*
	プログラム2-9: Pointクラス(2次元の点(x,y)を表すクラス)
*/
public class Point {/* Pointクラスの定義:x,yというデータと2つの関数で定義される */

	private double x, y;/* 2次元の点 */

	public Point(double x, double y){/* コンストラクタ */
		this.x = x; this.y = y;/* this.がつく左辺はこのクラス自身のメンバ変数 */
	}
	public double distanceFromOrigin(){/* 原点からのユークリッド距離 */
		return Math.sqrt(x*x+y*y);/* Math.sqrt()はstatic変数なので直接アクセスできる */
	}
}
非常にシンプルなクラスの例ですが、メンバー変数xとy、 ならびにメンバー関数(メソッド)として、 コンストラクタ と別のdouble型を返すdistanceFromOrigin()という名前の関数が定義されています。 コンストラクタは、クラスと同じ名前で型のない関数のことです。 通常、コンストラクタでは、メンバー変数の初期化を行います。 6行目にあるメンバー変数には、privateというアクセス修飾子がついています。 JavaではC++と異なり、 すべてのメンバー変数やメンバー関数に直接アクセス修飾子をつけることができます。 C++言語では、"private:"のようなアクセス修飾子をラベル形式でクラス内に書くと、 そこから、他のアクセス修飾子が現れるまでアクセス修飾子を省略できました。 これに対して、Java言語では、ラベル形式でなく、変数や関数の頭に直接付随させます。 クラス内の変数や関数に、 何もアクセス修飾子がついていない場合は、 C++言語では、privateとして扱われますが、 Java言語では、 パッケージ(package)として扱われます。 パッケージに関しては、アクセス制御の点からみると、 publicやprotectedよりアクセス制限が厳しく、 privateよりは、少し緩いアクセス制限の単位、と思えば結構です。 パッケージに関しては後述します。

9行目には、thisキーワードが現れています。 C++言語にもthisポインタとして、 ほぼ類似の働きをするキーワードがあります。 thisの意味は、自分自身のオブジェクトを表します。 ここでは、new演算子でインスタンスが作られた場合のPointクラスのオブジェクト、 という意味になります。 メモリ的には、newで作られたオブジェクトはヒープと呼ばれるメモリ領域内に ユニークなアドレスを持つことになります。 ユニークなため、当然、ひとつひとつのオブジェクトのメモリ内のアドレスは異なります。 ですから、thisと呼ぶことで、まさに、「この」オブジェクト と特定され、同じPointクラスのオブジェクトであっても、別の機会にnew演算子で作られた Pointオブジェクトとはアドレスが異なります。 なので、Pointクラスのオブジェクトを複数生成した場合、どのPointオブジェクトですか? という質問ができることになります。 なお、Java言語では、C++言語と異なり、deleteやデストラクタ(消滅子)の概念がありません。 システム内のガーベッジコレクタが勝手に発動し、不要になったオブジェクトを消滅させてくれます。

なお、プログラム2-9には、main関数がありません。 このように、main関数が現れなくても、コンパイルすると、正しくclassファイル(Point.class)が生成されます。

C言語やC++言語とJava言語が大きく異なる点は、 Java言語では、ファイル名(.javaの拡張子を除く)とクラス名が一致している必要がある点です。 一方、少し複雑なプログラムを作成するとき、 2つ以上のファイル(クラス)に分割し、できたプログラムを足し合わせて、動作させることが多いですが、Java言語では、クラスごとにmain関数を定義できます。 C言語やC++言語でも、もちろん複数のファイルに分割してプログラムを書き、 最後に使用するファイルを複数個、リンク時にロードすることは頻繁にありますが、main関数が2つ以上あると正しくリンクされません。 Java言語は、インタープリタ起動時にmainが含まれるクラスを選択できるため、 ファイルごと(クラスごと)main関数があっても何も問題ないわけです。

一般的なJava言語でのプログラム(通常、ひとつのファイルがひとつのクラスに対応します) の構成は以下の図のようになります。メンバー変数の修飾子には、public, protected, privateあるいは、 何も修飾子のないメンバー変数メンバー関数(メンバー関数は、時によってはクラスメソッドと呼ぶこともあります)は、package内で有効な変数や関数と考えます。

典型的なJavaのプログラムの中身


図に注意書きしているように、Java言語でのmain関数は、public staticという修飾子がつきます。 特に、staticがついている点が重要で、main関数自体は、どこかのクラスに属するものでないため、 同じファイル内で定義されたクラスの変数や関数アクセスする場合も、new演算で、 オブジェクトを生成することが必要となります。 Pointクラスに似たPoint2クラスでmain関数を含むものを以下に示します。
Point2.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
/*
	プログラム2-10: Point2クラス(2次元の点(x,y)を表すクラス:配列バージョン)
*/
public class Point2 {/* Point2クラスの定義:vというデータと2つの関数で定義される */

	private double[] v = null;/* 2次元の点 */

	public Point2(){/* コンストラクタ */
		v = new double[2];/* ここで配列を初期化 */
	}
	public Point2(double x, double y){/* コンストラクタ(オーバーロード) */
		v = new double[2];/* pのデータを割り当て */
		v[0] = x; v[1] = y;
	}
	public double distanceFromOrigin(){/* 原点からのユークリッド距離 */
		return Math.sqrt(v[0]*v[0]+v[1]*v[1]);/* Math.sqrtはstatic関数なので直接アクセス可 */
	}
	public Point2 add(Point2 p1){/* 自分自身とp1との和を計算 */
		Point2 p2 = new Point2();/* デフォルトのコンストラクタ */
		p2.v[0] = this.v[0] + p1.v[0];/* X値を加算 */
		p2.v[1] = this.v[1] + p1.v[1];/* Y値を加算 */
		return(p2);
	}
	public double[] getPoint2(){/* 点を返す */
		return v;
	}
	public void print(String something){/* Point2クラスのメンバー変数をプリントする関数 */
		if (something != null && something != "") System.out.println(something);
		System.out.printf("Point2の座標 =(%f, %f)\n",v[0],v[1]);
	}
	public static void main(String[] args){
		Point2 a = new Point2(3.0, -1.0);/* 座標値(3,-1)の点オブジェクト生成 */
		a.print("----a----");
		Point2 b = new Point2(-2.0, 5.0);/* 座標値(-2,5)の点オブジェクト生成 */
		b.print("----b----");
		Point2 c = a.add(b);/* 2つの座標値を足す */
		c.print("----c----");
	}
}
これを実行した結果例を以下に示します。

$ javacu Point2.java

$ javau Point2
----a----
Point2の座標 =(3.000000, -1.000000)
----b----
Point2の座標 =(-2.000000, 5.000000)
----c----
Point2の座標 =(1.000000, 4.000000)
Point2.javaのコンパイルと実行例


main関数の32行目と34行目で、Point2オブジェクトを作成しています。 また、36行目では、Point2オブジェクトを返す関数を呼び出していますので、 結果として、ここでもPoint2オブジェクトを作成しています。 そのメンバー関数にアクセスする場合、 33、35-37行名にあるように、ドット演算子でアクセスします。

Javaでの(標準)乱数の発生方法

プログラム2-10で示したPoint2クラスの利用側では、 main関数に直接(x,y)座標を指定することでオブジェクトを生成しました。 多数の座標値をランダムに生成することは、 数値積分をはじめ、多くのシミュレーションで必要となります。 そこで、以下では、Point2の座標値をランダムに生成する方法を紹介します。 Javaでの標準的な乱数発生方法では、 java.utilパッケージに含まれる Randomクラスを用います。 Randomクラスのコンストラクタでは、 乱数の種(seed)を与えるコンストラクタがあります。 ちょうど、C言語で、srand(time(NULL));のように乱数の初期値を現在時刻で行うことで、 実行するたびに異なる乱数を発生することができます。 時刻を発生するには、 Javaでは、java.utilパッケージの Dateクラス のオブジェクトを作成し、 getTime()メソッドを用います。 以下に乱数を利用したプログラム例を示す。
RandomPoint2.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
/*
	プログラム2-11: RandomPoint2クラス(2次元の点(x,y)をランダムに複数発生)
*/
import java.util.Date;/* 日付クラス */
import java.util.Random;/* 乱数クラス */

public class RandomPoint2 {/* Point2クラスの定義:vというデータと2つの関数で定義される */

     Random rand;/* Randomクラスの変数 */
     int numPoints;/* 点の数 */
     Point2[] pArray;/* Point2クラスのオブジェクト保持配列 */
     double min=0.0, max=1.0;/* 最小値minから最大値maxまでの範囲の数をランダムに生成*/

     public double getRand(){/* 個々の実数値で[min,max]の間で乱数発生 */
          double x = min + (max - min) * rand.nextDouble();
          return(x);
     }
     public void generate(){/* Point2クラスレベルで乱数発生 */
          for (int i =  0 ; i < numPoints ; i++){
               pArray[i] = new Point2(getRand(), getRand());
               pArray[i].print("");
          }
     }
     public void init(int numPoints){/* 初期化: 1引数 */
          Date date = new Date();/* Dateクラスのオブジェクト生成 */
          /* 現在時刻で初期化する */
          rand = new Random(date.getTime());/* Randomクラスのオブジェクト生成 */
          this.numPoints = numPoints;
          pArray = new Point2[numPoints];
     }
     public void init(int numPoints, double min, double max){/* 初期化: 3引数 */
          init(numPoints);
          this.min = min;/* 乱数の下限値 */
          this.max = max;/* 乱数の上限値 */
     }
     public static void main(String[] args){
          RandomPoint2 rp2 = new RandomPoint2();/* オブジェクト生成 */
          int numPoints = Integer.valueOf(args[0]).intValue();/* 何個Point2を発生するか*/
          if (args.length > 2){/* コマンドラインからの引数が3個以上の場合 */
               double min = Double.valueOf(args[1]).doubleValue();/* 最小値設定 */
               double max = Double.valueOf(args[2]).doubleValue();/* 最大値設定 */
               rp2.init(numPoints, min, max);/* 3変数の場合の初期化を呼び出す */
          }
          else
               rp2.init(numPoints);/* 1変数の場合の初期化を呼び出す */
          rp2.generate();/* Point2オブジェクトを発生 */
     }
}
なお、上のプログラムは、Point2クラスを利用しますので、 Point2.classがあるディレクトリ(フォルダ)と同じ位置で実行する必要があります。 これを実行した結果例を以下に示します。

$ javacu RandomPoint2.java

$ javau RandomPoint2 10
Point2の座標 =(0.207823, 0.456113)
Point2の座標 =(0.475661, 0.011135)
Point2の座標 =(0.799188, 0.321104)
Point2の座標 =(0.731046, 0.195282)
Point2の座標 =(0.031333, 0.615626)
Point2の座標 =(0.153132, 0.045875)
Point2の座標 =(0.137484, 0.373655)
Point2の座標 =(0.700307, 0.364687)
Point2の座標 =(0.802826, 0.132508)
Point2の座標 =(0.802322, 0.134089)

$ javau RandomPoint2 10 -1.0 1.0
Point2の座標 =(0.562631, 0.569394)
Point2の座標 =(0.942673, 0.340377)
Point2の座標 =(-0.223196, -0.430438)
Point2の座標 =(-0.731628, -0.460117)
Point2の座標 =(-0.657560, -0.347448)
Point2の座標 =(-0.986962, 0.009891)
Point2の座標 =(-0.921466, 0.394257)
Point2の座標 =(0.874867, -0.623581)
Point2の座標 =(-0.573001, -0.374083)
Point2の座標 =(-0.665027, 0.862140)

RandomPoint2.javaのコンパイルと実行例(乱数の総数、ならびに最小値と最大値を与える3引数と1引数の場合)


ところで、
Java言語には、CやC++言語と違い、 #defineマクロや#include等のプリプロセッサ命令はありません。 このうち、#defineマクロで定数を定義する部分を、 final staticという修飾子をつけた変数として代用することがあります。 以下で、 SwingによるGUI(グラフィックス)で乱数をポップアップ・ウィンドウに表示させるプログラムを紹介します。 このプログラムに、上述のfinal staticという修飾子をつけて、 変数の値を最初から代入して与える方式が含まれます。 16-17行目にその例があります。 これをクラス内では、そのまま変数名でアクセスし、mainのようなクラス外にある関数からは、 DrawRandomPoint2.FRAME_SIZEのようにクラス名のあとにドットをつけて直接アクセスします。 意味的には、Javaプログラム全体からひとつだけのインスタンスがあり、それを共有する形態をとります。 #defineマクロと違い、本当に変数(実際はfinalがつくので値を変更できない変数)として定義します。
DrawRandomPoint2.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
/*
	プログラム2-12: DrawRandomPoint2クラス(2次元の点(x,y)をランダムに複数発生)
*/
import java.util.Date;/* 日付クラス */
import java.util.Random;/* 乱数クラス */
import javax.swing.*;/* Swingパッケージ */
import java.awt.*;/* AWTパッケージ */

public class DrawRandomPoint2 extends JPanel {/* JPanelを継承 */
    /* Point2クラスの定義:vというデータと2つの関数で定義される */

    Random rand;/* Randomクラスのオブジェクトを保持 */
    int numPoints;/* 乱数の総数 */
    Point2[] pArray;/* Point2クラスのオブジェクトを配列に保持 */
    double min=0.0, max=1.0;/* 最小値minから最大値maxまでの範囲の数をランダムに生成*/
    final static int POINT_RADIUS = 10;/* paintで使う点を近似する円の半径 */
    final static int FRAME_SIZE = 500; /* Swingのウィンドウのサイズ */

    public double getRand(){/* 乱数発生の本体 */
        double x = min + (max - min) * rand.nextDouble();/* [min,max]の間の乱数値 */
        return(x);/* 返す */
    }
    public void generate(){/* 乱数で点を生成 */
        for (int i =  0 ; i < numPoints ; i++){
            pArray[i] = new Point2(getRand(), getRand());/* 個々の配列にあるPoint2オブジェクトを生成 */
            pArray[i].print("");
        }
    }
    public void init(int numPoints){
        Date date = new Date();/* Dateクラスのオブジェクト生成 */
        /* 現在時刻で初期化する */
        rand = new Random(date.getTime());/* Randomクラスのオブジェクト生成 */
        this.numPoints = numPoints;/* 乱数の発生総数 */
        pArray = new Point2[numPoints];/* Point2のオブジェクトをnumPoints個、配列で生成 */
    }
    public void init(int numPoints, double min, double max){
        init(numPoints);
        this.min = min;/* 最小値設定 */
        this.max = max;/* 最大値設定 */
    }
    public void paintComponent( Graphics g ){/* Swingウィンドウで描画 */
        super.paintComponent( g );/* JPanelクラスのpaintComponentメソッドの呼び出し */
        g.setColor(Color.RED);/* 赤色 */
        for (int i = 0 ; i < numPoints; i++){
            double [] x = pArray[i].getPoint2();
            g.fillOval((int)x[0], (int)x[1], POINT_RADIUS, POINT_RADIUS);/* 点を円で塗りつぶし */
        }
    }
    public static void main(String[] args){
        DrawRandomPoint2 panel = new DrawRandomPoint2();
        JFrame application = new JFrame();/* JFrameクラスでWindowを準備 */
        int numPoints = Integer.valueOf(args[0]).intValue();
        if (args.length > 2){
            double min = Double.valueOf(args[1]).doubleValue();/* 最小値設定 */
            double max = Double.valueOf(args[2]).doubleValue();/* 最大値設定 */
            panel.init(numPoints, min, max);/* 初期化 */
        }
        else
            panel.init(numPoints);/* 初期化 */
        panel.generate();/* パネル内の点を発生 */
        application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE);/* Windowを閉じると終了 */
        application.add( panel );/* JFrameにJPanelを加える */
        int size = DrawRandomPoint2.FRAME_SIZE;/* static変数はオブジェクトに直接アクセスできる */
        application.setSize( size, size );/* JFrameのサイズ */
        application.setVisible( true );/* Windowを表示 */
    }
}
実行すると端末へのランダムなPoint2のデータ以外に以下のようなWindowが表示されます。 標準的な乱数がどのように分布するかを視覚的に捉えているといいかと思います。

$ javacu DrawRandomPoint2.java

$ javau DrawRandomPoint2 10 0 400
Point2の座標 =(250.137143, 390.610376)
Point2の座標 =(110.911274, 177.586020)
Point2の座標 =(160.613944, 192.946361)
Point2の座標 =(122.815260, 382.472803)
Point2の座標 =(266.095300, 25.273511)
Point2の座標 =(137.410527, 126.421468)
Point2の座標 =(307.520539, 287.830558)
Point2の座標 =(257.974293, 372.602440)
Point2の座標 =(305.452828, 262.014361)
Point2の座標 =(51.564488, 280.857434)
DrawRandomPoint2.javaのコンパイルと実行
Swing GUI上の表示例


パッケージ (package)

Java言語には、C言語やC++言語にはない、パッケージという考え方があります。 パッケージを構成するのは、ごく簡単で、ファイルの先頭に、

package myPackage;


と一行挿入するだけです。 ここで、myPackageはパッケージの名前を表しますが、通常の変数と同様、決められた規則の中で任意の名前でOKです。 パッケージを宣言する意味は、一群の関連するプログラムをクラス単位で別々に定義する場合などに有効です。 本コースでは、採点基準でも述べた通り、課題では原則、 パッケージを使用しません。 では、パッケージを使用しないJavaプログラムでのパッケージとは概念的に何を表すのでしょうか? 一言でいうと、パッケージとは、フォルダ(ディレクトリ)のことです。 すなわち、同一のフォルダで作成したクラス(Javaプログラム)が複数あるとき、それらは同じパッケージである、と考えます。 パッケージは、さらに、

package myPackage.util;


のように、階層化することもできます。
この場合、myPackageという名前のパッケージ内に さらにutilというフォルダがあり、その中で定義されるクラスを定義する場合に使用します。 前述したように、パッケージはフォルダであると考えられますので、階層化されたパッケージとは フォルダの中に別のフォルダを作成することに対応します。 上述のプログラム2-10と2-12の先頭に、

package Point2;


という一行をそれぞれ挿入したとします。 その場合、まず、現在のフォルダにPoint2という名前のフォルダを生成します。 次に、そのフォルダ内にPoint2.javaとDrawRandomPoint2.javaの2つのファイルを移動します。 そして、以下のようにコンパイルと実行を行います。

$ javacu Point2/DrawRandomPoint2.java

$ javau Point2.DrawRandomPoint2 10 0 400
Point2の座標 =(160.618605, 119.090006)
Point2の座標 =(58.373998, 78.057096)
Point2の座標 =(8.139293, 308.731751)
Point2の座標 =(108.961011, 197.328352)
Point2の座標 =(192.323108, 365.398348)
Point2の座標 =(45.998854, 131.234043)
Point2の座標 =(144.877114, 195.466436)
Point2の座標 =(96.701460, 306.907535)
Point2の座標 =(76.519071, 377.481631)
Point2の座標 =(120.964576, 394.706151)
パッケージ化したプログラムのコンパイルと実行例(フォルダを階層化するのがポイント)


この図からわかるように、コンパイルでは、フォルダ名のあとにスラッシュ(/)をつけ、 そのあとに、ファイル名をつけます。実行する場合は、スラッシュでも通常は問題ないですが、 パッケージ名のあとにピリオドをつけて代用できます。ピリオドはコンパイル時には使えないことに注意してください。

アクセス制御

Java言語でのアクセス制御は、大まかに以下のようにまとめられます。

アクセス指定子 public protected default(デフォルト無指定時) private
同一クラス内
同一パッケージ内 ×
他パッケージの派生(サブ)クラス × ×
他パッケージの非派生クラス × × ×


クラスの継承やprotectedに関しては次回以降に説明予定です。