Java通识基础27(异常处理)

存在意义

有了异常处理的机制——我们的程序会更加健壮!

也有更高的容错率

程序在运行时会面临:

  1. 物理环境的改变

  2. 用户的误操作

  3. 恶意操作
    ……
    面临以上情况的处理——异常处理

传统的错误处理机制

  1. if(错误1){
  2. }
  3. else if(错误2){
  4. }
  5. ......

处理方式存在的问题

  • 所有的异常情况无法被穷举,总有漏网之鱼
  • 流程处理混杂(业务处理逻辑的和异常处理逻辑混在一起),程序可读性差

异常处理流程

  1. if(任何错误){
  2. //异常处理语句
  3. }
  4. else{
  5. //正常处理
  6. }

改进后

  1. try{
  2. //正常处理逻辑
  3. }
  4. catch(异常){
  5. //处理错误
  6. }

举例:

  1. import java.util.Scanner;
  2. public class Demo1 {
  3. public static void main(String[] args) {
  4. Scanner sc=new Scanner(System.in);
  5. while (sc.hasNextLine()){
  6. try {
  7. String line= sc.nextLine();
  8. String[] strArr=line.split(",");
  9. Integer x=Integer.parseInt(strArr[0]);
  10. Integer y=Integer.parseInt(strArr[1]);
  11. System.out.println("x="+x);
  12. System.out.println("y="+y);
  13. }
  14. catch (Exception ex){
  15. System.out.println(ex);
  16. System.out.println("写的时候动动脑子!");
  17. }
  18. }
  19. }
  20. }
  21. /*
  22. x
  23. java.lang.NumberFormatException: For input string: "x"
  24. 写的时候动动脑子!
  25. 23
  26. java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
  27. 写的时候动动脑子!
  28. 213xxas
  29. java.lang.NumberFormatException: For input string: "213xxas"
  30. 写的时候动动脑子!
  31. 1,2,3
  32. x=1
  33. y=2
  34. */

此时将正常的业务处理代码放在try块里面,将异常处理逻辑放在catch块里面

  • 如果遇到异常

    1. Java会自动将该异常封装称为Exception对象
    2. 该对象会提交给Java的运行环境——这个过程叫做(抛出)
    3. Java运行环境收到该异常之后会寻找匹配的catch块——这个过程叫做(捕获)
  • 如果找不到异常,程序就会报错结束

    • 刚刚例子里见面的捕捉的是所有的Exception(所有异常的父类),如果捕捉异常做出相应的改变,改变后捕捉的异常与抛出的异常不匹配,程序又会报错
    • 所以,咱们可以动态的catch不同的异常,在相应的处理快中编写相应的处理代码
  1. import java.util.Scanner;
  2. public class Demo1 {
  3. public static void main(String[] args) {
  4. Scanner sc=new Scanner(System.in);
  5. while (sc.hasNextLine()){
  6. try {
  7. String line= sc.nextLine();
  8. String[] strArr=line.split(",");
  9. Integer x=Integer.parseInt(strArr[0]);
  10. Integer y=Integer.parseInt(strArr[1]);
  11. System.out.println("x="+x);
  12. System.out.println("y="+y);
  13. }
  14. catch (ArrayIndexOutOfBoundsException ex){
  15. System.out.println("格式不符合,你没有加逗号");
  16. }
  17. catch (NumberFormatException ex){
  18. System.out.println("填数字,你个nt");
  19. }
  20. }
  21. }
  22. }
  23. /*
  24. 12
  25. 格式不符合,你没有加逗号
  26. x
  27. 填数字,你个nt
  28. x2
  29. 填数字,你个nt
  30. 2,y
  31. 填数字,你个nt
  32. 2,3,3
  33. x=2
  34. y=3
  35. */

注意事项

  1. 异常处理流程每次最多进入一个catch
  2. 处理异常的时候要先处理隶属子类异常,在处理隶属父类的异常

异常的体系

两大类

  • Error代表严重且不可恢复的错误(一般不要求处理)
  • Exception代表可以恢复的错误,一般会要求程序处理

多异常捕捉

接上例:

  1. catch (NumberFormatException|IndexOutOfBoundsException ex){
  2. System.out.println("填数字,你个nt");
  3. }

将两个异常归并为一类进行处理

  1. catch (Exception ex){
  2. System.out.println("你整出了我没想到的异常,你真是个小可爱");
  3. }

最后再将Exception类放进去防止漏网之鱼,通用,处理开发者还没有想到的异常

