目次 | 索引 |
---|---|
これまで、色々な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コマンドでバイトコードを生成し、それをアプレット・ビューアやWWWブラウザに読み込ませました。 アプレット・ビューアやWWWブラウザには、バイトコード・インタプリタの機能が備わっていて、それが働いているのです。 アプレット・ビューアやWWWブラウザの中には仮想マシンがあると言うこともできます。
この授業では、アプレットを使ってお絵描きをしました。 本当は、アプレットはもっと色々なことができます。 楽しいアプレットが埋め込まれたホームページは非常に魅力的です。
ここで、アプレットもれっきとしたプログラムであること、そしてそのホームページを訪れると、アプレットが転送され、あなたのコンピュータの上で動くことを思い出してください。 悪意のある人の作ったアプレットが、あなたのコンピュータのファイルを消したり、個人情報を盗んだりしないのでしょうか。
この心配への答えは、「アプレットにできることは制限されていて、悪事を働くのは困難である。」です。 以下では、個人情報(具体的にはユーザ名)がアプレットで取り出せないことを確かめます。
はじめに、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:~/java% java PropertyTest1 Mac OS X b04a001@AsiaA1:~/java%
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:~/java% appletviewer PropertyTest2.html & b04a001@AsiaA1:~/java%
アプレットビューア |
---|
Mac OS X |
アプレットが開始されました。 |
さて、
"os.name"
を
"user.name"
に置き換えて、Javaアプリケーションのときのようにユーザ名が取り出せるでしょうか。
試してみますと、アプレット・ビューアは起動しますが、ユーザ名は描画されません。
その代わり、「ターミナル」にエラーメッセージが出力されます。
セキュリティ上の問題があるのでアプレットを停止するということです。
b04a001@AsiaA1:~/java% appletviewer PropertyTest2.html & java.security.AccessControlException: access denied (java.util.PropertyPermission user.name read) (中略) b04a001@AsiaA1:~/java%
注意: 設定によっては、アプレット・ビューアでユーザ名が描画されてしまいます。 この場合は、WWWブラウザでアプレットを表示してください。
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:~/java% java GCTest1 0 1 2 3 4 5 6 7 b04a001@AsiaA1:~/java%
そこで、ヒープ領域を1MBに制限してみます。
オプションの
ms
は初期ヒープ領域、
mx
は最大ヒープ領域を表します。
1MBのヒープ領域に2MBのデータは収まりませんので、プログラムは途中でエラーを起こして停止します。
b04a001@AsiaA1:~/java% java -ms1m -mx1m GCTest1 0 1 2 3 4 Exception in thread "main" java.lang.OutOfMemoryError b04a001@AsiaA1:~/java%
次に、以下のようなプログラムを考えます。
これは上記のプログラムに似ています。
違いは、あらかじめ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:~/java% java -ms1m -mx1m GCTest2 0 1 2 3 4 5 6 7 b04a001@AsiaA1:~/java%
この現象を理解するには、GCTest2の7行目と8行目に注目する必要があります。
7行目では、確かに65536要素の配列を生成し、それを
a[i]
に格納しています。
しかし、8行目で
a[i]
に配列
b
を格納しなおしますので、7行目で生成した配列は誰も参照できなくなります。
誰も参照できないデータは記憶しておく必要がありませんので、Javaインタプリタはその領域を自動的に検知し、他のデータの記憶のために再利用するのです。
次回(7/16)の授業では試験を行います。 今日の演習問題をよく見直しておいてください。
レポートの提出に関するスケジュールは次の通りです。
この授業の成績は、レポートの提出と試験の得点で決まります。
成績に関して次のような事情のある人はメールで連絡してください。 できる限り対処します。
問1. 次のプログラムは、入力された3つの整数を年、月、日と見なし、例えば 2004 , 7 , 9 ならば20040709を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class SimpleDate { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ InputStreamReader isr = new InputStreamReader(System.in); /* 6*/ BufferedReader br = new BufferedReader(isr); /* 7*/ int year, month, day, date; /* 8*/ System.out.print("Enter a year: "); /* 9*/ year = Integer.parseInt(br.readLine()); /* 10*/ System.out.print("Enter a month: "); /* 11*/ month = Integer.parseInt(br.readLine()); /* 12*/ System.out.print("Enter a day: "); /* 13*/ day = Integer.parseInt(br.readLine()); /* 14*/ date = year * 100 + day; /* 15*/ date = date * 100 + month; /* 16*/ System.out.println(date); /* 17*/ } /* 18*/ }
b04a001@AsiaA1:~/java% java SimpleDate Enter a year: 2004 Enter a month: 7 Enter a day: 7 20040707 // 正しい b04a001@AsiaA1:~/java% java SimpleDate Enter a number: 2004 Enter a number: 7 Enter a number: 9 20040907 // 間違い b04a001@AsiaA1:~/java%
問2. 次のプログラムは、入力された整数が 100 未満ならば 2000 あるいは 1900 増加させて出力する(つもりの)ものです。 増加させるのは、入力が 70 未満ならば 2000, そうでなければ 1900 とします。 このプログラムの不具合を修正してください。
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class Year2000 { /* 4*/ public static void main (String[] args) throws IOException { /* 5*/ InputStreamReader isr = new InputStreamReader(System.in); /* 6*/ BufferedReader br = new BufferedReader(isr); /* 7*/ int year; /* 8*/ System.out.print("Enter a year: "); /* 9*/ year = Integer.parseInt(br.readLine()); /* 10*/ if (year < 100) { /* 11*/ if (year < 70) { /* 12*/ year = year + 2000; /* 13*/ } /* 14*/ } else { /* 15*/ year = year + 1900; /* 16*/ } /* 17*/ System.out.println(year); /* 18*/ } /* 19*/ }
b04a001@AsiaA1:~/java% java Year2000 Enter a year: 10 2010 // 正しい b04a001@AsiaA1:~/java% java Year2000 Enter a year: 90 90 // 間違い b04a001@AsiaA1:~/java%
問3. 次のプログラムは、入力された3つの整数が、ポーカーで言うワン・ペアになるならば"One pair."と出力し、そうでないならば"Not one pair."と出力する(つもりの)ものです。 ワン・ペアとは、二つの数が等しく、他は等しくないことです。 このプログラムの不具合を修正してください。
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class OnePair { /* 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("One pair."); /* 16*/ } else if (x != y && y == z) { /* 17*/ System.out.println("One pair."); /* 18*/ } else { /* 19*/ System.out.println("Not one pair."); /* 20*/ } /* 21*/ } /* 22*/ }
b04a001@AsiaA1:~/java% java OnePair Enter a number: 10 Enter a number: 10 Enter a number: 20 One pair. // 正しい b04a001@AsiaA1:~/java% java OnePair Enter a number: 10 Enter a number: 10 Enter a number: 10 Not one pair. // 正しい b04a001@AsiaA1:~/java% java OnePair Enter a number: 10 Enter a number: 20 Enter a number: 10 Not one pair. // 間違い b04a001@AsiaA1:~/java%
問4. 次のプログラムは、入力された負でない整数の平方根(小数点以下切り捨て)を出力する(つもりの)ものです。 このプログラムの不具合を修正してください。
/* 1*/ import java.io.*; /* 2*/ /* 3*/ class SquareRoot { /* 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, root = 0, square = 0; /* 8*/ System.out.print("Enter a number: "); /* 9*/ x = Integer.parseInt(br.readLine()); /* 10*/ while (square <= x) { /* 11*/ root++; /* 12*/ square = (root + 1) * (root + 1); /* 13*/ } /* 14*/ System.out.println(root); /* 15*/ } /* 16*/ }
b04a001@AsiaA1:~/java% java SquareRoot Enter a number: 9 3 // 正しい b04a001@AsiaA1:~/java% java SquareRoot Enter a number: 6 2 // 正しい b04a001@AsiaA1:~/java% java SquareRoot Enter a number: 0 1 // 間違い b04a001@AsiaA1:~/java%
問5. 次のプログラムは、{10, 20, 30, 40, 50} で表される配列について、配列要素を一つ右にずらして出力する(つもりの)ものです。 ここで、もともと右端にあった要素は削除され、新しく左端には 0 を格納します。 このプログラムの不具合を修正してください。
/* 1*/ class RightShift { /* 2*/ public static void main (String[] args) { /* 3*/ int i; /* 4*/ int[] a = {10, 20, 30, 40, 50}; /* 5*/ for (i = 1; i < a.length; i++) { /* 6*/ a[i] = a[i - 1]; /* 7*/ } /* 8*/ a[0] = 0; /* 9*/ for (i = 0; i < a.length; i++) { /* 10*/ System.out.println(a[i]); /* 11*/ } /* 12*/ } /* 13*/ }
b04a001@AsiaA1:~/java% java RightShift 0 10 10 10 10 // {0, 10, 20, 30, 40} でないので間違い b04a001@AsiaA1:~/java%
今日の演習12に従ってJavaプログラムを作成し、そのプログラムをkonishi@twcu.ac.jpあてにメールで提出してください。 メールには、学生番号、氏名、科目名、授業日(7/9)を明記してください。