2016年度 発展プログラミング演習 第3講 図書館システム 1日目

本日の内容

  1. ウェルカムメッセージを表示する.
  2. 本の情報を表す型を作成する.
  3. 図書館システムに本のリストを持たせる.
  1. 練習問題
    1. 本を追加する.
    2. Listの範囲を超えてアクセスする
    3. ファイルからBookを作成する
    4. 本の一覧を作成する
  2. 本日のまとめ

1. ウェルカムメッセージを表示する

1-1. まずやってみる

先週学習したクラス定義の基本形を元に, ファイル名をLibrary.javaとして,図書館システムを作成していきましょう. ここでは,復習を兼ねて一つずつ説明していきます.では,書いてみましょう.

Javaプログラムの一番外側は,必ず public class Foo{ } で囲まれます. この部分をクラス宣言と呼びます. そして,この部分が決まれば,ファイル名もFoo.javaと決まります. このとき,Fooクラス名と呼びます.

また,Javaの名前は,キャメルケース で表すのが一般的です.C言語のスネークケース とは異なりますので,注意しましょう.

1-2. 最初に実行されるもの

Javaでは,最初に実行されるメソッドは, mainメソッドです.メソッドとは,引数返り値を持つ,ひとまとまりの作業(手続き)のことです. C言語で言うところの関数と同じようなものと思ってもらって結構です. Java言語のmainメソッドのシグネチャ は厳密に決められています.返り値は,void,引数はString型の配列です. voidは何も値を返さないことを表す型です. また,String型は,その名前の通り,文字列を表す型です. mainメソッドの引数では,コマンドラインで与えられた値が文字列型の配列として渡されます.

public class Library{
    public static void main(String[] args){
    }
}

mainメソッドは,クラス宣言の波括弧 の中に書かなければいけません. 大文字,小文字など,一言一句合っている必要があります. 一種のイディオムとして覚えてしまいましょう.

mainメソッドの後ろの括弧内には, mainメソッドの引数が記述されます.

1-3. ウェルカムメッセージを表示する

Javaでは,ターミナルに文字列を出力するために,System.out.printlnメソッドを利用します. System.out.printlnは,改行付きで引数に与えられた値を出力するメソッドです. 改行したくない場合は,System.out.printを利用しましょう. また,C言語のprintfのように利用したい場合は, System.out.printfメソッドが利用できます(FAQを参照してください). では,ウェルカムメッセージを出力してみましょう.出力するメッセージは何でも構いません.

public class Library{
    void run(){
        this.printWelcomeMessage();
    }
    void printWelcomeMessage(){
        System.out.println("ようこそ図書館システムへ.");
    }
    public static void main(String[] args){
        Library lib = new Library();
        lib.run();
    }
  }

runメソッドの中でウェルカムメッセージを表示するのも良いですが, 左のプログラムのように,ウェルカムメッセージを表示するためだけのメソッドを用意する方がより良いでしょう. 先週のページにも書きました が,ウェルカムメッセージを表示するためのメソッドを用意することで,run メソッドの処理内容を見るだけで,どのようなことを行うのかが理解できます.

これからも様々な仕様を追加していきます.その都度細かくメソッドに分けてプログラムを書いていきましょう. それがバグの少ないプログラムに繋がっていきます.

実行すると,次のように出力されます.

$ java Library
ようこそ図書館システムへ.

2. 本の情報を表す型を作成する.

2-1. 仕様を確認する.

JavaではC言語でのintと同じレベルの型が自由に定義できます. ここでは,本(Book)という型を定義します. 仕様には,本について,次のように記述されています.

本は,タイトル(title),著者(authors),出版社(publisher),出版年(publishYear)を持つ.

仕様を見ると,本にはタイトルと,著者,出版年が含まれているように見えます. その通り,素直に解釈し,Bookという型には, titleauthorspublisherpublishYearという4つの変数を宣言しましょう.

2-2. 型を作成する.

では,実際に型を作成しましょう.といっても,特に変わったことはありません.先ほどのLibrary.javaと同じく,Book.javaを作成します.Library.javaと同じディレクトリに作成してください.

public class Book {
}

以降,『Fooクラスを作成してください』と言われた場合は, Foo.javaを適切な場所に作成してください.そして,内容を public class Foo { } としてください. Fooは適宜読み替えるようにしてください.

では,Bookクラスを作成してください. このプログラムは,mainメソッドを持ちません. 単体では実行できない,Libraryから参照されるためのプログラムです.

2-3. 型に変数を追加する.

次に,4つの変数それぞれの型を考えましょう.この図書館システムで最初から利用できる型は,次の4種類です.

実は,Javaで利用できる型は1万以上あり,さらにユーザが独自に定義もできます. しかし,この講義では,最初は少数の型で慣れてもらい,徐々に利用する型を増やしていきます.

public class Book {
    String title;
    String authors;
    String publisher;
    Integer publishYear;
}

