java scannerの使い方を勘違いしていた話(メモ)


目次

  • 実行環境
  • 発生した問題、エラーメッセージ
  • 勘違いしていた内容
  • 解決後のコード
  • コードの解説
  • まとめ

実行環境

Java version : 12.0.1
コード記述 : テキストエディタ(Atom)
使用OS : windows(文字コードの問題なだけでMacでも作動しました)
問題の経緯 : Java初級者が学習した内容で何かをつくれないかと思い、自分でコードを組んでみたところ、エラーが発生

発生した問題、エラーメッセージ

発声した問題としては、Scannerクラスを用いて、特定の入力キー以外が入力された場合(今回の場合は1か2の入力を求めていました)、再入力するだけのプログラムです。
まず最初に機能だけで考えて組んでみた。

errorFile.java
Scanner sc = new Scanner().nextInt();
while(sc){
  switch(sc){
    case 1:
    // 処理1
    break;
    case 2:
    // 処理2
    break;
    default:
    // 処理3
    break;
  }
}

お分かりかと思いますが、当然作動しません。この時点でいろいろと、本当にいろいろと理解を間違えてしまっていることがわかります。
とりあえずよくわからなくても動かしてみて、間違っていたらその都度調べたり、エラー解決した方が楽しい!などと思って浅い理解で組んでしまったためのこの結果です。

勘違いしていた内容

この時点で、自分が考えていた原因について説明します。単純に入力エラーの解決方法だけを見たいという方はコードの解説までとんでください。ここは蛇足になるかと思います。

この時点で勘違いをしていた内容については主に以下の内容
〇 while()の()内をループ条件だと思っていた
→実際はbooleanで()内の条件がfalseになるまでループ

〇 ScannerクラスをScanner 変数名 = new Scanner().nextInt()で一つの形だと思っていた
→実際はインスタンス化と入力した要素の取得は別々の機能
つまり、new Scanner()と.nextInt()はわけることが可能
そもそもこの文だとScannerでint型を取得することになり、余計なエラーが・・・

〇 例外処理を理解していない
→このままでは数字入力しか対応しておらず、文字列で入力された場合、例外が発生し、うまく実行ができない
一応解決後には例外処理の記入もしてありますが、正直例外処理に関してはまだまだ試行不足

解決後のコード

ここまで勘違いしていた内容を、偉大なる先人の方々の質問や資料、書籍などを用いて解決したあとの、比較的きれいになったコードがこちらです。

SolutionFile.java
int all = 0;
  while(all == 0){
    try{
      Scanner sc = new Scanner(System.in);
      switch(sc.nextInt()){
        case 1:
          // 処理1
          all +=1;
          break;
        case 2:
          // 処理2
          all +=1;
          break;
        default:
          // 処理3
          break;
      }
    }catch(Exception e){
      // 処理4
    } 
  }

いかがでしょうか?少なくとも最低限自分の意図に沿った動作はするようになりました。可読性やどこから出てきたint all、catchの例外処理などはちゃんとわかってる?といわれるようなコードを書いていますが、動けばOK!とするならこれで納得します。しましょう。

コードの解説

ここでは一応完成したコード(SolutionFile.java)をかみ砕いた解説になります。

まずはそれぞれのスコープ範囲を一覧すると
(ここでは範囲の開始から終わりまでを挟んで表記しています)

while // 1
  try // 2
    Scanner // 3
    switch // 4
      case 1:
      case 2:
      default:
    switch
  try 
  catch
  catch
while

こんな感じ
while > try == catch > switch > case == default
ですね

// 1
whileのを抜けない限り、try~catchまでの処理が延々と繰り返されることになります。
今回の条件では最初に初期値0のallを宣言して「allが0以外になるまで」を条件にしています。
つまりallの値が加減算などされるなどすればループが終了するんですね。

// 2
次にエラーが起きる可能性のある処理ですね。エラーが発生する可能性と、エラーが発生しない通常処理したい内容をtryで囲んで例外をcatchで囲んでいます。そして下記で解説していますが、catchはwhileの中でのループ分で、ループを抜けることをしていないため、catch内の処理が実行されたあとは、またwhileの初めに処理が戻ります。

ちなみに、
本来エラーが起きるというだけに絞ればScannerの入力だけになると思うんですが、試しにswitchだけを移動してみましたが、エラーが発生しました。
以下はswitchを移動してみたコード

Changefile.java
int all = 0;
  while(all == 0){
    try{
      Scanner sc = new Scanner(System.in);
    }catch(Exception e){
      // 処理4
    } 
      switch(sc.nextInt()){
        case 1:
          // 処理1
          all +=1;
          break;
        case 2:
          // 処理2
          all +=1;
          break;
        default:
          // 処理3
          continue;
      }
  }

エラー: シンボルを見つけられません
switch(sc.nextInt()){
               ^
  シンボル:   変数 sc

scの要素の取得をswitch内でしていることが理由だと考えられます。

// 3
ここでは入力だけしてもらう形ですね。今回のコードでは欄外に記述していますが、
import java.util.Scanner;
を最初に宣言しているため、この記述になっています。
何度も宣言するわけではないので、import宣言せずにここだけ
Scanner sc = new java.util.Scanner(System.in);
としても問題なさそうですね。

// 4
ここでは入力してもらった内容を取得して、その取得した内容によって処理を変えています。
if文でも条件式は可能ですが、自分での読みやすさと、コードの意味の読みやすさからswitch文を選択しています。
nextInt()
のため、数字入力を求めているわけですね。
case 1:
case 2:
では、それぞれの処理に加えてwhileのループを抜けるために
all += 1
とすることでallの値を1加算しています。
結果的に all = 1 となり、ループの条件である「allが0以外になるまで」という条件を満たすことによって条件がfalseになって、ループを抜けます。

そしてdefault、つまり1か2以外の数字が入力された場合はallの値を変えないことでループ条件をfalseにせずに処理をwhileの最初まで戻しています。

つまり1か2が入力されるまで何度も入力してもらうよ!別の数字か文字列を入力すると延々ループするよ!という処理なわけです。

まとめ

自分が実装したかった機能は
 1か2以外が入力された時、もう一度入力を求める
という機能です。

勘違いしていた点は
 whileの条件、Scannerの構成、例外処理

そして勘違いを正して解決しました、というのが今回の記事の内容です。
しかし動作するようになったものの、まだまだ無駄が多いコードですので、無駄を削る、例外処理の試行、他人から読みやすいコードなのか、という問題点がありますので、今後も試行を重ねていきたいと思います。以上、ありがとうございました。