完整的异常处理流程

  1. try{
  2. //业务代码
  3. }
  4. catch(异常类1 ex){
  5. }
  6. catch(异常类2 ex){
  7. }
  8. ......//catch块可以出现1-n次
  9. finally{
  10. //final块最多出现1次
  11. }

final\finally\finalize的区别:

  • final作为修饰符,用于修饰变量方法和类;

  • finally作为异常处理中最后的一个块,用于回收资源

  1. Java会保证无论是否出现异常,甚至有`return`都不会阻止`finally`语块的执行。
  2. 你要阻止`final`语块的执行,要么`exit`虚拟机,要么拔电源
  • finalize所有对象被GC之前,系统会自动调用finalize方法(基本用不到)
  1. public class FinallyEx {
  2. public int Test(){
  3. int a = 0;
  4. try{
  5. return ++a;
  6. }
  7. finally {
  8. //Java会最后执行finally再return结束方法
  9. return ++a;
  10. }
  11. }
  12. public static void main(String[] args) {
  13. FinallyEx m1=new FinallyEx();
  14. System.out.println(m1.Test());
  15. }
  16. }

异常处理的嵌套

一般来说,为了处理可能在catch块中出现的异常,就出现了异常处理的嵌套流程

Java7以后的改进

  1. try(
  2. //需要声明并创建可以自动关闭的资源
  3. )

注意,圆括号内的资源必须声明并创建

自动关闭会调用AutoCloseable接口和其子接口Closeable

自动关闭资源的try语句自带隐藏finally,可以既没有catch也没有finally

异常处理方法

Exception继承Throwable

所有的异常都是Exception的子类

  • getMessage()获取异常信息
  • getStackTrace()获取异常的跟踪栈(堆栈信息)
  • printStackTrace()打印跟踪栈信息(堆栈信息)
  1. import java.util.Random;
  2. public class A {
  3. public void foo() {
  4. System.out.println("foo");
  5. B b = new B();
  6. b.bar();
  7. }
  8. }
  9. class B {
  10. public void bar() {
  11. System.out.println("bar");
  12. C c1 = new C();
  13. c1.info();
  14. }
  15. }
  16. class C {
  17. public void info() {
  18. int a = 20 / (new Random().nextInt(3));
  19. //三分之一的可能性报错(整除以0的时候)
  20. System.out.println(a);
  21. }
  22. }
  23. class Test{
  24. public static void main(String[] args) {
  25. A a1=new A();
  26. a1.foo();
  27. }
  28. }
  29. /*
  30. foo
  31. bar
  32. Exception in thread "main" java.lang.ArithmeticException: / by zero
  33. at C.info(A.java:21)
  34. at B.bar(A.java:15)
  35. at A.foo(A.java:7)
  36. at Test.main(A.java:29)
  37. */

从上面例子中的报错可以总结出来的技巧——通过跟踪栈调试错误的时候,应该从第一个自己写的错误类下手进行调试!

checked异常与runtime异常

RuntimeException以及其子类的实例,都是runtime(运行时)异常

  1. Java编译器**不强行要求处理`Runtime`异常**

checked异常,不是RuntimeException,就是checked异常

  1. **这是必须处理的异常**
  2. 要么显示用`try catch`来处理该异常,要么就**声明抛出**该异常——否则编译无法通过

runtime异常比checked异常用起来更方便

一些框架喜欢把checked异常包装为runtime异常

声明抛出异常语法

在方法签名上使用,

throws 异常类1,异常类2,异常类3

throws可以一次声明抛出多个异常

  1. import java.util.Random;
  2. public class RuntimeE {
  3. public static void main(String[] args){
  4. try {
  5. //下面的代码有可能出异常
  6. Random rnd = new Random();
  7. int a = 20 / (rnd.nextInt(3));
  8. //ArithmeticException
  9. //动态初始化定义一个长度为4的数组
  10. int[] iArr = new int[4];
  11. System.out.println(iArr[rnd.nextInt(8)]);
  12. //ArrayIndexOutOfBoundsException
  13. String str=null;
  14. System.out.println(str.length());
  15. }
  16. catch (ArrayIndexOutOfBoundsException|ArithmeticException|NullPointerException ex){
  17. System.out.println("嗯,出异常了");
  18. }
  19. }
  20. }

回顾方法重写的规则

