java初学者问题 异常捕获

    ArrayList<Integer> al = new ArrayList<>();
        Scanner scanner = new Scanner(System.in);
        //死循环 直到输入0结束
        while (true) {
            try {
                System.out.println("请输入一个数 输入0表示结束");           
                //如果在此输入一个非int类型的数据  则陷入一个死循环  为何不能重新给i赋值
                int  i = scanner.nextInt();
                if (i != 0) {
                    al.add(i);
                } else
                    break;
            } catch (Exception e) {
                // 捕获非int类型的异常
                System.out.println("错误 请输入整数");
            }
        }

public static void main(String[] args) {
ArrayList al = new ArrayList<>();
Scanner scanner = new Scanner(System.in);
while (true) {
try {
System.out.println("请输入一个数 输入0表示结束");
String i = scanner.next();
if (isNum(i)) {
if (!"0".equals(i)) {
al.add(Integer.parseInt(i));
} else {
break;
}
} else {
// break;跳出循环
System.out.println("请重新输入一个数字");
}
} catch (Exception e) {
System.out.println("错误 请输入整数");
}
}
System.out.println("循环结束");
for (int i = 0; i < al.size(); i++) {
System.out.print(al.get(i) + "\t");
}
}

public static boolean isNum(String str) {
    return str.matches("^[-+]?(([0-9]+)([.]([0-9]+))?|([.]([0-9]+))?)$");// 判断是否是数字
}你可以这样写

同样很好奇,通过DEBUG追踪了下。
Scanner最后是调用 public String next(Pattern pattern) 方法获取一个String对象,通过Pattern表示要获取对象的类型。
代码如下

   public String next(Pattern pattern) {
        ensureOpen();
        if (pattern == null)
            throw new NullPointerException();

        // Did we already find this pattern?
        if (hasNextPattern == pattern)
            return getCachedResult();
        clearCaches();

        // Search for the pattern
        while (true) {
            String token = getCompleteTokenInBuffer(pattern);//尝试获取符合类型的结果
            if (token != null) { 
                matchValid = true;
                skipped = false;
                return token;
            }
            if (needInput) //是否需要input数据
                readInput();
            else
                throwFor();
        }
    }

问题就出在
getCompleteTokenInBuffer(pattern)

if (needInput)
readInput();
首先初次尝试失败后,会判断是否需要input数据,此时你还没有输入数据,因此通过readInput();得到数据的数据,并把needInput设置为了false

然后再次调用
getCompleteTokenInBuffer(pattern);
然而调试里面的代码发现,当pattern指定的类型与你实际输入的类型不匹配时,并不会讲needInput设置为false,
查看该方法的说明:

 /*
     * Returns a "complete token" that matches the specified pattern
     *
     * A token is complete if surrounded by delims; a partial token
     * is prefixed by delims but not postfixed by them
     *
     * The position is advanced to the end of that complete token
     *
     * Pattern == null means accept any token at all
     *
     * Triple return:
     * 1. valid string means it was found
     * 2. null with needInput=false means we won't ever find it
     * 3. null with needInput=true means try again after readInput (意味着这是readInput后的又一次尝试)
     */
    private String getCompleteTokenInBuffer(Pattern pattern) {
            /*
                ...
            */
        }

因此,当你循环调用next()方法时,只会不停的尝试将你上次输入的数据中获取你想要的int类型数据。

最后,当知道原因后,就可以通过各种各样的方法解决这问题了。

ArrayList al = new ArrayList();
Scanner scanner = new Scanner(System.in);
//死循环 直到输入0结束
while (true) {
try {
System.out.println("请输入一个数 输入0表示结束");
//如果在此输入一个非int类型的数据 则陷入一个死循环 为何不能重新给i赋值
int i = scanner.nextInt();
if (i != 0) {
al.add(i);
} else
break;
} catch (Exception e) {
// 捕获非int类型的异常
System.out.println("错误 请输入整数");
scanner = new Scanner(System.in);
}
}



ArrayList al = new ArrayList();
Scanner scanner = new Scanner(System.in);
//死循环 直到输入0结束
while (true) {
try {
System.out.println("请输入一个数 输入0表示结束");
//如果在此输入一个非int类型的数据 则陷入一个死循环 为何不能重新给i赋值
int i = scanner.nextInt();
if (i != 0) {
al.add(i);
} else
break;
} catch (Exception e) {
// 捕获非int类型的异常
System.out.println("错误 请输入整数");
scanner = new Scanner(System.in);
}
}



知道为什么嘛?看看源码,就知道了。
public int nextInt(int radix) {
// Check cached result
if ((typeCache != null) && (typeCache instanceof Integer)
&& this.radix == radix) {
int val = ((Integer)typeCache).intValue();
useTypeCache();
return val;
}
setRadix(radix);
clearCaches();
// Search for next int
try {
String s = next(integerPattern());
if (matcher.group(SIMPLE_GROUP_INDEX) == null)
s = processIntegerToken(s);
return Integer.parseInt(s, radix);
} catch (NumberFormatException nfe) {
position = matcher.start(); // don't skip bad token
throw new InputMismatchException(nfe.getMessage());
}
}

    看到注释了嘛?position = matcher.start(); // don't skip bad token

答非所问。。问题应该出在scanner.nextInt()身上,这方法读取当前输入后光标并不会换行,所以它总是把你输入的非整数内容或者是“请输入一个数 输入0表示结束”这句话当成你的输入,(具体哪个你自己试试,完了别忘分享一下哈)。所以循环一直不断。

nextInt()方法会读取下一个int型标志的token.但是焦点不会移动到下一行,仍然处在这一行上。
所以你的异常分支处理的有问题,仅仅是打印异常信息,却没有将当前控制台输入的非int数据清除,导致下一轮循环nextInt仍读取到当前行的字符串输入。
修正下异常分支,迫使输入控制台光标下移动一行,用nextLine();就可以让下一轮重新读入了。

 } catch (Exception e) {
                // 捕获非int类型的异常
                System.out.println("错误 请输入整数");
                scanner.nextLine();
            }