Java によるオブジェクト指向演習 1. 演習の目的 授業の一環として,プログラミング言語 Java を用いてオブジェクト指向プログラミングを修得することを目的と する. 一部の節の見出しには右上に「†(ダガー)」が付けられている.これらは発展的な内容であるので, 時間が足りない場合は飛ばしてよい.後から戻ってきてやってみても良い.また,問題の終わりにある記号「■」 はそこまでが問題の範囲であることを示している. 2. オブジェクト指向と Java 言語 オブジェクト指向(OO: Object Oriented)とは,実世界の「もの」を抽象化した「オブジェクト」を構成単位として, プログラムを記述していく枠組みである.本演習で取り扱う Java 言語はオブジェクト指向言語 (OOPL: ObjectOriented Programming Language)の一つである.Java の基本的な式,制御構造,演算子などの構文は C 言語に 類似しているが,オブジェクト指向に基づいている点が大きく異なる. オブジェクト指向における主要な概念として,「クラスとインスタンス」「フィールドとメソッド」「継 承」が挙げられる. クラスとインスタンスは変数と関数の機能を組み合わせたものと捉えられる. クラスは C 言語における型や構造体のようなものであり,ユーザが独自にプログラム中で定義することができ る.インスタンスとはクラスから生成されるデータである.実世界になぞらえるなら,クラスが鋳型,インスタンス が製品といった関係にある.インスタンスは英語で「具体的」という意味であり,クラスは(学校で使われるように) 「集まり」というような意味である.クラスは鋳型であるが,特定の鋳型によって作られた(具体的な)インスタンスた ちは共通の性質を持った集まりとなるため,それが「クラス」という名前の由来であるとも捉えられる. プログラム中でクラスを定義する際,そのクラスに属するインスタンスからのみ実行できる固有の関数を記述し ておく.これをメソッドと呼ぶ.メソッドは C の関数のようにどこからでも呼ぶことができるわけではなく,常にイ ンスタンスを表す変数やクラスを通して呼ぶことしかできない.一方,クラスにおいて定義され,インスタンスが属 性として値を持つことができる変数をフィールドと呼ぶ.これは構造体のメンバに類似する. 実世界に存在する「もの」は通常,属性と機能を持っている.たとえば自動車であれば「車体の色」「排気量」と いった属性に加えて,「加速する」「減速する」という機能がある.プログラミングにこの考え方を持ち込み,属性 (フィールド)や機能(メソッド) を持つクラスを多数定義し,それらを連携させる形でプログラムを書いていくのが オブジェクト指向言語の特徴である. 継承とはひとつのクラスのフィールドとメソッドを継承させつつ,新たなフィールドとメソッドを付け加えた新しい クラスを作成する技法である.すなわち最初に一般的なクラス(スーパークラス)を作っておき,それに様々な機 能を付け加えることでより個別的なクラス(サブクラス)を作り出す. スーパークラスとサブクラスの関係はいわば 「自動車」と「トラック」の関係に類似している.「自動車」に具体的な属性と機能を付け加えて得られるのが「トラッ ク」であるが,トラックは自動車の持つ属性と機能をすべて継承している. これらの概念によってプログラムの書きやすさや読みやすさ,再利用性が高まっているのがオブジェクト指向 言語の利点である.本演習では Java プログラミングを通してオブジェクト指向の考え方に習熟することを目的と している. 2.1 ソースファイルとクラスファイル 1 Java プログラムは以下に示す手順で実行する. 1. ソースファイルを Emacs 等のエディタで作成する. 2. ソースファイルを javac コマンドでコンパイルし,クラスファイルを作成する. 3. java コマンドを使い,クラスファイルを実行する. ソースファイルとは,Java ソースプログラムを指し,拡張子".java"を持つテキストファイルである.C 言語や Ruby のプログラム作成と同様に,Emacs や Windows の「メモ帳」などのテキストエディタを用いて作成すればよ い. ソースファイルを Java コンパイラ(javac)でコンパイルすると,クラスやインタフェースごとにクラスファイルと呼 ばれるバイナリファイルが作成される.クラスファイルは,拡張子".class"を持つ.クラスファイルはバイトコード (bytecode)とも呼ばれ,プラットフォームに依存しないという特徴を持つ.Sample.java という名前のソースファ イルのコンパイルは,以下のコマンドにより行う. (なお,以下では Linux 上での Java の実行環境を前提として いる.) % javac Sample.java エラーが出た場合には,ソースコードのどの行でエラーが生じたかが表示されているので,その行を見て確認 すること. クラスファイル(バイトコード)は,Java 仮想機械(JVM: Java Virtual Machine)と呼ばれるインタプリタ上で解釈 実行する.インタプリタはバイトコードを各計算機に向けの機械語に逐次変換して,その処理を実行する. クラスファイル Sample.class をインタプリタで実行させるには,java コマンドを使用する. (javac では ないので注意) % java Sample 2.2 Java プログラム 簡単な Java プログラム Sample1.java を示す.なお,各行で「//」の後はコメントであり,打ち込まなくてもよい. /* Sample1 */ public class Sample1 { // クラスの宣言 public static void main(String[] args) { System.out.println("Java プログラミングを楽しんでね!"); } } // 文字列を画面に出力 なお,本授業で使用するサンプルプログラムは以下の場所に置いている。 /home/tezuka.taro.gm/lec/java/ また、以下のように Web からもアクセスできるように置いてあるが、こちらを保存すると文字化けを生じさせる可 能性がある。 http://xi.kc.tsukuba.ac.jp/lec/java/Sample1.java Java プログラムでは予約語 class によりクラス Sample1 を定義する.public は,このクラスを他のパッケージに 対して公開することを表す.main は Java プログラムにおいて特殊なメソッド(手続き)であり,java コマンドの引数 として指定したクラスの実行開始点である.java Sample1 と実行した場合は,クラス Sample1 の main から実行が 始まる.System.out.println()の System.out は標準出力を意味し,println()は文字列を画面に表示する メソッドである.また,//からその行の終りまでの文字,/*と*/にはさまれる文字はコメントとなる.Sample1 のプ ログラムをコンパイルし,実行すると,以下のようになる. 2 % javac Sample1.java % java Sample1 Java プログラミングを楽しんでね! Java でソースファイルを作成する際,ファイル名(Sample1.java)と同じ名前の公開(public)クラスを作ってお く必要があることに注意.この場合,ファイル内で Sample1 という名前のクラスが定義されているので問題ないが, もし Sample というクラスしか定義されていない場合はコンパイルエラーが出る.各自確認してみること. Java プログラムは以下のように引数を与えて実行できる.この場合,プログラム中での変数 args[0]に hello が,args[1]に hi が入る. % java Sample1 hello hi 2.3 クラスとインスタンス クラス(class)とはデータ(属性)とそれに対する処理(操作)をひとまとめにして定義した抽象データ型である. Java プログラムは 1 つ以上のクラスで構成され,属性をプロパティ(property)あるいはフィールド(field),操作を メソッド(method)と呼ぶ.実行時には,クラスからオブジェクトを生成し,生成したオブジェクトが動作することで処 理を行う.生成したオブジェクトのことを,クラスに対してインスタンス(instance)と呼ぶ.つまり,クラスとはインスタ ンスを生成する雛形(鋳型)のようなものである.インスタンスを生成し,処理を実行する Java プログラムを Sample2 に示す. /* Sample2 */ public class Sample2 { public static void main(String[] args) { /* Book クラスからインスタンスを生成 Book book1 = new Book("Learn to System.out.println("Book1 title System.out.println("Book1 title System.out.println("Book1 price */ cook sushi", 55); = " + book1.title); = " + book1.getTitle()); = " + book1.getPrice()); /* Book クラスからインスタンスを生成 */ Book book2 = new Book("How to sail", 40); System.out.println("Book2 title = " + book2.getTitle()); System.out.println("Book2 price = " + book2.getPrice()); } } class Book { // クラス Book の宣言 String title; // 題名 private int price; // 価格(ドル) Book(String t, int p) { // コンストラクタ title = t; // 題名の設定 price = p; // 価格の設定 } public String getTitle() { return title; } public int getPrice() { return price; // 題名の取得 // 価格の取得 3 } } Book は本を表すクラスである.属性として本の題名と価格を持ち,それぞれ title という文字列型 (String)および price という整数型 (int)のインスタンス変数で参照する.さらに,Book は本の題名を取 得するメソッド getTitle()と本の価格を取得するメソッド getPrice()を持つ.クラス名と同じメソッドは,コン ストラクタ(constructor)である.コンストラクタはインスタンス生成時に自動的に実行されるため,インスタンス の初期化に用いる. クラス Sample2 では,book1 と book2 という Book 型のインスタンス変数を宣言し,new 演算子によりクラ ス Book からインスタンスを 2 つ生成する.生成したインスタンスは,それぞれ変数 book1 と book2 を用いて 扱う.たとえば,book1.getTitle()により book1 のメソッド getTitle()を呼び出したり,book1.title により book1 のインスタンス変数 title の値を参照したりすることができる.Sample2 のプログラムを実行す ると,以下のようになる. % java Sample2 Book1 title = Learn to cook sushi Book1 title = Learn to cook sushi Book1 price = 55 Book2 title = How to sail Book2 price = 40 Java ではさらに,インスタンスではなくクラスに属するフィールドやメソッドも定義できる.これらは,宣言や定義 において修飾子 static を付け,それぞれクラス変数(class variable)およびクラスメソッド(class method)と呼ぶ. たとえば,System.out の out はクラス変数,Math.sqrt() の sqrt()はクラスメソッドである.クラス変数 やクラスメソッドは,インスタンスを生成しなくともクラス名を指定するだけで参照や呼び出しが可能である.特に, クラス変数は同一のクラスから生成したインスタンス群で値を共有したい場合や定数を保持するために使う. 【基本課題 1】 price フィールドの値はドル単位である.価格を円($1 = ¥92.70)で返すメソッド double getYenPrice() を追加し,main メソッドの中でドルでの価格,円での価格の両方を出力するように変更せよ. ■ 2.4 変数と型 Java において変数はすべて型を持つ.Java のデータ型には,次に示す基本型 (primitive type)と参照型 (reference type)がある.基本型には,論理型の boolean(true あるいは false をとる),整数型の byte, short, int, long,浮動小数点型の float, double,Unicode 文字型の char がある.配列,クラス, インタフェースは参照型であり,その変数の値はすべてポインタとなる. さらに,Java の変数はスコープ(scope)を持つ.スコープとは,その変数を単純名(名前のみ)で参照できる範 囲である.インスタンス変数のスコープは宣言されたクラスであり,メソッド引数のスコープは宣言されたメソッドで ある.ローカル変数のスコープは,それが宣言された位置から始まり,それが宣言されたブロックの終りまでとな る. Sample2.java では title や price はインスタンス変数であり,t や p はメソッド変数である.t や p に 格納されているデータはメソッド(この場合はコンストラクタ)が終わった時に消えるが,title や price に格納 されているものは book1 インスタンスや book2 インスタンスが残っている間は消えない. オブジェクト指向におけるプログラムの挙動は「オブジェクト間でのメッセージパッシング」と説明されることがあ るが,これをソースコードレベルで見ると,「クラス A のメソッドの定義の中でクラス B のメソッドを呼び出す」ことが メッセージパッシングに相当する。 【基本課題 2】 4 Book クラスに価格を設定するためのメソッド void setPrice(int y) を追加せよ.■ 【基本課題 3】 Book クラスに出版年(西暦)を保持するフィールド(int 型)year を追加せよ.さらに,そのフィールドにアクセ スするためのメソッド void setYear(int y) と int getYear()を作成し,main メソッドの中でインスタ ンス book1 に対して出版年の設定と出力を行うように変更せよ(本の出版年は任意に設定してよい). ■ 【基本課題 4】 題名,価格,出版年を一度に設定してインスタンスを生成するためのコンストラクタを追加せよ.■ 2.5 演算子と文 Java の主な演算子には,算術演算子(+, -, *, /, %, ++, --, ?:),代入演算子(=, +=, -=, *=, /=, %=),比較演算子(<, <=, >, >=, ==, !=),論理演算子(&&, ||, !)がある. また,Java は,条件文(if),繰り返し文(while, for),break 文,continue 文, return 文を備える.これらの記 法と意味は,C 言語や Ruby と同じである. これら以外の重要な演算子として,インスタンスを生成する new,型を変換するキャスト(型を括弧で括る),イ ンスタンスの型を判定する instanceof,配列の生成やアクセスに用いる[],限定名を形成するドット(.)があ る. 【基本課題 5】 価格(インスタンス変数 price)が x より低いかどうかを判定するメソッド boolean isCheaperThan(int x)を追加せよ. これを用いて,main メソッドの中で 50 ドルより安い場合のみ,本の情報を出力させるようにせ よ.■ 2.6 文字列 配列を用いて文字列を実現する C 言語と異なり,Java では文字列を扱うクラス String と StringBuffer が用意されている.String から生成したインスタンスでは,格納されている文字列を変更することはできない. これに対して,StringBuffer から生成したインスタンスでは,文字の追加や挿入ができるようになっている. ここで,String のインスタンスは頻繁に利用されるため,new 演算子を使わなくとも,次のようにインスタンスの 生成が可能である. String str = "Java"; // String str = new String("Java");と同じ String から生成したインスタンスは参照オブジェクトであるため,文字列の比較には==演算子ではなく,ク ラス Object のメソッド equals()を用いる.たとえば String 型の a と b を比較する if 文は以下のように なる. if(a.equals(b)){ } また,文字列の先頭が一致するかどうかを確かめる startsWith(),末尾が一致するかを確かめる endsWith()などのメソッドもある.たとえば a に”sushi”が含まれるかどうかを確かめることは以下の if 文 によって可能になる. if (a.startsWith(”sushi”)){ } String クラスが持つプロパティやメソッドの一覧はウェブページなどで参照できる.「Java String」で検 索するとよい.String に限らず,多くのクラスに関してそれが持つプロパティやメソッドの一覧はウェブ上で検 索できる. 5 2.7 配列 Java では,複数のオブジェクトをまとめて扱うために配列とコレクションクラスが用意されている. 配列(array)とは,同じ型の複数の変数(要素)を並べたものである.配列は []を用いて宣言し,i 番目の要素 にアクセスする場合は,添字 i を[]に入れて指定する.Java では,配列もクラス (オブジェクト)であるため, new 演算子を用いて,要素数に応じた領域を確保しなければならない.配列の利用例を示す. int numbers[]; // int[] numbers でもよい numbers = new int[10]; // 10 個分の領域を確保(添字 0~9) numbers[5] = 1; // 5 番目の要素に 1 を代入 System.out.println("5th = " + numbers[5]); // 5 番目の要素を表示 System.out.println("length = " + numbers.length); // 配列の長さを表示 int max = 10; String strings[] = new String[max]; // max 個分の領域を確保(添字 0~max-1) 配列に文字列を設定するのは以下のように行える. String[] kwds = {“Japanese”, “cuisine”, “sushi”, “fish”}; 配列の長さを取得したい場合は,配列インスタンスのフィールド length を用いる.たとえば上記の配列の長 さは kwds.length で求められる. 【基本課題 7】 Book クラスの中で書籍の検索時に使えるキーワードを格納する配列 keywords をインスタンス変数として 定義しておき,それを設定できるコンストラクタを加えよ.■ 【基本課題 6】 実行時に与えた引数をタイトルに含む書籍の情報を出力させるプログラムを作成せよ.実行時に与えた引数 の値は配列 args に格納されているので,args[0]などで取り出せる.■ 【基本課題 8】 keywords の中を調べ,もし与えられたクエリ(query)が keywords の要素として含まれている場合に true を 返 す メ ソ ッ ド boolean containsInKeywords(String query) を 追 加 せ よ . こ れ に は keywords.length を使い,配列の各要素についてチェックを行うループをまわすという方法がある. さらに,キーワードを格納した配列 kwds を作った上でそれを引数として与えて Book クラスのインスタンスを 作成せよ.また,実行時に与えた引数とキーワードのどれかがマッチする場合にその書籍のタイトルを出力させ るプログラムを書け.なお,実行時に与えた引数は配列 args の中に格納されている.■ 2.8 継承 継承(inheritance)とは,既存クラスのインスタンス変数やメソッドを引き継いで新しいクラスを定義することであ る.継承を用いると,すでに存在するクラスに機能を追加したり,一部の機能を書き換えるだけで新しい機能を 持つクラスを作成することができる.継承を用いてクラスを拡張するプログラムを Sample3 に示す. /* Sample3 */ public class Sample3 { 6 public static void main(String[] args) { Book book = new Book("Learn to cook sushi", 55); System.out.println("Title = " + book.getTitle()); System.out.println("Price = " + book.getPrice()); OnlineBook obook = new OnlineBook( "The Java Virtual Machine Specification", 0, "http://java.sun.com/docs/books/vmspec/index.html"); System.out.println("Title = " + obook.getTitle()); System.out.println("Price = " + obook.getPrice()); System.out.println("Website = " + obook.getWebsite()); } } class Book { ... // Sample2 と同じ } class OnlineBook extends Book { // クラス Book を継承 public String website; // URL OnlineBook(String t, int p, String website) { super(t, p); // スーパークラスのコンストラクタの呼び出し this.website = website; // URL の設定 } public String getWebsite() { return website; } // URL の取得 public String getTitle() { return "Online: " + title; } } 予約語 extends は継承の宣言を表す.つまり,クラス OnlineBook はクラス Book を拡張している.Book を親クラスあるいはスーパークラス (super-class),OnlineBook を子クラスあるいはサブクラス(subclass)と呼ぶ. Java では,すべてのクラスは Object(java.lang.Object)のサブクラスであり, extends を省略した場合は,Object の直接の子クラスになる. Sample3 において,OnlineBook から生成したインスタンスは,OnlineBook のインスタンス変数 website,メソッド getWebsite()に加えて,Book の title, price, getTitle(), getPrice() が利用できる.ただし,コンストラクタは継承されない.継承により引き継がれる機能は,直接の親クラスだけでな く,間接的に継承している親クラスの祖先からも引き継がれる(よって,OnlineBook は Object の機能も引き 継ぐ).さらに,OnlineBook ではメソッド getTitle()を再定義することで, Book の機能の一部を修正して いる.このように,親クラスに存在するメソッドと同じシグニチャを持つメソッドを子クラスで再定義することをメソッド のオーバーライド(override)という.シグニチャとは,メソッドの引数の型と並びを指す. ここで,super は親クラス,this は自分自身(のインスタンス)を指す.また,super()および this()はコ ンストラクタ内部でのみ利用可能でそれぞれ親クラスおよび自分自身の別のコンストラクタを呼び出す際に用い る. Sample3 のプログラムを実行すると,次のようになる. % java Sample3 Title = Learn to cook sushi Price = 55 Title = Online: The Java Virtual Machine Specification 7 Price = 0 Website = http://java.sun.com/docs/books/vmspec/index.html 【基本課題 9】 OnlineBook クラスを継承した OnlineMagazine クラスを作成し,出版月と出版日を保持するフィールド(int 型 ) を 追 加 せ よ . こ れ ら に 登 録 を 行 う メ ソ ッ ド void setMonth(int month) , void setDate(int date),および出版年/出版月/出版日をまとめて出力するメソッド String getPublicationDate()を 作成し,main メソッドの中で使用せよ.出力のフォーマットはどのようなものでもよい.■ 2.9 抽象クラス/多態性/インタフェース 継承を用いると,類似した機能を持つクラスの共通部分をまとめて親クラスで定義しておき,それぞれ異なる 機能だけをその子クラスに定義することができる.その際,親クラスではメソッドの実装を定義せずに,そのシグニ チャ(引数の型の組み合わせ)だけを規定しておきたいことがある.このような要求に対して,Java では抽象クラ ス (abstract class)が用意されている.実装が定義されていないシグニチャだけのメソッドを抽象メソッド(abstract method)という.抽象クラスは実装を持たないメソッドを含むため,直接インスタンスを生成することはできない.抽 象クラスの子クラスからインスタンスを生成するためには,その子クラスで抽象メソッドをすべて実装する必要があ る.抽象クラスを含むプログラムを Sample5 に示す. /* Sample5 */ public class Sample5 { public static void main(String[] args) { Publication[] pub = new Publication[4]; pub[0] = new Article("Java Compiler", "IEEE Software"); pub[1] = new TechReport("Research on Software Engineering", "University of Tsukuba"); pub[2] = new Article("An apple a day", "Nature"); pub[3] = new TechReport("Research on Global Warming", "University of World"); for(int i = 0; i < pub.length; i++){ pub[i].showInfo(); } } } abstract class Publication { // 抽象クラスの宣言 protected String title; // 題名 Publication(String t) { title = t; } abstract public void showInfo(); // 抽象メソッドの宣言 } class Article extends Publication { // クラス Publication を継承 private String journal; // 雑誌名 Article(String t, String j) { super(t); journal = j; } 8 public void showInfo() { // showInfo の()実装 System.out.println("Article: " + title + ", in " + journal); } } class TechReport extends Publication { // クラス Publication を継承 private String institution; // 発行機関 TechReport(String t, String i) { super(t); institution = i; } public void showInfo() { // showInfo の()実装 System.out.println("Report: " + title + ", by " + institution); } } 修 飾 子 abstract は 抽 象 ク ラ ス お よ び 抽 象 メ ソ ッ ド に 付 加 す る . Publication は 抽 象 ク ラ ス , showInfo()は抽象メソッドである.Publication を継承しているクラス Article および TechReport で は,メソッド showInfo()を実装しているため,インスタンスが生成可能である.また,クラス Sample5 では,ク ラス Article および TehcReport から生成したインスタンスを Publication 型のインスタンス変数 pub で保持している.Java では,このように子クラスのインスタンスを親クラスのインスタンスとして保持することが可 能である. Sample5 のプログラムを実行すると,次のようになる. % java Sample5 Article: Java Compiler, in IEEE Software Report: Research on Software Engineering, by University of Tsukuba Article: An apple a day, in Nature Report: Research on Global Warming, by University of World この実行例をみると,メソッド pub.showInfo()の呼び出しに対して,Article か TechReport のイン スタンスのどちらかが選択され,そのインスタンスのメソッド showInfo()が実行されていることがわかる.このよ うに,インスタンス変数 pub に格納されている値の型に応じて実行時に呼び出しメソッドが決定されることを動 的束縛(dynamic binding)と呼ぶ.また,showInfo()のように 1 つのメソッド呼び出しで,異なる振る舞 いを実現することを多態性(polymorphism,多相性)と呼ぶ.また,メソッドを上書きすることをオーバーライド (override)と呼ぶ.これによってどのサブクラスに属するインスタンスが格納されるか事前に分からない状況 での処理の場合分けが容易になる.なお,オーバーライドは抽象クラスでないクラスに対しても行える. オーバーライドと名前が似ているが異なる概念として,メソッドのオーバーロード(overload)がある.こちらは シグニチャの異なるメソッドを定義することを指す.Java ではシグニチャが異なれば,同一クラス内でも同じ名 前を持つメソッドを多重に定義することができる.たとえば,次の 2 つのメソッドは,オーバーロードの関係にあり, 別のメソッドとして扱われる. public int getPrice() { ... } public int getPrice(int rate) { ... } Java では,抽象メソッド(と定数)だけで構成されているクラスをインタフェースと呼ぶ.インタフェースの利用例 を以下に示す. interface Printable { public void print(); } 9 class Article extends Publication implements Printable { public void print() { ... } } インタフェースは抽象メソッドしか持たないため,修飾子 abstract は不要である.インタフェースを実装(継 承)する際には,extends ではなく implements を用いる.インタフェースはメソッドの実装を持たずにそのシ グニチャを規定でき,さらにインタフェースを継承するクラスにメソッドの実装を強要するため,フレームワークの 構築によく使われる. 【基本課題 10】 Publication クラスにおいてその String クラスの各フィールド(書誌事項)を調べ,変数として与えられたク エリ(query)をどこかに含むかを判定し,戻り値として boolean を返すメソッド searchBiblio(String query)を抽象メソッドとして定義せよ.このメソッドをサブクラス Article と TechReport において実装せよ. これを Sample5 の main の中で使用し,「Software」という単語を含む Publication だけが出力される ようにせよ.■ 【基本課題 11】 前の課題の結果において,Publication クラスをインタフェースに書き換えよ.その際,コンストラクタを取り除 くことに注意する.Article と TechReport もそのインタフェースを implements する形に書き換えよ.■ 2.10 入出力処理† Java ではデバイスへの入出力をストリーム(stream)で扱う.これらは,java.io パッケージに含まれている. ス ト リ ー ム は 4 つ の ク ラ ス InputStream, OutputStream, Reader, Writer を 基 礎 と す る . InputStream と OutputStream はバイト指向ストリーム(バイトデータの入出力に用いる)であり,Reader と Writer は文字指向ストリーム(テキストデータの入出力に用いる)である. ファイルから文字列を入力する場合は,ファイル名を指定して FileReader オブジェクトを生成する.ファイ ルに文字列を出力する場合は,FileWriter オブジェクトを生成する.ファイル入出力のプログラムを Sample8 に示す. /* Sample8 */ import java.io.*; public class Sample8 { public static void main(String[] args) { String filename = "test8.txt"; try { PrintWriter pw = new PrintWriter( new BufferedWriter( // バッファを挿入 new FileWriter(filename))); // 出力ストリームの作成 pw.println("Java"); pw.println("Programming"); // 文字列を書き出す pw.println("Language"); pw.close(); // 出力ストリームのクローズ } catch (IOException e) { System.out.println("Cannot write: " + filename); System.exit(1); // プログラムの終了 } try { BufferedReader br = new BufferedReader( 10 // バッファを挿入 new FileReader(filename)); // 入力ストリームの作成 String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); // 入力ストリームのクローズ // 文字列を読み込む } catch (FileNotFoundException e) { System.out.println("File Not Found: " + filename); } catch (IOException e) { System.out.println("Cannot read: " + filename); System.exit(1); // プログラムの終了 } } } この Sample では,ファイルの入出力を効率的に行うためにクラス BufferedReader によりバッファリング を行っている.Sample8 のプログラムを実行すると,次のようになる. % java Sample8 Java Programming Language % cat test8.txt Java Programming Language キーボードからの入力を受け付けるには,System.in をもとにした InputStreamReader クラスのインス タンスを作成し,それをもとにして BufferedReader クラスのインスタンスを作成する. すなわち new BufferedReader(new InputStreamReader(System.in)) という形で作成する. /* Sample9 */ import java.io.*; public class Sample9 { public static void main(String[] args) { try { BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); // 入力ストリームの作成 System.out.print("お名前は?"); String str = br.readLine(); System.out.println("こんにちは," + str + "さん!"); } catch (IOException e) { System.out.println("Exception in IO."); System.exit(1); // プログラムの終了 } } } 11 【発展課題 1】 本のタイトルと価格,出版年がコンマで区切られ,一行ずつ入っている CSV 形式(comma separated value 形式)のデータを読み込み,その内容をタブで区切って出力させるプログラムを作成せよ. CSV ファイルから要素を切り出すには String クラスの indexOf メソッドと substring メソッドを使用すれ ばよい.たとえば str の最初のコンマまでを切り出すには以下のようにすればよい. elem[0] = str.substring(0, str.indexOf(“,”)); 以下のようにすると str の残りの部分を取り出せる. str = str.substring(str.indexOf(“,”)+1); コンマを含まない場合は str.indexOf の戻り値が-1 になるので,それまで繰り返すことですべての要素を 取り出せる. 書誌事項を載せた CSV ファイルは自分で作成すること.また,タブは「\t」で表現される.■ 2.11 例外処理† C 言語によるプログラミングでは,コンパイル時には判定できないエラーを起こす可能性がある文の実行に対 して,その直後に条件文を用いてエラー検査を行う.この方法では,エラー検査のコードがプログラムに散らばり 再利用を妨げる.これに対して,Java は正常な処理とエラー処理を分離する例外(exception)という仕組みを持 つ.Java の例外処理は,例外を送出する部分と送出された例外を捕捉して決められた処理を実行する部分から なる.例外の送出と捕捉を含むプログラムを Sample6 に示す. /* Sample6 */ public class Sample6 { public static void main(String[] args) { int numbers[] = new int[10]; // 配列の宣言と確保 try { // ここから numbers[20] = 0; // エラー発生 } catch (ArrayIndexOutOfBoundsException e) { // ここまでの例外を捕捉 System.out.println("Exception occurred: " + e); // 例外処理 } System.out.println("Finish!"); // 終了メッセージの表示 } } ArrayIndexOutOfBoundsException は,配列の添字が範囲を越えた際に送出される例外である. Java では,エラーが発生する可能性のあるコードを try 文と catch 文にはさむことで例外を捕捉し, catch 文のブロックに例外処理を記述する.捕捉された例外は,catch ブロックの実行後に消滅する. Sample6 のプログラムを実行すると,次のようになる. % java Sample6 Exception occurred: java.lang.ArrayIndexOutOfBoundsException Finish! 12 エラーが発生しているにもかかわらず,例外処理が実行され,プログラムは正常に終了している. さらに,Java では,独自の例外を作成することや throw 文を用いて明示的に例外を送出することができる. Sample7 は,例外 NoTitleException を独自に定義するプログラムである(クラス Exception のサブクラ スとして NoTitleException を定義している).クラス Manual のメソッド setTitle()の処理中に例外 NoTitleException が送出される可能性があるため,throws 文を用いてその例外を送出する可能性があ ることを宣言する. /* Sample7 */ public class Sample7 { public static void main(String[] args) { try { Manual manual = new Manual(); manual.setTitle(""); // 文字数 0 の題名を設定 } catch (NoTitleException e) { // 例外の捕捉 System.out.println("No title"); } } } class Manual { String title; public void setTitle(String t) throws NoTitleException { if (t.length() == 0) { throw new NoTitleException(); // 例外の送出 } title = t; } // 例外の送出 } class NoTitleException extends Exception { } // 例外の定義 【発展課題 2】 独自の例外を作成し,それを catch するプログラムを作成せよ.たとえば Book クラスのインスタンスを作 成する際,出版年が現在よりも先の年になっていた場合,入力ミスであると考えられるため, yearOutOfBound という例外を throw させ,メッセージを表示させるという対応が考えられる.■ 2.12 パッケージとアクセス制御† 大規模なプログラムを開発する際には,複数の人間で効率的にクラスを作成したい.また,Java ではクラスごと にクラスファイル(拡張子が .class のファイル)を作成するため,複数のクラスを同じファイルに記述するよりも クラスごとに Java ソースファイルを作成する方が,不必要なコンパイルを削減できる.たとえば,Sample2 におい て,クラス Book をクラス Sample2 以外のクラスから利用する場合は,Book のコードをソースファイル Book.java に分けて記述するとよい.これにより,Sample3.java では Book のコードをあらためて記述する必要はなく, Sample2 と Book, Sample3 と Book は独立に開発することができる. また,大規模なプログラムを作成する場合は,ほかの人が作成したクラス(あるいはインタフェース)を再利用 する場合が多い.さまざまな人が作成したクラスを混在させて利用する場合に名前の衝突を避けるため,Java に はパッケージという仕組みが組み込まれている.ソースファイルの先頭に package 文が存在すると,そのファイル 13 で定義したクラスやインタフェースはそのパッケージ(名前空間, name-space)に属することになる.たとえば,次 のように記述すると,クラス File はパッケージ mysoft に属し,完全限定名(fully-qualified name)は, mysoft.File となる.これによって J2SE のクラスライブラリに含まれる java.io.File(java.io パッケ ージ)と区別される. package mysoft; class File { ... } また,パッケージ mysoft に含まれるクラスファイルはパッケージ名と同じ(この場合 mysoft)ディレクトリの 中にまとめて入れておかなくてはならない。 ソースファイルに package 文が存在しない場合は,そのファイルで定義されているクラスは無名パッケージ に属することになる.同じパッケージに属するクラスどうしは,特にパッケージ名を指定しなくともお互いに利用可 能である.異なるパッケージに属するクラスを利用する場合は,パッケージ名を含む完全限定名で指定する.た とえば,パッケージ mysoft に含まれるクラス File を指定する場合は,mysoft.File とすればよい.また, import 文を用いると,完全限定名を指定しなくともクラス名だけでクラスを指定可能となる.よって,次のコード において,File は mysoft.File と同じものを指す. import mysoft.*; class Directory { mysoft.File file1; // 完全限定名で指定 java.io.File file2; // mysoft.File とは異なるクラス File file3; // mysoft.File と同じクラス } 特定のパッケージの直下に属するクラス群をまとめて指定する場合は,次のように"*"を用いて import 文を記 述することができる. import java.io.*; class Directory { ... } このように記述することで,パッケージ java.io に属するクラスをクラス名だけで指定できる.ここで, java.lang パッケージについては,特に import 文を記述しなくとも自動的に取り込まれている. パッケージに関連して,Java では情報隠蔽(information hiding)を実現するために,アクセスを制御する (access control)修飾子がいくつか用意されている.private なメソッドはクラスの公開後に書き換えても他の 利用者に影響を与えないため,分業での開発が容易になる。修飾子とアクセス制御の関係を以下にまとめる. public: 異なるパッケージのクラスから,そのクラス,変数,メソッドにアクセス可能 protected: 同じパッケージとサブクラスから,その変数,メソッドにアクセス可能 なし: 同じパッケージから,そのクラス,変数,メソッドにアクセス可能 private: クラス内からのみ,その変数,メソッドにアクセス可能 【発展課題 3】 現在までに作成した各種クラスをひとつのパッケージ book にまとめよ.この時,book パッケージに属する各ク ラスは book ディレクトリに入れることに注意する.また,各メソッドやフィールドに public を付けないと他のクラ スからの呼び出し時にエラーが生じるので注意すること.■ 参考文献 14 [1]* 10 日でおぼえる Java 入門教室 第 3 版, 丸の内とら著, 翔泳社, 2012. [2]* 独習 Java 第 4 版, O’Neil, J.著, トップスタジオ他訳, 翔泳社, 2008. [3] Java 言語仕様 第 3 版, Gosling, J.他著, 村上雅章訳, ピアソンエデュケーション, 2006. [4] プログラミング言語 Java 第 4 版, Arnold, K.他著, 柴田芳樹訳, ピアソンエデュケーション, 2007. *がついているものが初級者向けの入門書であり,学習に適している.それ以外はリファレンス的な書籍である. 15
© Copyright 2024 Paperzz