两同两小一大——两小:

  • 返回值类型相同或者更小
  • 抛出异常相同或是更小
  1. import java.io.FileInputStream;
  2. import java.io.FileNotFoundException;
  3. import java.util.Date;
  4. class Base{
  5. public Object info() throws Exception{
  6. System.out.println("父类的Info");
  7. return new Date();
  8. }
  9. }
  10. public class Sub extends Base{
  11. @Override
  12. public String info() throws FileNotFoundException {
  13. new FileInputStream("Sub.java");
  14. System.out.println("重写的info");
  15. return "Java";
  16. }
  17. public static void main(String[] args) {
  18. try {
  19. new Sub().info();
  20. }
  21. catch (Exception ex){
  22. ex.printStackTrace();
  23. }
  24. }
  25. }

throws throw throwable的区别

throws——声明抛出异常,表明该方法暂时不处理这个异常,交给方法的调用者处理

throwable——ExceptionError的共同的父类

throw——一个自定义异常的类,一次只能new一个异常

喜闻乐见的五大Runtime异常

  • NullPointException空指针异常
  • IndexOutOfBoundsException、ArrIndexOutOfBoundsException索引越界异常、数组索引越界异常
  • ArithmeticException算术异常
  • NumberFormatException数字格式异常
  • IllegalArgumentException非法参数异常

运行时异常的常见子类——NPENullPointerException

出现原因:

  • 掉用一个空对象的实例方法
  • 访问一个空对象的成员变量
  • 访问一个空数组的length属性
  • 访问空数组的元素

手动抛出异常与自定义异常

抛出异常

在某些时候,如果出现了 和业务不符合的情况,就可以主动抛出异常

throw异常实例:

——throw是一个独立使用的语句,用于抛出一个异常

throw是可以放在方法体,构造器,初始化块中使用

如果一个方法中throw了一个异常,表明这个代码可能抛出异常,

如果该异常是checked异常,按惯例是声明抛出

如果该异常是runtime异常,编译总可以通过,那么可以不用管

  1. public class User {
  2. private String name;
  3. private String passwd;
  4. public User() {
  5. }
  6. ;
  7. public User(String name, String passwd) {
  8. if (name == null || name.length() > 10 || name.length() < 6) {
  9. throw new IllegalArgumentException("用户名长度必须在6到10位");
  10. }
  11. if (passwd == null || passwd.length() > 20 || passwd.length() < 6) {
  12. throw new IllegalArgumentException("用户密码长度必须在6到20位");
  13. }
  14. this.name = name;
  15. this.passwd = passwd;
  16. }
  17. ;
  18. public String getName() {
  19. return name;
  20. }
  21. public String getPasswd() {
  22. return passwd;
  23. }
  24. public void setName(String name) {
  25. if (name == null || name.length() > 10 || name.length() < 6) {
  26. throw new IllegalArgumentException("用户名长度必须在6到10位");
  27. }
  28. this.name = name;
  29. }
  30. public void setPasswd(String passwd) {
  31. if (passwd == null || passwd.length() > 20 || name.length() < 6) {
  32. throw new IllegalArgumentException("用户密码长度必须在6到20位");
  33. }
  34. this.passwd = passwd;
  35. }
  36. }
  37. class UserTest{
  38. public static void main(String[] args) {
  39. User user=new User();
  40. user.setName("ld");
  41. }
  42. }

自定义异常

自定义runtime异常,就要继承RuntimeException

自定义checked异常,就要继承CheckedException

  1. public class ShopService {
  2. public void sellItem(double price)throws ItemException{
  3. //刚定义的是一个checked异常
  4. //要么抛出,要么try catch
  5. if (price<0){
  6. throw new ItemException("价格怎么是负数,滚吧");
  7. }
  8. System.out.println("price="+price);
  9. }
  10. }
  11. class ItemException extends CheckedException{
  12. public ItemException(){
  13. }
  14. public ItemException(String msg){
  15. super(msg);
  16. }
  17. }

catchthrow的结合使用

在某些情况下,方法捕获了异常,程序也对该异常进行了部分处理,但仍然无法处理完成

此时就需要在catch块中再次抛出新的异常对象

例子——把checked异常改为Runtime异常

  1. import java.io.FileInputStream;
  2. public class CatchAndThrow {
  3. public void Test(String fileName){
  4. try {
  5. //该代码可能抛出FileNotFound的checked异常
  6. new FileInputStream(fileName);
  7. }
  8. catch (Exception ex){
  9. System.out.printf("%s该文件不存在\n",fileName);
  10. throw new IllegalArgumentException("传入的文件名不存在"+fileName);
  11. }
  12. }
  13. public static void main(String[] args) {
  14. CatchAndThrow ct=new CatchAndThrow();
  15. ct.Test("add.txt");
  16. //会抛出checked异常,编译不通过
  17. //FileInputStream fi=new FileInputStream("a.txt");
  18. }
  19. }