Java通识基础27(异常处理)
存在意义
有了异常处理的机制——我们的程序会更加健壮!
也有更高的容错率
程序在运行时会面临:
物理环境的改变
用户的误操作
恶意操作
……
面临以上情况的处理——异常处理
传统的错误处理机制
if(错误1){}else if(错误2){}......
处理方式存在的问题
- 所有的异常情况无法被穷举,总有漏网之鱼
- 流程处理混杂(业务处理逻辑的和异常处理逻辑混在一起),程序可读性差
异常处理流程
if(任何错误){//异常处理语句}else{//正常处理}
改进后
try{//正常处理逻辑}catch(异常){//处理错误}
举例:
import java.util.Scanner;public class Demo1 {public static void main(String[] args) {Scanner sc=new Scanner(System.in);while (sc.hasNextLine()){try {String line= sc.nextLine();String[] strArr=line.split(",");Integer x=Integer.parseInt(strArr[0]);Integer y=Integer.parseInt(strArr[1]);System.out.println("x="+x);System.out.println("y="+y);}catch (Exception ex){System.out.println(ex);System.out.println("写的时候动动脑子!");}}}}/*xjava.lang.NumberFormatException: For input string: "x"写的时候动动脑子!23java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1写的时候动动脑子!213xxasjava.lang.NumberFormatException: For input string: "213xxas"写的时候动动脑子!1,2,3x=1y=2*/
此时将正常的业务处理代码放在try块里面,将异常处理逻辑放在catch块里面
如果遇到异常
- Java会自动将该异常封装称为
Exception对象 - 该对象会提交给Java的运行环境——这个过程叫做(抛出)
- Java运行环境收到该异常之后会寻找匹配的
catch块——这个过程叫做(捕获)
- Java会自动将该异常封装称为
如果找不到异常,程序就会报错结束
- 刚刚例子里见面的捕捉的是所有的
Exception(所有异常的父类),如果捕捉异常做出相应的改变,改变后捕捉的异常与抛出的异常不匹配,程序又会报错 - 所以,咱们可以动态的
catch不同的异常,在相应的处理快中编写相应的处理代码
- 刚刚例子里见面的捕捉的是所有的
import java.util.Scanner;public class Demo1 {public static void main(String[] args) {Scanner sc=new Scanner(System.in);while (sc.hasNextLine()){try {String line= sc.nextLine();String[] strArr=line.split(",");Integer x=Integer.parseInt(strArr[0]);Integer y=Integer.parseInt(strArr[1]);System.out.println("x="+x);System.out.println("y="+y);}catch (ArrayIndexOutOfBoundsException ex){System.out.println("格式不符合,你没有加逗号");}catch (NumberFormatException ex){System.out.println("填数字,你个nt");}}}}/*12格式不符合,你没有加逗号x填数字,你个ntx2填数字,你个nt2,y填数字,你个nt2,3,3x=2y=3*/
注意事项
- 异常处理流程每次最多进入一个
catch块 - 处理异常的时候要先处理隶属子类异常,在处理隶属父类的异常
异常的体系
两大类
Error代表严重且不可恢复的错误(一般不要求处理)Exception代表可以恢复的错误,一般会要求程序处理
多异常捕捉
接上例:
catch (NumberFormatException|IndexOutOfBoundsException ex){System.out.println("填数字,你个nt");}
将两个异常归并为一类进行处理
catch (Exception ex){System.out.println("你整出了我没想到的异常,你真是个小可爱");}
最后再将Exception类放进去防止漏网之鱼,通用,处理开发者还没有想到的异常
完整的异常处理流程
try{//业务代码}catch(异常类1 ex){}catch(异常类2 ex){}......//catch块可以出现1-n次finally{//final块最多出现1次}
final\finally\finalize的区别:
final作为修饰符,用于修饰变量方法和类;finally作为异常处理中最后的一个块,用于回收资源
Java会保证无论是否出现异常,甚至有`return`都不会阻止`finally`语块的执行。你要阻止`final`语块的执行,要么`exit`虚拟机,要么拔电源
finalize所有对象被GC之前,系统会自动调用finalize方法(基本用不到)
public class FinallyEx {public int Test(){int a = 0;try{return ++a;}finally {//Java会最后执行finally再return结束方法return ++a;}}public static void main(String[] args) {FinallyEx m1=new FinallyEx();System.out.println(m1.Test());}}
异常处理的嵌套
一般来说,为了处理可能在catch块中出现的异常,就出现了异常处理的嵌套流程
Java7以后的改进
try(//需要声明并创建可以自动关闭的资源)
注意,圆括号内的资源必须声明并创建
自动关闭会调用AutoCloseable接口和其子接口Closeable
自动关闭资源的try语句自带隐藏finally,可以既没有catch也没有finally块
异常处理方法
Exception继承Throwable
所有的异常都是Exception的子类
getMessage()获取异常信息getStackTrace()获取异常的跟踪栈(堆栈信息)printStackTrace()打印跟踪栈信息(堆栈信息)
import java.util.Random;public class A {public void foo() {System.out.println("foo");B b = new B();b.bar();}}class B {public void bar() {System.out.println("bar");C c1 = new C();c1.info();}}class C {public void info() {int a = 20 / (new Random().nextInt(3));//三分之一的可能性报错(整除以0的时候)System.out.println(a);}}class Test{public static void main(String[] args) {A a1=new A();a1.foo();}}/*foobarException in thread "main" java.lang.ArithmeticException: / by zeroat C.info(A.java:21)at B.bar(A.java:15)at A.foo(A.java:7)at Test.main(A.java:29)*/
从上面例子中的报错可以总结出来的技巧——通过跟踪栈调试错误的时候,应该从第一个自己写的错误类下手进行调试!
checked异常与runtime异常
RuntimeException以及其子类的实例,都是runtime(运行时)异常
Java编译器**不强行要求处理`Runtime`异常**
checked异常,不是RuntimeException,就是checked异常
**这是必须处理的异常**要么显示用`try catch`来处理该异常,要么就**声明抛出**该异常——否则编译无法通过
runtime异常比checked异常用起来更方便
一些框架喜欢把checked异常包装为runtime异常
声明抛出异常语法
在方法签名上使用,
throws 异常类1,异常类2,异常类3
throws可以一次声明抛出多个异常
import java.util.Random;public class RuntimeE {public static void main(String[] args){try {//下面的代码有可能出异常Random rnd = new Random();int a = 20 / (rnd.nextInt(3));//ArithmeticException//动态初始化定义一个长度为4的数组int[] iArr = new int[4];System.out.println(iArr[rnd.nextInt(8)]);//ArrayIndexOutOfBoundsExceptionString str=null;System.out.println(str.length());}catch (ArrayIndexOutOfBoundsException|ArithmeticException|NullPointerException ex){System.out.println("嗯,出异常了");}}}
回顾方法重写的规则
两同两小一大——两小:
- 返回值类型相同或者更小
- 抛出异常相同或是更小
import java.io.FileInputStream;import java.io.FileNotFoundException;import java.util.Date;class Base{public Object info() throws Exception{System.out.println("父类的Info");return new Date();}}public class Sub extends Base{@Overridepublic String info() throws FileNotFoundException {new FileInputStream("Sub.java");System.out.println("重写的info");return "Java";}public static void main(String[] args) {try {new Sub().info();}catch (Exception ex){ex.printStackTrace();}}}
throws throw throwable的区别
throws——声明抛出异常,表明该方法暂时不处理这个异常,交给方法的调用者处理
throwable——Exception和Error的共同的父类
throw——一个自定义异常的类,一次只能new一个异常
喜闻乐见的五大Runtime异常
NullPointException空指针异常IndexOutOfBoundsException、ArrIndexOutOfBoundsException索引越界异常、数组索引越界异常ArithmeticException算术异常NumberFormatException数字格式异常IllegalArgumentException非法参数异常
运行时异常的常见子类——NPENullPointerException
出现原因:
- 掉用一个空对象的实例方法
- 访问一个空对象的成员变量
- 访问一个空数组的
length属性 - 访问空数组的元素
手动抛出异常与自定义异常
抛出异常
在某些时候,如果出现了 和业务不符合的情况,就可以主动抛出异常
throw异常实例:
——throw是一个独立使用的语句,用于抛出一个异常
throw是可以放在方法体,构造器,初始化块中使用
如果一个方法中throw了一个异常,表明这个代码可能抛出异常,
如果该异常是checked异常,按惯例是声明抛出
如果该异常是runtime异常,编译总可以通过,那么可以不用管
public class User {private String name;private String passwd;public User() {};public User(String name, String passwd) {if (name == null || name.length() > 10 || name.length() < 6) {throw new IllegalArgumentException("用户名长度必须在6到10位");}if (passwd == null || passwd.length() > 20 || passwd.length() < 6) {throw new IllegalArgumentException("用户密码长度必须在6到20位");}this.name = name;this.passwd = passwd;};public String getName() {return name;}public String getPasswd() {return passwd;}public void setName(String name) {if (name == null || name.length() > 10 || name.length() < 6) {throw new IllegalArgumentException("用户名长度必须在6到10位");}this.name = name;}public void setPasswd(String passwd) {if (passwd == null || passwd.length() > 20 || name.length() < 6) {throw new IllegalArgumentException("用户密码长度必须在6到20位");}this.passwd = passwd;}}class UserTest{public static void main(String[] args) {User user=new User();user.setName("ld");}}
自定义异常
自定义runtime异常,就要继承RuntimeException
自定义checked异常,就要继承CheckedException
public class ShopService {public void sellItem(double price)throws ItemException{//刚定义的是一个checked异常//要么抛出,要么try catchif (price<0){throw new ItemException("价格怎么是负数,滚吧");}System.out.println("price="+price);}}class ItemException extends CheckedException{public ItemException(){}public ItemException(String msg){super(msg);}}
catch和throw的结合使用
在某些情况下,方法捕获了异常,程序也对该异常进行了部分处理,但仍然无法处理完成
此时就需要在catch块中再次抛出新的异常对象
例子——把checked异常改为Runtime异常
import java.io.FileInputStream;public class CatchAndThrow {public void Test(String fileName){try {//该代码可能抛出FileNotFound的checked异常new FileInputStream(fileName);}catch (Exception ex){System.out.printf("%s该文件不存在\n",fileName);throw new IllegalArgumentException("传入的文件名不存在"+fileName);}}public static void main(String[] args) {CatchAndThrow ct=new CatchAndThrow();ct.Test("add.txt");//会抛出checked异常,编译不通过//FileInputStream fi=new FileInputStream("a.txt");}}
