2016年度 発展プログラミング演習 第4講 図書館システム 2日目
目次
4. 図書館システムに本を追加する.
4-1. 変数の有効範囲.
変数shelf
の宣言場所をメソッド
の中ではなく,クラス宣言
の中(メソッド宣言の外側)で宣言しましょう.
変数の宣言場所によって,変数が利用できる範囲が異なります.一般的に,
変数はその変数が宣言された一番内側の波括弧内で有効になります.
波括弧の外側では変数が参照できません.この変数の有効範囲を
スコープと呼びます.
スコープの有効範囲を下図の例を用いて説明します.
この例では,一番内側の波括弧のfor
で,str
が宣言されています.
str
の宣言は,for
の内部で行われているため,
str
の有効範囲は,for
の波括弧内です.
for
の外側でstr
を参照しようとすると,
「シンボルが見つかりません」というコンパイルエラーが起こります.
また,variable2
はmethod1
内で宣言されているため,
有効範囲は,method1
内のみに限定されます.
method1
外で参照しようとすると,先ほどと同じく
「シンボルが見つかりません」というエラーでコンパイルに失敗します.
これらの変数は,ローカル変数と呼ばれます.
一方のlist1
は,Library
内のあらゆるメソッドで利用可能です.
実際に,str
を利用しているfor
内でも参照されています.
また,メソッド宣言の外側で宣言された変数(このlist1
のような場所で宣言された変数)は,
フィールドと呼ばれます.フィールドは
実体が代入された変数を経由して参照できます.Book
を思い出してください.
Book
で宣言された title
, authors
,
publisher
, publishYear
は,Book
の実体を代入した変数を介して代入しています.
4-2. shelf
の宣言場所を変更する.
さて,話を元に戻します.変数shelf
は現在,メソッド内で宣言されています.
このままだと,メソッドの外側(他のメソッド)から参照できません.
この変数は,本が格納されている変数ですので,様々なメソッドから参照される必要があります.
そこで,変数shelf
を,フィールドに変更して,クラス内のメソッドから参照できるようにします.
public class Library { List<Book> shelf = new ArrayList<Book>(); void run(){ // .... } void printBook(Book book){ // .... } void addBooks(List<Book> shelf){ // .... } Book createBook(String title, String authors, String publisher, Integer publishYear){ // .... } void printWelcomeMessage(){ System.out.println("ようこそ図書館システムへ"); } }
5. 図書館システムが持つ本の一覧を表示する.
5-1. 本の要素での繰り返し.
では,本の一覧を画面に表示してみましょう.
表示するためには,格納している本にアクセスしなければいけません.
本はshelf
という名前のList
型の変数に格納されていましたね.
List
型に格納されている要素を一つずつ取り出していきましょう.
そのためには,C言語のときと同じく,繰り返しを利用します.
原理的には,C言語と同じく,0番目の要素から,参照するインデックスを1ずつ増やして
List
の要素を順に取り出します.
しかし,配列やList
など,型によって要素の長さを取得する方法が少しずつ違っています.
配列であれば,args.length
ですし,List
であれば,
list.size()
です.
型を意識しないと繰り返しを書けないよりは,どんな型であっても
複数の要素を持つ型であれば,同じように各要素を取得できる方が望ましいのは当然です.
5-2. 拡張for文.
Javaはそのような要望に応えるため,拡張for文というものが導入されています(Java SE 5から).
拡張for文は次のように,for(型 変数名: コンテナ変数)
のように書くと,
コンテナ変数内に格納されている要素を順に取り出せるようになります.
コンテナ変数とはList
型や配列のような複数の要素を持てる変数を指します.
for(Book book: shelf){ // some operation... }
この拡張for文のコンテナ変数として扱えるかはBoolean flag = 変数 instanceof Iterable
で確認できます.しかし,よく使う型はコンテナ変数であるか否かは覚えてしまいましょう.
今のところ,配列やList
, Set
がコンテナ変数として扱えることを覚えておけば良いでしょう.
では,shelf
というコンテナ変数に格納されているBook
型の変数を一つずつ取り出し,情報を標準出力に出力するメソッド,list
を作成しましょう.
また,一つのBook
型変数を受け取り,
情報を標準出力に出力するメソッドprintBook
を作成し,
list
メソッド内から呼び出してみましょう.
class Library { // .... void list() { for(Book book: shelf) { this.printBook(book); } } void printBook(Book book){ System.out.printf( "%s,%s,%s,%d%n", book.title, book.authors, book.publisher, book.publishYear ); } }
6. 図書館システムの本のリストから本を検索する.
6-1. タイトルから本を検索する.
では,特定のタイトルを持つ本を検索できるようにしましょう.Library
の
run
メソッドに検索するプログラムを書いてみましょう.
検索するメソッドの名前はfind
で,1つのString
型の変数を受け取り,
Book
型の変数を返します.
void run(){ // addBooksメソッドは前回の練習問題で行った内容です. this.addBooks(shelf); // ここに,findメソッドを使って,本を検索する処理を追記してください. // 本が見つかれば,printBookを使って画面に出力しましょう. }
次に検索するfind
メソッドを作成しましょう.
run
メソッドの中で呼び出したように,
find
メソッドは,返り値の型がBook
,引数がString
型です.
メソッドを宣言しましょう.
public class Library { // .... // 以下の形式で find メソッドを作成する. // 返り値の型 find(引数の型 引数名){ // } }
では,find
メソッドの中身を作成しましょう.
まず,本を検索するためには,shelf
の要素を順に調べていく必要があります.
shelf
の要素を繰り返しで調べていきましょう.
繰り返しは,「5. 図書館システムが持つ本の一覧を表示する」
と同じ方法が利用できます.
for
の内容は,要素となるBook
型の変数が持つタイトルと引数で与えられたタイトルが一致するかを確認すれば良いですね.
6-2. 変数の比較.
型が違えば比較できません.比較するときは型に注意しましょう.
なお,Javaでは文字列の比較は,一般的には,==
では行えません.
Javaでは実際の値はメモリ上にあり,変数はメモリ上の値を参照しています(C言語で言う所のポインタ変数です).
==
による比較は,参照先が同じであるかを判定しており,参照先の値が同じか否かは判定しません.
次の例を見てみましょう.プログラムの枠内のs1
, s2
,
s3
, s4
はString
型の変数であると思ってください.
メモリの枠内には,具体的な値があり,変数がどの値を指しているかを表しています.
次のようなプログラムコードであると思ってください.
public class StringTest{ public void run(){ String s1 = "string1"; String s2 = s1; String s3 = "string2"; String s4 = "string1"; } }
この例では,s1
とs2
はメモリ上の同じ場所を指していますが,
s3
とs4
は異なる場所を指しています.
この場合,s1 == s2
はtrue
となりますが,
s1 == s4
やs2 == s4
はfalse
となります.
文字列の中身が同じであったとしても,メモリ上の異なる場所を指し示しているためです.
では,文字列の中身が一致しているかを調べるにはどうすれば良いでしょうか.
それには,equals
メソッドを利用します.
上記の例では,s1.equals(s2)
や,s2.equals(s4)
が
true
になります.異なる場所を指し示している場合でも,
文字列の中身が一致しているかで判定するためです.文字列の内容自体が異なるs3
の場合,s1.equals(s3)
やs3.equals(s4)
は
false
となります.
しかし,どこも参照していない変数があればどうなるでしょう.どこも参照していないということはすなわち,
null
であることです.null
の変数に対して比較しようとすると
NullPointerException
というエラーが発生します.
そのため,比較するときには,null
でないことの確認が必須です.
例えば,次のようなプログラムです.
if(s1 != null && s1.equals(s3)){ // 一致した場合の処理を書く. }
ここで,s1
とs3
の両方がnull
であった場合など,単に文字列の内容自体を比較したいのに,4通りも確認するのは面倒です.
そこで,近年のJavaでは,Objects.equals(s1, s3)
と比較すれば良いようになっています.なお,この比較を行う場合は,プログラムの最初に
import java.util.*;
という一文が必要です.
では,本の検索に戻りましょう.引数で与えられた文字列と,Book
型の変数が持つtitle
が一致するかを判定し,
一致した場合,その要素を返しましょう.一方,全ての要素を調べても見つからなかった場合は,
null
を返すようにしましょう.
プログラムが書ければ,検索条件を変えて何度か検索してみましょう.
public class Library { // .... Book find(String title){ for(Book book: shelf){ if(Objects.equals(title, book.title)) { return book; } } return null; } }
6-3. 本を絞り込んで検索する.
次に,Book
型の変数が持つ4つの変数,
title
,authors
,publisher
,
publishYear
のどの情報でも絞り込んで調べられるようにしましょう.
つまり,publisher
とpublishYear
で絞り込んで,
「とある出版社のこの年に発行された本一覧が欲しい」という要望に対応します.
絞り込みに不要な情報はnull
で渡されるものとします.
このように与えた条件の全てが一致するように検索する方法をAND検索と呼びます.
この検索では,複数の結果が返される可能性があります.タイトルから本を検索する
では,タイトルから本が一意に決まります
(現実世界では一意に定まらない場合もありますが,この課題の範囲では定まるものとします).
一方,ここでは,タイトルが指定されない場合もあり,検索結果が複数になる場合があります.
そのため,返り値はBook
では表せないことになります.
では,複数のBook
を返すにはどのような型を使えば良いのでしょうか.
複数のBook
型の変数を格納できるものはどんなものがあったでしょうか.
List
型が利用できますね.
どのように検索すれば良いでしょうか.ここでは,isMatch
メソッドを定義し,検査対象の本と検索条件が引数として渡されます.
そして,その本が検索に一致すればtrue
を返し,
一致しなければfalse
が返されるものとします.
ですから,逆に言えば,null
でない条件が一つでも一致しなければ
false
を返すようにすれば実現できます.では,実装してみましょう.
4つの変数が条件として指定される可能性があります.
条件が指定されるということは,null
でない値が入っていることを意味します.
そして,null
でない条件全てが満たされた場合にisMatch
メソッドは
true
を返します.
逆に考えれば,条件が一つでも一致しなければ,全体がfalse
となるわけです.
ですから,title
が指定された時に,本のタイトルと一致しない場合,
isMatch
はfalse
となります.
他の条件についても同様に考え,全ての条件で判定してみましょう.
プログラムが書ければ,検索条件として与える値を変えて,何度か試してみましょう.
public class Library { // .... List<Book> findAnd(String title, String authors, String publisher, Integer publishYear){ List<Book> result = new ArrayList<Book>(); for(Book book: shelf){ if(this.isMatch(book, title, authors, publisher, publishYear)) { result.add(book); } } return result; } Boolean isMatch(Book book, String title, String authors, String publisher, Integer publishYear){ // 条件は,title, authors, publisher, publishYearの4種類. // null以外が指定された場合,検索条件として指定されたものとする. // ここに,検索条件として与えられた条件全てを満たすか否かを判定する処理を書く. // 検索条件を満たしていれば,trueを返し,満たしていなければfalseを返す. } }
7. 図書館システムから本を削除する.
7-1. remove
メソッド
ここでは,格納された本のうち,一部の本を登録から削除する機能を追加します.
List
型に含まれている要素を削除する方法です.
要素を追加するのは,List
型の変数に対して,
add
を呼び出せば実現できました.
削除も同じように,List
型の変数に対して,
remove
を呼び出せば実現できます.
add
では,追加したい要素を引数に渡せば,その値が追加されました.
remove
には何を渡せば良いでしょうか.
7-2. remove
メソッドの引数
実は,remove
には,要素そのもの,もしくは,インデックスの2種類の値が渡せます.
どちらを利用しても良いので,時と場合によって使い分けましょう.
では,Library
にremove
メソッドを追加しましょう.
ここでは,remove
メソッドの引数には削除したい本を渡しましょう.
つまり,Book
型の変数を渡すようにします.
削除する本は,find
メソッドで見つけてきた本を指定します.
public class Library { // .... void run(){ // ... this.addBooks(shelf); // findで本を見つける処理が書かれているはずです. // ここで,findで見つかった本を削除するよう,removeメソッドを呼び出しましょう. // 最後に,本当に削除されたか,本棚の内容を出力してみましょう. } void remove(Book book){ // shelfから引数で与えられた本を削除する処理を書きます. } }
では,実際に上記のremove
メソッドの中身を実装していきましょう.
Book
型の変数book
はfind
で見つかった本を
shelf
に格納されているはずです.
練習問題
1. 検索して表示する
本の一覧を表示するでは,この図書館システムが持つ全ての本が出力されました.
この練習問題では,findAnd
の結果として得られたList
に含まれるBook
の情報を出力するメソッド,findAndPrint
を作成しましょう.
findAndPrint
メソッドは,引数として,本のタイトル,著者,出版社,出版年を受け取ります.
返り値の型は,検索結果を返すため,List<Book>
としましょう.
メソッドの中身では,引数に受け取った情報をそのままfindAnd
に渡して下さい.
そして,findAnd
の返り値であるList<Book>
を変数に代入しましょう.
そして,for
を使って,得られた検索結果に含まれるBook
を
printBook
を使い,一つずつ画面に出力しましょう.
以上のプログラムがかければ,run
メソッドの最後に,
複数の検索結果になるような検索条件を与えて,findAndPrint
メソッドを呼び出しましょう.
想定通りの動作になっているかを確認しましょう.
2. OR検索を実装する
6.2 本を絞り込んで検索するで,AND検索を行いました.
この練習問題では,OR検索を実現するメソッドを追加して,動作を確認してみましょう.
OR検索を実現するメソッド,findOr
を作成し,動作を確認しましょう.
findOr
の引数は,findAnd
と同じとします.
isMatch
メソッドは利用できません.同じようなメソッドも作成して
findOr
を実現してください.
3. 本を削除する?
図書館システムから本を削除するでは,find
で見つけた本をList
型のremove
メソッドに渡すことで削除を実現していました.
では,削除したい本の情報がわかっているものとして,
新たに本をnew
することで,削除するようにしてみましょう.
つまり,次のメソッドを実行してみて,実行結果がどうなるか確認してみましょう.
また,その理由も考えてみましょう.
public class Library { // ... void remove2(String title, String author, String publisher, Integer publishYear){ Book book = createBook(title, author, publisher, publishYear); shelf.remove(book); } // ... }
本日のまとめ
- 変数のスコープ
List
の要素の繰り返し- 拡張for文
- 文字列の比較
List
からの要素の削除