さて,Bookという型に含まれる4つの変数,titleauthorspublisherpublishYear はそれぞれどのような型が相応しいでしょうか.titleauthorspublisherは文字列(String)で表し, publishYearは整数(Integer)で表すのが良さそうです.

これでBookという型が作成できました.

3. 図書館システムに本のリストを持たせる.

3-1. 概要

最初に2で作成したBook型の変数に値を代入してみましょう. Book型に限らない話ですが,値を作成するには,new というキーワードを使い,値を作成する必要があります.以下の図のように, 値を作成する前は,実体はメモリ上に確保されていません. newで値を作成することではじめてメモリ上に領域が確保されて値が代入できるようになります. また,値はいくつでも作成でき,値が異なれば互いに影響は受けず,独立した値として扱えます.

newの動作イメージ

3-2. Bookの実体を作成し,タイトルなどを代入する.

では,Libraryrunメソッド内で, Bookの実体を作成しましょう.そして,上の図にあるように, タイトル,著者,出版社,出版年を代入しましょう.

public class Library {
    void run(){
        this.printWelcomeMessage();
        Book book = new Book();
        book.title = "羅生門";
        book.authors = "芥川龍之介";
        book.publisher = "青空文庫";
        book.publishYear = 1997;

        System.out.printf("%s (%s) %s, %d%n",
            book.title, book.authors, book.publisher, book.publishYear);
    }
    void printWelcomeMessage(){
        System.out.println("図書館システムへようこそ.");
    }
    public static void main(String[] args){
        Library lib = new Library();
        lib.run();
    }
}

このように,型の中で宣言された変数であっても,ドット(.) を介してアクセスできます.実は,System.out.println も同じようなフォーマットになっています. この呼び出しは,Systemというクラスの変数outprintlnというメソッドを呼び出しています.

book.title = new String("羅生門");
book.authors = new String("芥川龍之介");
book.publisher = new String("青空文庫");
book.publishYear = new Integer(1997);

String型,Integer型はnew しなくても実体が作成されているように見えますが,これはなぜでしょうか. 本来であれば,左図のように書かなければいけないはずです. しかし,文字列や整数型をはじめとした数値型はプログラム中で頻繁に使います. そのため,簡略化して書けるようになっているのです. しかし,バイナリ中では,以下のようなコードになっています.コンパイラが頑張って変換してくれているんです.

3-3. Bookを作成するメソッドを用意する.

さて,1冊の本の情報を作成できました.しかし,これから何冊もの本を同じように作成するとなると runメソッドに似たコード片を何度も書かなくてはならなくなります. そこで,Bookを作成するcreateBookメソッドを用意しましょう. createBookメソッドは,本のタイトル,著者,出版社,出版年を引数として受け取り, Bookの実体を返します.

    Book createBook(String title, String authors, String publisher, Integer publishYear){
        Book book = new Book();
        book.title = title;
        book.authors = authors;
        book.publisher = publisher;
        book.publishYear = publishYear;
        return book;
    }

このメソッドが作成できれば,先ほどの本の実体作成部分をcreateBook メソッドの呼び出しに変更してみましょう.

3-4. Bookの集合を保持する変数を用意する.

さて,ここまでで1冊の本の実体を作成できるようになりました. しかし,図書館たるもの複数冊の本が管理できなければいけません. C言語の場合は,同じ型の値を複数扱うときは,配列を使っていました. Javaにも配列はありますが,この図書館システムが扱う本の上限は設定されていません. また,途中で追加,削除が行われる可能性もあります. 配列を使う上でのメリットとデメリットをまとめておきましょう.

配列のメリット
同じ型の値を複数個扱うときに,一つの変数で扱える.
配列のデメリット
上限が決まってしまう.
途中のインデックスの値を削除すると,あとあと面倒.

配列のデメリットは,以下の2つの図でも表されます.例えば,整数を格納する配列を考えてみましょう. 配列の上限が5と決まれば,その配列はそれ以上の要素を格納できません.6つ目の要素を追加したい場合, 配列をつくり直すところからやり直さなければいけません. また,2番目の要素が削除した後,要素を詰めて空欄をなくしたい場合があります. そのとき,3番目以降の要素を一つずつ左にずらしていく操作が必要になります.

配列のイメージ 途中のインデックスが削除された配列

もちろん,上記の問題はプログラムを頑張れば解決できます. しかし,両方の問題ともに,プログラムの目的からすると,本質的ではありません. そのような本質的でないところに多大な労力をかけるのは本末転倒です. そして,近年のプログラム言語では,配列より高度なデータ構造が標準で用意されています. 当然のことながら,Javaでも標準で用意されています.List型です. 上限がなく,途中の要素を削除しても,自動的に間を詰めてくれます.

List型を利用するときには,List型に入れる型を指定する必要があります. 例えば,ListString型の値を順番に入れていくときは, List<String>型として宣言する必要があります. また,List型の実体を作成するときにも,newを利用しますが, List<String> list = new List<String> とはできません. Listはあくまで型であり,実体を作成できるようにはなっていないためです. ArrayList<String>の実体を作成することで, List<String>型の変数に代入できるようになります. さらに,ListArrayList を利用するときは,クラス宣言よりも前に import java.util.*;という一行を書いておく必要があります.

