これまで、色々なJavaプログラムを作成し、実行してきました。 実は、コンピュータはJavaプログラムを理解しません。 コンピュータが直接Javaプログラムを実行しているわけではないのです。 ここでは、Javaプログラムが実行される仕組みについて説明します。
まず、コンピュータが直接理解できるのは、機械語だけです。 機械語 ( machine language ) とは、コンピュータを直接制御するプログラミング言語です。 Javaプログラムが構造を持った英単語などの列なのに対して、機械語のプログラムは構造のないビットの列です。 また、同じ制御を行う場合でも、コンピュータ・システムが異なりますと、異なるビット列になります。 機械語によるプログラミングは、システムごとに異なるビット列を、構造不明のまま書く、困難な作業なのです。
プログラミングをもう少し楽にするために、高級言語が考え出されました。 高級言語 ( high level language ) とは、コンピュータにやってほしいことを、人間にとって分かりやすく書けるように作られたプログラミング言語です。 高級言語はプログラムの構造を反映する文法を持ち、英単語など覚えやすい構成要素から成り立ちます。 Javaは高級言語の一つです。 Javaの他にも、CやBASICなど数多くの高級言語が使われています。 もちろん、高級言語で書いたプログラムをコンピュータに与えても、機械語でないので実行できません。
実行するために、コンパイラやインタプリタを利用します。 コンパイラ ( compiler ) とは、高級言語で書かれたプログラムを機械語のプログラムに変換するソフトウェアです。 高級言語の中では、例えばC言語が通常コンパイラを使います。 C言語でプログラムを書き、Cコンパイラによってそれを機械語に変換したあとで、コンピュータにその機械語プログラムを実行させるのです。 コンピュータ・システムが異なれば機械語も異なりますので、システムごとにCコンパイラを用意する必要がありますが、Cプログラム自身はシステムの差をあまり意識せずに書けるのです。 なお、変換前のプログラムは ソースコード ( source code ) 、または 原始プログラム ( source program ) とよばれます。 また、変換後のプログラムは オブジェクトコード ( object code ) 、または 目的プログラム ( target program ) とよばれます。
一方、 インタプリタ ( interpreter ) とは、高級言語で書かれたプログラムを少し読んでは、それに対応する機械語プログラムを呼び出す、ということを繰り返すソフトウェアです。 高級言語の中では、例えばBASICが通常インタプリタを使います。 BASICでプログラムを書き、BASICインタプリタを実行しますと、プログラムを少し読んでは機械語プログラムを呼び出す、ということを繰り返します。 コンパイラの場合と同様に、システムごとにBASICインタプリタを用意する必要がありますが、BASICプログラム自身はシステムの差をあまり意識しなくてもよいのです。
さて、Javaプログラムは、コンパイラ・インタプリタ方式で実行されます。 コンパイラ・インタプリタ方式とは、さまざまなコンピュータ・システムの共通語のような機械語を定め、コンパイラによって原始プログラムを共通機械語のプログラムに変換し、その後で共通機械語インタプリタを実行するという方式です。 共通機械語は、実在する必要のないコンピュータ・システムの機械語と考えられます。 そこで、仮想マシン(または仮想計算機)という概念を導入し、共通機械語は仮想マシンの機械語であると位置付けることにします。 Javaのための共通機械語は、 Javaバイトコード ( Java Bytecode ) です。 Javaのための仮想マシンは、 Java仮想マシン ( Java virtual machine ) とよばれます。
ここで、Javaアプリケーションを実行するときに、javacコマンドとjavaコマンドを用いたことを思い出してください。 javacコマンドは、Javaプログラム(〜.java)をバイトコード(〜.class)に変換するコンパイラです。 そして、javaコマンドはバイトコード・インタプリタなのです。 一方、Javaアプレットを実行するときは、javacコマンドでバイトコードを生成し、それをアプレット・ビューアやウェブ・ブラウザに読み込ませました。 アプレット・ビューアやウェブ・ブラウザには、バイトコード・インタプリタの機能が備わっていて、それが働いているのです。 アプレット・ビューアやウェブ・ブラウザの中には仮想マシンが内蔵されていると言うこともできます。
Java仮想マシンは、細かいところまで厳密に定義されています。 したがって、Javaプログラムはシステムの差を意識せずに書けるのです。
この授業では、アプレットを使ってお絵描きをしました。 本当は、アプレットはもっと色々なことができます。 楽しいアプレットが埋め込まれたホームページは非常に魅力的です。
ここで、アプレットもれっきとしたプログラムであること、そしてそのホームページを訪れると、アプレットが転送され、あなたのコンピュータの上で動くことを思い出してください。 悪意のある人の作ったアプレットが、コンピュータ・ウィルスのように、あなたのコンピュータのファイルを消したり、個人情報を盗んだりしないのでしょうか。
この心配への答えは、「アプレットにできることは制限されていて、悪事を働くのは困難である。」です。 以下では、個人情報(具体的にはユーザ名)がアプレットで取り出せないことを確かめます。
はじめに、Javaアプリケーションで、Javaシステムに関する情報(システム・プロパティとよばれます)を取り出します。 次のプログラムは、Javaシステムが動いているOSの名前を表示します。
/* 1*/ class PropertyTest1 { /* 2*/ public static void main (String[] args) { /* 3*/ String s = System.getProperty("os.name"); /* 4*/ System.out.println(s); /* 5*/ } /* 6*/ }
b04a001@AsiaA1:~/comp2b% java PropertyTest1 Mac OS X b04a001@AsiaA1:~/comp2b%
3行目の
System
クラスのクラスメソッド
getProperty
がシステム・プロパティを取り出します。
引数はシステム・プロパティの名前(文字列)です。
OSの名前はos.nameで、ユーザ名はuser.nameです。
(システム・プロパティは他にもあります。)
したがって、3行目の
"os.name"
を
"user.name"
に置き換えますと、ユーザ名(この場合はb04a001)が表示されます。
次に、同じことをアプレットで行ないます。
7行目の
Graphics
クラスのインスタンスメソッド
drawString
はアプレットで文字列を描画するものです。
この第2引数と第3引数は、文字列イメージの左下の座標です。
/* 1*/ import java.applet.*; /* 2*/ import java.awt.*; /* 3*/ /* 4*/ public class PropertyTest2 extends Applet { /* 5*/ public void paint (Graphics g) { /* 6*/ String s = System.getProperty("os.name"); /* 7*/ g.drawString(s, 0, 20); /* 8*/ } /* 9*/ }
<applet code="PropertyTest2.class" width="200" height="100"> </applet>
b04a001@AsiaA1:~/comp2b% appletviewer PropertyTest2.html & b04a001@AsiaA1:~/comp2b%
アプレットビューア |
---|
Mac OS X |
アプレットが開始されました。 |
さて、
"os.name"
を
"user.name"
に置き換えて、Javaアプリケーションのときのようにユーザ名が取り出せるでしょうか。
試してみますと、アプレット・ビューアは起動しますが、ユーザ名は描画されません。
その代わり、「ターミナル」にエラーメッセージが出力されます。
セキュリティ上の問題があるのでアプレットを停止するということです。
b04a001@AsiaA1:~/comp2b% appletviewer PropertyTest2.html & java.security.AccessControlException: access denied (java.util.PropertyPermission user.name read) (中略) b04a001@AsiaA1:~/comp2b%
注意: 設定によっては、アプレット・ビューアでユーザ名が描画されてしまいます。 この場合は、ウェブ・ブラウザでアプレットを表示してください。
Javaは高い信頼性を持つ言語です。 この信頼性を支える仕組みの一つに、 ガベージ・コレクション ( garbage collection ) があります。
コンピュータのメモリ領域は有限ですので、大規模なデータ処理を行なう際には、不要なデータを破棄し、メモリを開放する必要が生じます。 多くのプログラミング言語では、この作業はプログラマの責任で行ないます。 しかし、不要なデータをいつまでも残してメモリ不足を引き起こしたり、誤った開放によって必要なデータを失ったりというバグを導きやすいのも事実です。
Javaは、この面倒な作業を自動的に行ないます。 これがガベージ・コレクションです。 ガベージ・コレクションでは、誰からも参照されないデータを不要なものと見なし、機械的にそれを検知してメモリを開放します。 機械的な部分で多少時間がかかりますが、バグの要因が減った分、信頼性の高いプログラムが書けるのです。
ガベージ・コレクションの働く例を以下に示します。 ここで ヒープ ( heap ) とは、プログラムの実行中に確保されるメモリ領域のことです。
次のプログラムは、単にメモリを消費するものです。
Java言語では、
int
型のデータは4バイトと決まっていますので、2次元配列(配列の配列)
a
の消費するメモリは8×65536×4バイト、すなわち2MBです。
/* 1*/ class GCTest1 { /* 2*/ public static void main (String[] args) { /* 3*/ int i; /* 4*/ int[][] a = new int[8][]; /* 5*/ for (i = 0; i < 8; i++) { /* 6*/ a[i] = new int[65536]; /* 7*/ System.out.println(i); /* 8*/ } /* 9*/ } /* 10*/ }
Javaインタプリタは、デフォルトでは十分な(16MB)最大ヒープ領域を持ちますので、このプログラムは問題なく実行できます。
b04a001@AsiaA1:~/comp2b% java GCTest1 0 1 2 3 4 5 6 7 b04a001@AsiaA1:~/comp2b%
そこで、ヒープ領域を1MBに制限してみます。
オプションの
ms
は初期ヒープ領域、
mx
は最大ヒープ領域を表します。
1MBのヒープ領域に2MBのデータは収まりませんので、プログラムは途中でエラーを起こして停止します。
b04a001@AsiaA1:~/comp2b% java -ms1m -mx1m GCTest1 0 1 2 3 4 Exception in thread "main" java.lang.OutOfMemoryError b04a001@AsiaA1:~/comp2b%
次に、以下のようなプログラムを考えます。
これは上記のプログラムに似ています。
違いは、あらかじめ5行目で配列
b
を用意しておき、それを8行目で
a
[
i
]
に格納しなおすところです。
/* 1*/ class GCTest2 { /* 2*/ public static void main (String[] args) { /* 3*/ int i; /* 4*/ int[][] a = new int[8][]; /* 5*/ int[] b = new int[65536]; /* 6*/ for (i = 0; i < 8; i++) { /* 7*/ a[i] = new int[65536]; /* 8*/ a[i] = b; /* 9*/ System.out.println(i); /* 10*/ } /* 11*/ } /* 12*/ }
配列 b がありますので、プログラムGCTest2はGCTest1よりメモリを消費するように思えます。 しかし、GCTest2は1MBのヒープ領域で実行できるのです。
b04a001@AsiaA1:~/comp2b% java -ms1m -mx1m GCTest2 0 1 2 3 4 5 6 7 b04a001@AsiaA1:~/comp2b%
この現象を理解するには、GCTest2の7行目と8行目に注目する必要があります。
7行目では、確かに65536要素の配列を生成し、それを
a
[
i
]
に格納しています。
しかし、8行目で
a
[
i
]
に配列
b
を格納しなおしますので、7行目で生成した配列は誰も参照できなくなります。
誰も参照できないデータは記憶しておく必要がありませんので、Javaインタプリタはその領域を自動的に検知し、他のデータの記憶のために再利用するのです。
今後の予定は以下の通りです。
この授業の成績は、レポートの提出と試験の得点で決まります。
成績に関して次のような事情のある人はメールで連絡してください。 できる限り対処します。
問1. 次のプログラムは、入力された2つの整数の平均と合計を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class AverageTotal { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ InputStreamReader isr = new InputStreamReader(System.in); /* 6*/ BufferedReader br = new BufferedReader(isr); /* 7*/ int x, y, average = 0, total = 0; /* 8*/ System.out.print("Enter a number: "); /* 9*/ x = Integer.parseInt(br.readLine()); /* 10*/ System.out.print("Enter a number: "); /* 11*/ y = Integer.parseInt(br.readLine()); /* 12*/ average = total / 2; /* 13*/ total = x + y; /* 14*/ System.out.println(average); /* 15*/ System.out.println(total); /* 16*/ } /* 17*/ }
b04a001@AsiaA1:~/comp2b% java AverageTotal Enter a number: 90 Enter a number: 80 0 // 間違い 170 // 正しい b04a001@AsiaA1:~/comp2b%
問2. 次のプログラムは、入力された2つの整数を、小さい順に出力する(つもりの)ものです。 このプログラムの不具合を修正してください。
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class SmallerLarger { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ InputStreamReader isr = new InputStreamReader(System.in); /* 6*/ BufferedReader br = new BufferedReader(isr); /* 7*/ int x, y, smaller, larger; /* 8*/ System.out.print("Enter a number: "); /* 9*/ x = Integer.parseInt(br.readLine()); /* 10*/ System.out.print("Enter a number: "); /* 11*/ y = Integer.parseInt(br.readLine()); /* 12*/ if (x < y) { /* 13*/ smaller = x; /* 14*/ larger = y; /* 15*/ } /* 16*/ smaller = y; /* 17*/ larger = x; /* 18*/ System.out.println(smaller); /* 19*/ System.out.println(larger); /* 20*/ } /* 21*/ }
b04a001@AsiaA1:~/comp2b% java SmallerLarger Enter a number: 20 Enter a number: 10 10 // 正しい 20 // 正しい b04a001@AsiaA1:~/comp2b% java SmallerLarger Enter a number: 10 Enter a number: 20 20 // 間違い 10 // 間違い b04a001@AsiaA1:~/comp2b%
問3. 次のプログラムは、入力された3つの整数がすべて等しいかどうかを出力する(つもりの)ものです。 このプログラムの不具合を修正してください。
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class EqualNumbers { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ InputStreamReader isr = new InputStreamReader(System.in); /* 6*/ BufferedReader br = new BufferedReader(isr); /* 7*/ int x, y, z; /* 8*/ System.out.print("Enter a number: "); /* 9*/ x = Integer.parseInt(br.readLine()); /* 10*/ System.out.print("Enter a number: "); /* 11*/ y = Integer.parseInt(br.readLine()); /* 12*/ System.out.print("Enter a number: "); /* 13*/ z = Integer.parseInt(br.readLine()); /* 14*/ if (x == y || y == z) { /* 15*/ System.out.println("These are equal numbers."); /* 16*/ } else { /* 17*/ System.out.println("These are not equal numbers."); /* 18*/ } /* 19*/ } /* 20*/ }
b04a001@AsiaA1:~/comp2b% java EqualNumbers Enter a number: 10 Enter a number: 10 Enter a number: 10 These are equal numbers. // 正しい b04a001@AsiaA1:~/comp2b% java EqualNumbers Enter a number: 10 Enter a number: 20 Enter a number: 10 These are not equal numbers. // 正しい b04a001@AsiaA1:~/comp2b% java EqualNumbers Enter a number: 10 Enter a number: 10 Enter a number: 20 These are equal numbers. // 間違い b04a001@AsiaA1:~/comp2b%
問4. 次のプログラムは、入力された2つの2以上の整数 x と y に対して、 x が y で何回割り切るれるかを出力する(つもりの)ものです。 このプログラムの不具合を修正してください。
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class DivideDivide { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ InputStreamReader isr = new InputStreamReader(System.in); /* 6*/ BufferedReader br = new BufferedReader(isr); /* 7*/ int x, y, remainder = 0, count = 0; /* 8*/ System.out.print("Enter a number: "); /* 9*/ x = Integer.parseInt(br.readLine()); /* 10*/ System.out.print("Enter a number: "); /* 11*/ y = Integer.parseInt(br.readLine()); /* 12*/ while (remainder == 0) { /* 13*/ count++; /* 14*/ x = x / y; /* 15*/ remainder = x % y; /* 16*/ } /* 17*/ System.out.println(count); /* 18*/ } /* 19*/ }
b04a001@AsiaA1:~/comp2b% java DivideDivide Enter a number: 12 Enter a number: 2 2 // 正しい b04a001@AsiaA1:~/comp2b% java DivideDivide Enter a number: 12 Enter a number: 5 1 // 間違い b04a001@AsiaA1:~/comp2b%
問5. 次のプログラムは、{30, 50, 10, 40, 20} で表される配列の、要素の最小値を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。
/* 1*/ class ArrayMin { /* 2*/ public static void main (String[] args) { /* 3*/ int i, min = 0; /* 4*/ int[] a = {30, 50, 10, 40, 20}; /* 5*/ for (i = 0; i < a.length; i++) { /* 6*/ if (a[i] < min) { /* 7*/ min = a[i]; /* 8*/ } /* 9*/ } /* 10*/ System.out.println(min); /* 11*/ } /* 12*/ }
b04a001@AsiaA1:~/comp2b% java ArrayMin 0 // 間違い b04a001@AsiaA1:~/comp2b%
今日の演習12の答案(Javaプログラム)をメールで提出してください。 差出人は学内のメール・アドレス(b04a001@twcu.ac.jpなど)とし、宛先はkonishi@twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(7月13日)を明記してください。