|
C 言語スタイルのフォーマット Scanner |
||||
|
||||
scanf は printf に比べれば使用頻度は低いと思いますし、玄人ほど使わないとは思います。でも、それなりに便利なのは確かです。 Java で scanf のようなことをやろうとすれば DataInputStream クラスや StreamTokenizer を使用するのが今までのやり方でした。それはそれでもいいのですが、もうちょっと使いやすいといいですね。 特に正規表現といっしょに使えたりするともっといいのではないでしょうか。 そこで、Tiger で登場したのが java.util.Scanner クラスです。 大量の入力をさばくような用途には向かないかもしれませんが、ちょっとした入力には重宝します。
|
|
|||||||||||||||||
Scanner クラスはデフォルトでは StreamTokenizer クラスでデリミタを " " (スペースもしくはタブ) にしたときと同じような使い方になります。
Scanner クラスの生成にはいろいろあるのですが、ここでは一番単純な文字列を引数にして生成します。
hasNext メソッドでトークンがあるか調べて、next メソッドで取りだすという手順は StreamTokenizer とメソッド名こそ違いますが、ほぼ同じです (Iterator インタフェースのメソッドですね)。 実行してみると
ここでは Scanner オブジェクトの生成に、引数を文字列にしました。その他には InputStream クラス、File クラスまた ReadableByteChannel も使えるのがうれしいところです。 また、Formatter クラスでは Appendable インタフェースでしたが、Scanner クラスは Readable インタフェースを引数にしようできます。Reader クラスが Readable インタフェースをインプリメントしているので、XXXReader であればすべて Scanner クラスのコンストラクタの引数に使用することが可能です。 next メソッドの戻り値は文字列なのですが、その他に nextInf メソッドなどプリミティブ型に対応したメソッドが用意されています。同様に hasNext メソッド以外に、hasNextInt メソッドなども用意されています。 これらのメソッドを使用すれば文字列から数値への変換が必要なくなるので、便利です。まずは試してみましょう。
スキャンする文字列は One little, 2 little, 3 little Indian です。わざと数字と文字を混ぜて書いてみました。
これで 2 と 3 が出力されるはずです。やってみましょう。
あれっ、何も出力されていません。おかしいですね。 実をいうと、hasNextInt メソッドも nextInt メソッドも文字列のトークンを飛ばしていくわけではないのです。はじめのトークンが One なので hasNextInt メソッドは false になります。ですからそのままループを抜けてしまうのでした。 そうと分かればダミーの hasNext メソッドや next メソッドを使用してやればいいことになります。
while ループの中に if 文で hasNextInt メソッド、hasNext メソッドを書いてみました。hasNext メソッドの後に next メソッドをコールしないと、次のトークンが読み込まれないので永久ループになってしまいます。この点を注意してください。
hasNextInt メソッドを hasNext メソッドより先に書いたのは hasNext メソッドはトークンがあれば数値であろうと文字であろうとお構いなしにあれば true を返してしまうからです。 これで期待通りに動くでしょう。早速実行です。
どうやら意図したとおりに実行できたようです。 ここでは hasNextInt メソッドを使用しましたが、hasNext メソッドでも同じことができます。それには hasNext メソッドの引数に正規表現を使う方法です。この場合、切り出されたトークンが指定された正規表現とマッチするかどうかが hasNext メソッドの戻り値になります。文字列全体とのマッチではないです。
ScannerTest2_1 では hasNextInt メソッドを使用していた部分に hasNext("\\d+") を使用しています。引数の "\\d+" は 1 つ以上の数字列ということをあらわしています。切り出されたトークンが数字だけで構成されていた場合 (すなわち正の整数だったら)、hasNext メソッドの戻り値が true になります。
実行結果は一緒なので省略します。ここでは hasNext の引数に文字列を使用しましたが java.util.Pattern クラスも使用できます。というかここで使った例では Pattern クラスの方がふさわしいのですが、簡単化のために文字列のままにしています。 当たり前のことですが、この "\\d+" は正の整数だけとマッチするので負の整数や、浮動小数点数だとマッチしません。あしからず。
|
|
|||||
前のサンプはダミーの読み込みをしており、ちょっとかっこ悪いと思いませんか。 もうちょっとスマートに、ダミーの読み込みをしない方法を考えてみましょう。 まず考えられるのが、デリミタを変えてみようということです。StringTokenizer クラスや StreamTokenizer クラスは複数の文字をデリミタにすることはできますが、正規表現であらわされたデリミタは使えません。 ところが Scanner クラスでは正規表現のデリミタを使用することができるのです。
デリミタを変更するには useDelimitar メソッドを使用します。引数は文字列もしくは Pattern クラスです。
このサンプルでデリミタとして使用した \D は数字以外の文字をあらわすので、数字以外の文字が 1 つ以上つながっているものをデリミタとしています。ようするに数字以外の文字のつながりがデリミタになります。そのため、小数点もデリミタになってしまうので、浮動小数点数は 2 つのトークンとみなされてしまいます。 複数の文字をデリミタに使用することも可能です。たとえばスペースとタブとカンマをデリミタに使いたいのであれば、"[ \t,]" と書くことができます。
|
|
|||||
今までの書き方は scanf とはちょっと違っていましたが、最後に scanf 的な書き方をやってみましょう。これも正規表現を使用する方法なのですが、行全体と正規表現をマッチさせる方法です。1 行のマッチが findInLine メソッド、複数行の場合が findWithinHorizon メソッドです。
このサンプルでは findInLine メソッドを使用します。
findInLine メソッドの引数がマッチのために使用される正規表現です。マッチするとときにデリミタは無視されます。findInLine メソッドの戻り値はマッチした文字列なので、このままでは text とまったく同じものが得られます。 そこで、グループを取りだすために match メソッドを使用して正規表現のマッチの結果を取得します。この戻り値は java.util.regex.MatchResult インタフェースのオブジェクトなのですが、このインタフェースも Tiger で導入されたものです。 java.util.regex.MatchResult クラスの インデックスを引数とした group メソッドを使用すればマッチしたグループを取得することができます。しかし、あくまでも正規表現のグループなので戻り値が文字列になってしまうところが残念なところです。 また、group メソッドの引数を 0 にしてしまうと引数なしの group メソッドと同じになってしまいます。つまりここでは文全体が戻ってきてしまうので、引数は 1 からにする必要があります。
|
|
||||
Scanner クラスはいうなれば StreamTokenizer + DataInputStream + 正規表現 のようなクラスですね。正規表現が使えるのであれば、強力な武器になりそうです。 ただし、scanf と同じことがいえるのですが、数字でない入力を nextInt メソッドで読み込んでしまったら InputMissmatchException 例外が発生してしまいます。この例外は RuntimeException なので、コンパイル時には間違いを発見することができません。 したがって、使い方を注意しないとコードの品質を落としかねないのです。便利なクラスには落とし穴があるので、気をつけましょう。
今回使用したサンプルはここからダウンロードできます。
(Jun. 2004) |
|