では,上で作成した Book型の変数を Libraryが持つ List<Book>型の変数shelfに格納しましょう.

import java.util.*;
public class Library {
    void run(){
        this.printWelcomeMessage();
        List<Book> shelf = new ArrayList<Book>();
        shelf.add(this.createBook("羅生門", "芥川龍之介", "青空文庫", 1997));

        Book book1 = shelf.get(0);
        System.out.printf("%s (%s) %s, %d%n",
            book1.title, book1.authors, book1.publisher, book1.publishYear);
    }
    Book createBook(String title, String authors, String publisher, Integer publishYear){
        Book book = new Book();
        book.title = title;
        book.authors = authors;
        book.publisher = publisher;
        book.publishYear = publishYear;

        return book;
    }
    void printWelcomeMessage(){
        System.out.println("図書館システムへようこそ.");
    }
    public static void main(String[] args){
        Library lib = new Library();
        lib.run();
    }
}

ここは確かにややこしいです.しかし,配列という古いデータ構造は, 近年のプログラム言語ではほとんど使われなくなってきています. 文法的には配列でも,実際にはより高度なデータ構造である場合がほとんどです. Javaでも配列はデータの受け渡しにしか使われておらず, C言語では配列を利用すべき場面では,ほとんどこのList型に置き換わっています. 皆さんも,配列ではなく,より高度なデータ構造を使うようにしましょう.

練習問題

1. 本を追加する

以下のプログラムを参考に,shelfに5冊の本を格納してください.

import java.util.*;
public class Library {
    void run(){
        this.printWelcomeMessage();
        List<Book> shelf = new ArrayList<Book>();
        this.addBooks(shelf);

        Book book1 = shelf.get(0);
        this.printBook(book1);
        Book book2 = shelf.get(1);
        this.printBook(book2);
        Book book3 = shelf.get(2);
        this.printBook(book3);
        Book book4 = shelf.get(3);
        this.printBook(book4);
        Book book5 = shelf.get(4);
        this.printBook(book5);
    }
    void printBook(Book book){
        System.out.printf("%s (%s) %s, %d%n",
            book.title, book.authors, book.publisher, book.publishYear);
    }
    void addBooks(List<Book> shelf){
        Book book1 = this.createBook("羅生門", "芥川龍之介", "青空文庫", 1997);
        shelf.add(book1);

        // 適当に本を追加する.
    }
    Book createBook(String title, String authors, String publisher, Integer publishYear){
        Book book = new Book();
        book.title = title;
        book.authors = authors;
        book.publisher = publisher;
        book.publishYear = publishYear;

        return book;
    }
    void printWelcomeMessage(){
        System.out.println("図書館システムへようこそ.");
    }
    public static void main(String[] args){
        Library lib = new Library();
        lib.run();
    }
}

2. Listの範囲を超えてアクセスする

練習問題1では,shelfに入れたBookの実体を取り出しています. これをfor文での繰り返しを用いたプログラムに変更してみましょう. List型に入っている要素の数を取得するには, sizeメソッドが利用できます.次のプログラムで確認してみてください.

List<Book> shelf = new ArrayList<Book>();
// ....
for(Integer i = 0; i < shelf.size(); i++){
    // ここに,shelf からBook型の要素を取り出し,
    // printBook を呼び出す処理を書く.
}
// ...

以上のことができれば,shelf に入れた要素の数を超えてアクセスしようとすると何が起こるでしょうか. 確認してみましょう.

このように,どのようなエラーがどんな状況で起こるのかを予め知っておくと, 今後,同じようなエラーが発生した時に,役立ちますので,様々なエラーを積極的に起こしてみましょう.

3. ファイルからBookを作成する

books.csvに書かれた本を shelfに登録してみましょう. そのために,LibraryUtil.javaを用意しました. このプログラムをダウンロードし,Library.javaと同じディレクトリに置いてください.

    void addBooks(List<Book> shelf){
        LibraryUtil util = new LibraryUtil();
        List<Book> books = util.readFromFile("books.csv");
        for(Integer i = 0; i < books.size(); i++){
            shelf.add(books.get(i));
        }        
    }

左のプログラムのように,readFromFile メソッドに 文字列 "books.csv" を渡すと,Bookが格納された Listが返されます. このBookは books.csv に書かれている内容を読んで作成されています. book.csv には,1行に1つのBookの情報が記入されているとしています. タイトル,著者名,出版社,出版年を,それぞれコンマ区切り(半角のコンマ) で記入するとそれらが読み込まれるようになっています. 一つ一つのBookを出力して確認してみましょう. また,図書館システムのため,それぞれをshelfに登録してみましょう.

4. 本の一覧を作成する

books.csv に追記し,10冊の本を登録できるようにしましょう. 1行に一冊の本を書いていきましょう.

本日のまとめ

今日,学んだ内容は次の通りです.