2016年度 発展プログラミング演習 第7講 Findプログラム 1日目
本日の内容
1. コマンドライン引数で指定されたディレクトリを受け取る.
1-1. プログラムの大枠を作成する.
まず,プログラムを作成しましょう.Javaの基礎 で勉強したクラス定義の基本形 を元に,次の条件を満たすプログラムを作成しましょう.
- クラス名は,
FileFinder
とします. run
メソッドは,String
型の配列 (String[]
型)を受け取ります.main
メソッドから,FileFinder
の実体を介してrun
メソッドを呼ぶとき,main
メソッドの引数をそのままrun
メソッドに渡してください.-
printArgs
メソッドを作成し,String
型の配列を引数として受け取ってください.返り値はvoid
とします. -
run
メソッドでは,printArgs
を呼び出してください.run
メソッドの引数をそのままprintArgs
に渡してください. -
printArgs
メソッドでは,引数に受け取ったString
型の配列(args
)の要素を順に出力してください. 下に示すコードで出力できます(どちらも同じ結果です.どちらかをメソッド内に書いてください).
void printArgs(String[] args){ for(String arg: args){ System.out.println(arg); } }
void printArgs(String[] args){ for(Integer i = 0; i < args.length; i++){ System.out.println(args[i]); } }
List
に格納されている要素の数は,List
の実体に対して,
size
メソッドを呼び出すことで得られました.
配列の場合は,配列を指す変数に対して,length
変数を参照することで要素数を得られます.この変数は参照のみで代入はできません.
代入しようとするとコンパイルエラーとなります.
完成すれば,実行してみましょう. 何か値を出力させるために,どのように実行すれば良いか考えてみましょう.
1-2. コマンドライン引数を理解する.
コマンドライン引数とは,実行する時にプログラムに与える引数のことです.
例えば,find
プログラムの例として,以下のコマンド列を挙げました.
find . -name HelloWorld.java
この例の場合,find
がコマンド,.
,-name
,
HelloWorld.java
の3つがコマンドライン引数と呼ばれます.
こレラコマンドライン引数をプログラムで扱うには,main
メソッドの引数を使います.
java FileFinder . -name HelloWorld.java
仮に,左に示したコマンドが実行された場合,main
メソッドの引数である
String[]
型の変数にはどのような値が入るでしょうか.
配列の要素それぞれに,コマンドライン引数が文字列として格納されます.
右図のように,スペースで区切った文字列それぞれが,配列の各要素に割り当てられます. 配列の0番目の要素には,最初の引数,配列の1番目の要素は,2番目の引数, のような割り当てが行われます.
では,実際に,先ほど作成したプログラムを使って,コマンドライン引数を指定してみましょう.
1-3. オプションを扱う型を作成する.
このfind
プログラムの仕様では,
様々な条件を元にファイルが検索できることが示されています.
これを元に,コマンドとそのオプションの指定方法を整理しましょう.
$ java FileFinder <DIR> [OPTIONS] DIR: 検索を開始するディレクトリを指定する.必須項目. OPTIONS: -help: ヘルプメッセージを表示して終了する(検索しない). -name <NAME>: ファイル名・ディレクトリ名で検索する.NAMEは必須. -type <d|f>: ファイルの種別で検索する.dはディレクトリ,fはファイルを指す. dもしくはfのどちらかを必ず指定する. このオプション自体を指定しない場合,両方を検索する. -maximum <SIZE>: ファイルサイズが指定のサイズ(SIZE
)より小さいものを検索する. -minimum <SIZE>: ファイルサイズが指定のサイズ(SIZE
)より大きいものを検索する. -grep <STRING>: 内容に指定された文字列(STRING
)が含まれているファイルを検索する.
上のようなコマンドを作成しましょう.オプションは,6つあり,どれもが指定してもしなくても構いません.
しかし,検索の開始ポイントであるDIR
は必須とします.
何もオプションが指定されない場合は,
DIR
以下の全てのファイルが検索結果として表示されるものとします.
さて,どのようなオプションが指定されたのかを表す型を作成しましょう.
名前は,Arguments
として,以下のフィールドを持つものとします.
-
File
型のdirectory
. 検索の開始ポイントとなるディレクトリを意味します.必須項目です.File
型については,後述します. -
Boolean
型のhelp
. 指定されるとヘルプメッセージを表示します. -
String
型のname
. 指定されるとファイル名・ディレクトリ名を元に検索を行います. -
String
型のtype
. 指定されるとファイル・ディレクトリの種別を元に検索を行います. -
Integer
型のmaximum
. 指定されると,ファイルサイズの上限が定まり,この上限より小さなファイルサイズのファイルが検索対象となります. -
Integer
型のminimum
. 指定されると,ファイルサイズの下限が定まり,この下限より大きなファイルサイズのファイルが検索対象となります. -
String
型のgrep
. ファイルの内容を元に検索を行います.
なお,File
型を利用するときは,import java.io.*;
の一文をプログラムの最初に追加してください.
2. 指定されたディレクトリから,ディレクトリを辿っていく.
2-1. Javaでファイル・ディレクトリを扱う.
Javaでファイルやディレクトリを扱う型は2種類存在します.
java.io
パッケージのFile
と
java.nio.file
パッケージのPath
です.
Path
の方が新しく,機能的にも充実していますが,
本講義では,File
を扱います.
理由はFAQの
「なぜ新しいPath
ではなく古いFile
を使うのでしょうか」に記しています.
void run(String[] args){ Arguments arguments = this.parseArguments(args); List<File> resultList = this.find(arguments); this.printResult(resultList); }
File
型は,ファイル,ディレクトリを同じように扱います.
では,次の指示に従い,File
型の実体を作成しましょう.
なお,FileFinder
には,左に示す,run
メソッドを定義しておきましょう.
-
FileFinder
に次の3つのメソッドを定義しましょう.-
parseArguments
メソッド: 引数はString
型の配列, 返り値の型は,Arguments
です. -
find
メソッド:引数は,Arguments
,返り値の型は,List<File>
とします. なお,コンパイルが通るように,List<File>
の実体を作成し, 返すようにしてください. -
printResult
メソッド: 引数はList<File>
,返り値はなし(void
)とします. 受け取ったList<File>
の要素を一つずつ取り出し,System.out.println
で出力してください.
-
-
parseArguments
メソッドでは,次の処理を行ってください.Arguments
型の変数を定義し,new
してください.-
引数で受け取った配列を順に調べて,
Arguments
型の実体のdirectory
フィールドに代入してください. 代入の時には,File
型の実体を作成する必要があります.new File(args[i])
として作成してください (拡張for文の場合など,args[i]
を適切に読み替えてください). - 本来はコマンドライン引数を解析して,適切なフィールドに代入しますが, ここでは検索対象のディレクトリを指定するだけにしておきます. 実際の分析は後日プログラミングを行います.
- 作成した
Arguments
型の実体をreturn
してください.
2-2. ファイル・ディレクトリを区別する.
Javaでは,ファイルも,ディレクトリも,File
型で表現します.
しかし,ディレクトリは0個以上のディレクトリ,もしくはファイルを含みます.
両者を区別する2つの方法が,File
型には用意されています.
それが,isFile
メソッド,もしくは,isDirectory
メソッドです.
File
型の実体が指し示すパスがファイルの場合,isFile
メソッドは
true
,isDirectory
メソッドは,false
を返します.
一方,
File
型の実体が指し示すパスがディレクトリの場合,isFile
メソッドは
false
,isDirectory
メソッドは,true
を返します.
次に示す例を元に解説します.
public class FileOrDir{ void run(){ File file = new File("FileOrDir.java"); System.out.println("FileOrDir.java is file: " + file.isFile()); System.out.println("FileOrDir.java is directory: " + file.isDirectory()); File dir = new File("."); System.out.printf(". is file: %s%n", dir.isFile()); System.out.printf(". is directory: %s%n", dir.isDirectory()); } // mainメソッドは省略. }
上のプログラムをコンパイル,実行して結果を確認しましょう.
まず,3行目で,File
の実体をを作成し,
File
型の変数file
に代入しています.
file
が指し示すパスは,FileOrDir.java
です.
4, 5行目でfile
に対して,ファイルであるか,ディレクトリあるか,
それぞれ判定した結果を出力しています.
一方,6行目で別のFile
型の変数dir
に,別の実体を作成,代入しています.
dir
が指し示すパスはカレントディレクトリです.
7, 8行目でdir
に対して,ファイルであるか,ディレクトリであるか,
それぞれ判定した結果を出力しています.
2-3. ディレクトリを掘り進む.
ディレクトリ内のファイル一覧を取得する.
ここでは,指定されたディレクトリ内のファイル・ディレクトリを確認し,ディレクトリを掘り進んでいきます.
そのために,ディレクトリ内のファイル・ディレクトリ一覧を取得しましょう.
取得するには,File
型の変数に対して,listFiles
メソッドを呼び出します.
File[] files = dir.listFiles();
左の呼び出しでdir
が指し示すディレクトリ以下のファイル一覧を取得できます.
もし,dir
が指し示すパスがディレクトリでなければ
(dir.isDirectory()
がfalse
を返せば),
null
が返されます.
if(dir.isDirectory()){ for(File file: dir.listFiles()){ // file に対する処理を行う. } }
そこで,左のようなプログラムであれば,dir
以下の各ファイルに対して順番に処理が行えるようになります.
なお,listFiles
で返される配列は特定の順序にはなりません.
ディレクトリを横断するメソッドを作成する.
では,FileFinder
に,次の処理を追加しましょう.
-
traverse
メソッドを追加してください.引数はArguments
,List<File>
,File
の3つ,返り値はvoid
です. traverse
メソッドの処理は次の通りとします.-
引数の
List<File>
に引数のFile
を追加してください. -
引数に受け取った
File
がディレクトリの場合,listFiles
メソッドでディレクトリ内の ファイル・ディレクトリそれぞれでtraverse
メソッドを呼び出します. -
引数に受け取った
File
がファイルの場合,何も行いません.
-
引数の
-
find
メソッドで次の処理を追加してください.
さて,上記のプログラムが作成できれば,実行してみましょう.
実行の際,プログラムにコマンドラインから引数を渡すのを忘れずに.
また,traverse
メソッド内から
traverse
メソッドを呼び出しています.
これがどのような結果になるのかを確認しましょう.
このような,あるメソッドからそのメソッド自身を呼び出すことを再帰呼び出しと呼びます.
練習問題
再帰呼び出しの例(階乗を例に)
Javaの基礎の 3. 階乗 のプログラムを元に再帰呼び出しの例を示します. 階乗の漸化式は次の通りです. \[ n! = \begin{cases} 1 & n = 1\\ n \times (n-1)! & n > 1 \end{cases} \]
public class Factorial2{ void run(){ Integer number = 3; Integer factorialValue = this.factorial(number); System.out.printf( "%d! = %d%n", number, factorialValue ); } Integer factorial(Integer value){ if(value == 1){ return 1; } return value * this.factorial(value - 1); } // mainメソッドは省略. }
まず,左のプログラムのfactorial
メソッドに注目してください.
引数のvalue
の値が1
の場合は,返り値として1
を返します.
そして,value
の値が1
以外の場合は,
value
の値を変更して,factorial
を再度,呼び出しています.
あるメソッドから,同一メソッドを呼び出すことを再帰呼び出しと言います.
上に示した漸化式で考えると理解が容易でしょう.
このプログラムがよくわからない場合は,factorial
メソッド内でvalue
がどのような値になっているのかを調べてみると良いでしょう.
また,run
メソッドのnumber
の値を変更して実行しなおしてみましょう.
再帰呼び出しを利用するときは,呼び出しが確実に終わるようにしておく必要があります. このプログラムの例であれば,1より小さい値で呼び出すと, プログラムが終了しません(階乗自体が自然数を対象としていますので,ここでは対応していません). このように,条件によっては,終了しない場合もあり, その可能性を排除する必要があることは念頭に置いておいてください.
1. Fibonacci数列再び
Javaの基礎の 4. Fibonacci数列で書いた Fibonacci数列のプログラムを再帰呼び出しを利用して描き直してみましょう. Fibonacci数列の漸化式は次の通りです. \[ F_i = \begin{cases} 0 & i=0\\ 1 & i=1\\ F_{i-2}+F_{i-1} & i\geq2 \end{cases} \]
- クラス名は,
Fibonacci2
とします. -
クラス定義の基本形を参考に,
main
メソッドからrun
メソッドを呼び出します. -
printFibonacciSeries
メソッドを用意します. 返り値はvoid
,引数はInteger
型の変数max
とします. -
run
メソッドからprintFibonacciSeries
メソッドを呼び出しましょう. 引数は,20を渡してください. -
getFibonacciValue
メソッドを用意します. 返り値はInteger
,引数はInteger
とします. -
printFibonacciSeries
メソッド内で0
からmax
まで繰り返してください. この繰り返しで,Fibonacci数列のi
番目の値を順に取得します. Fibonacci数列のi
番目の値は,getFibonacciValue
メソッドの呼び出しで取得してください. また,繰り返し中に取得した値を画面に出力してください. -
getFibonacciValue
メソッドでは次の処理を行ってください.- 引数の値が
0
の場合は,$F_0$の値である0
を返します. - 引数の値が
1
の場合は,$F_1$の値である1
を返します. -
上記以外の場合は,再帰呼び出しで$F_i$の値を計算して返しましょう.
getFibonacciValue
を適切な引数で呼び,返り値の値を足し合わせれば, $F_i$の値が取得できます.
- 引数の値が
本日のまとめ
今日,学んだ内容は次の通りです.
- コマンドラインオプションの扱い方.
main
メソッドの引数で与えられるString
型の配列.- インデックスの0番目は最初のコマンドライン引数.
- Javaでのファイル・ディレクトリを扱う型,
File
.File
型が指し示すパスがディレクトリかファイルかを判定する方法.File
型が指し示すパスがディレクトリの場合,そのディレクトリに含まれるファイル・ディレクトリの一覧を取得する方法.
- コマンドライン引数を解析する方法.
- 再帰呼び出し.