我们还可以在外层套循环来保证输入的数据不会出现异常,出现异常则会一直在循环中重复进行输入,甚至之后我们可以自定义异常来使得输入的数据符合我们的要求(异常除了系统自己规定的类以外还可以自定义,毕竟异常本质上也还是一种类)。
在出现异常的情况下,代码是按照try-catch-finally的执行顺序来的,finally可以省略,finally是语句必须执行的内容,一般是在执行一段代码中不管是否发生异常都必须执行其中的业务逻辑的应用场景中,比如不管是否发生异常都需要关闭一些文件流,关闭一些资源等等。catch其实也可以省略,不过这样的话程序异常会和之前一样由JVM来处理:中断程序,输出异常信息。
try-catch-finally的具体使用实例
import java.util.Scanner; public class Try { public static void main(String[] args) { while(true) { try { Scanner scanner = new Scanner(System.in); System.out.println("请输入一个整数"); int a = scanner.nextInt(); System.out.println("a的值为:" + a); break; } catch (Exception e) { System.out.println("输入错误,请重新输入"); } } } }
以上就实现了数据输入时限定只能得到整数,输入异常时还不会退出程序的数据输入。我们还可以在其中添加我们想要具体限定的条件,比如大小,位长等等。
多说一句,如果尝试在循环外部创建Scanner对象,然后内部依旧使用nextInt方法,并在调试时第一次输入异常的话,那么之后就不会再有输入的机会了,会陷入一个死循环,无法实现我们想要的功能。有兴趣的朋友可以看一下。
我们在出现异常的时候是直接跳出代码的执行的,通过断点追踪进去Scanner具体的原码后发现,本来退出被调用方法时为了不影响下一次操作需要对一些参数进行重置的操作,现在因为异常直接跳出导致重置操作无法进行,这样就会对下一次数据的读取造成影响。关于上面那个问题,我发现的是Scanner里面的skipped属性(用来判断分隔符是否被跳过了,应该就是字段是否已经被读取完毕)没有被改回false,所以导致之后的循环中scanner对象的每一次使用skipped属性都无法被修改。(因为skipped每次都是true,就代表字段已经被读取完毕了;被读取的字段token是空,无法进行重置参数的操作;needInput初始就是false,而且只有第一次正常的读取中readInput后也被修改为false,不需要更多的输入,跳过了输入阶段;所以又抛出异常throwFor()退出方法的调用到nextInt(),但是退出时又没有修改skipped属性,所以下一次还是true,就这样进入了一个死循环)具体有问题的源码内容如下:
我们可以通过在循环体内创建对象,这样scanner会在每次调用的时候重新创建从而来进行更新;或者我们可以通过使用Integer.paseInt(scanner.next())来改变数据类型,这样就不会让scanner的属性没有重新初始化遭受异常强制中断了,因为这个时候异常时发生在外部的paseInt中的。
当然严谨来说我其实应该还要具体进去看看getCompleteTokenInBuffer方法的,并且我的解释在自己看来都不是很好,但是这对于我这个小垃圾来说源码看着属实是有点头晕了,看着getCompleteTokenInBuffer的注释大概应该可能是这么回事吧,但是又想具体搞清楚,属于是又菜又爱玩了。之后要是实力有所精进会更加具体详实的搞清楚这个问题。诚心欢迎各位大佬朋友、兄弟姐妹指正我说法里面的错误和不足,拜谢!
异常处理的整体机制
知道try-catch-finally的用法之后我们可以来看看异常处理的整体的处理机制了。我们知道,在使用try-catch-finally之前,异常都是由我们的JVM系统来处理的,JVM在异常处理中有着我们Object类一样的顶级地位,Object是所有类的最终父类,JVM也是所有类的异常处理的最终处理单位。具体是像下图中的结构:
我们的main调用了f1方法,f1方法又调用了f2方法。这里用到了我们的throws了,throws就像是摆烂摸鱼,如果f2中没有显式地处理这个异常,那么f2就会把这个异常抛给调用他的f1,这个异常我不想管,不管了。所以在没有使用try处理前,异常会被一层一层的被抛给最高级的JVM系统,并最终由他来处理这个异常。同时我们可以修改异常类型来选择性的抛弃指定的异常。(将Exception修改为具体的异常类型)
try-catch-finally的使用细节
public class Try { public static void main(String[] args) { System.out.println(a.method()); } } class a{ public static int method() { int i = 0; try { return 9 / i; } catch (Exception e) { System.out.println("catch"); return ++i; } finally { System.out.println("finally"); return ++i; } } }
关于这一段代码的执行顺序,我们只需要明确一个概念就可以迎刃而解了——finally中的代码是必须执行的业务逻辑。在这个语句中try中发生了异常被捕获到了catch中,执行完之后到return ++i;当然是先执行完 ++i 之后发现finally没有被执行,所以就在return之前先去执行finally 的语句,并最终在finally中return。所以最后的结果是2;
如果将catch中的 ++i 换成 i - 8,我们的过程其实没有变化,只不过 i - 8 和 ++i有很大的区别,后者是可以作为一个独立的语句来执行的,而前者没有接收者,所以return i - 8;这里其实在底层先用一个临时变量temp储存了 i - 8 的值,然后再去执行finally的代码。如果finally中的代码没有出口return,就算finally中修改了 i ,最终还是会返回temp的值;如果有,那么会在finally中直接return。
public class Try { public static void main(String[] args) { System.out.println(a.method()); } } class a{ public static int method() { int i = 0; try { return 9 / i; } catch (Exception e) { System.out.println("catch"); return i - 8; } finally { System.out.println("finally"); i++; // return ++i; //返回2 } } }
结果如下:
catch
finally
-8