Java 基础(异常处理)

248 阅读9分钟

异常概述与异常体系结构

Java源程序 - javac.exe(编译) -> 字节码文件 - java.exe(运行) -> 在内存中加载、运行类

  • 异常体系结构:
    1. 父类:java.lang.Throwable

    2. 子类: java.lang.Error,不编写针对代码处理

    3. 子类: java.lang.Exception,编写针对代码处理

      • 编译时异常(checked):IOException、ClassNotException、FileNotFoundException
      • 运行时异常(unchecked):NullPointerException、ArrayIndexOutBoundsException

异常: 在Java语言中,将程序执行中发生的不正常情况称为 异常

  • 开发过程中的语法错误和逻辑错误不是异常

Java程序在执行过程中所发生的异常事件可分为两类:

  • Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等;如:StackOverflowError和OOM,一般不编写针对性代码进行处理
public class ErrorTest {
    public static void mian (String[] args) {
    	// 1.栈溢出: java.lang.StackOverflowError
    	// main(args);
        // 2.堆溢出: java.lang.OutOfMemoryError(OOM)
        Integer[] arr = new Integer[1024*1024*1024];
    }
}
  • Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性代码进行处理。
    1. 空指针访问
    2. 读取不存在的文件
    3. 网络中断
    4. 数组角标越界
// Throwable
// 1. Error
// 2. Exception
public class ExceptionTest {
	//
}
  • 对于这些错误,一般有两种解决方案: 一是遇到错误就终止程序运行;另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息提示,及错误的处理

  • 捕获错误最理想的是在编译期间,但有的错误只会在运行时才会发生;如: 除数为0、数组下标越界等

    1. 编译时异常(checked)
    2. 运行时异常(RuntimeException)
  • 开发中,运行时异常一般不处理,我们着重处理编译时异常

Java异常处理

Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅、易于维护

Java异常处理的方式:

  • Java提供的是异常处理的抓抛模型

    1. :程序正常执行过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出,且之后的代码终止执行

    2. : 可以理解为异常的处理方式

  • Java程序的执行过程中如出现异常,会生成一个异常类对象,改异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常

  • 异常对象的生成

    1. 由虚拟机自动生成,后台自动创建一个对应异常类的实例对象并抛出 --- 自动抛出
    2. 由开发人员手动创建

常见异常

  • 编译时异常:执行javac.exe命令时,可能出现的异常
  • 运行时异常:执行java.exe命令时,出现的异常
// 1.编译时异常
public class CheckedTest {
	// FileNotFoundException
    @Test
    public void test7 () {
    	File f1 = new File("hello.txt");
        // 流
        FileInputStream fis = new FileInputStream(f1);
        // 读取流
        // IOException
        int data = fis.read();
        while (data != -1) {
        	System.out.println((char)data);
            data = fis.read();
        }
        // 关闭流
        fis.close();
    }
}
// 2.运行时异常
public class ExceptionTest {
	// NullPointerException
    @Test
    public void test1 () {
    	int[]  arr = null;
        System.out.println(arr[3]);
        String str = "123";
        str = null;
        System.out.println(str.charAt(0));
    }
    // IndexOutOfBoundsException
    @Test
    public void test2 () {
    	// ArrarIndexOutOfBoundsException
    	int[] arr = new int[5];
        System.out.println(arr[5]);
        // IndexOutOfBoundsException
        String str = "abc";
        System.out.println(str.charAt(3));
    }
    // ClassCastException 类型转换异常
    @Test
    public void test3 () {
    	Object o1 = new Date();
        String s1 = (String)o1;
    }
    // NumberFormatException 数值类型转换错误
    @Test
    public void test4 () {
    	String str = "123";
        str = "123ab";
        int num = Integer.parseInt(str);
    }
    // InputMismatchException 输入异常
    @Test
    public void test5 () {
    	Scanner scan = new Scanner(System.in);
        int score = scan.nextInt(); // "abc"
        System.out.println(score);
        scan.close();
    }
    // ArithmeticException 算数异常
    public void test6 () {
    	int a = 10;
        int b = 0;
        System.out.println(a / b);
    }
}

异常处理机制一*:try-catch-finally

  • 系统自动生成的异常

一、try-catch-finally(自己处理)

try {
	// 可能出现异常的代码
} catch (异常类型1 变量名1) {
	// 处理异常的方式1
} catch (异常类型2 变量名2) {
	// 处理异常的方式2
} catch (异常类型n 变量名n) {
	// 处理异常的方式n
} finally {
	// 一定会执行的代码
}
  • 说明:
    1. finally是可选的
    2. 使用try将可能出现异常代码包裹起来,在执行过程中,出现异常后,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
    3. 一旦try中的异常对象匹配到某个catch后,就进入catch中就进行异常处理,处理完成后,就跳出 try-catch结构(无finally),继续执行其后的代码
    4. catch中的异常类型如果没有子父类关系,则声明前后没有关系,catch中的异常类型如果有子父类关系,则要求子类在前父类在后
    5. 常用异常对象处理方式: ①String getMessage()、② void printStackTrace()
    6. 在try、catch、finally结构中声明的变量,在结构外就不能调用;解决办法就是在结构外声明并初始化,在结构中使用
    7. 可以嵌套使用

体会:

  • 使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但运行时仍可能报错;相当于我们使用 try-catch-finally 将一个编译时可能出现的异常,延迟到运行时出现
  • 开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常进行处理;但针对编译时异常,我们一定要考虑异常处理

try-catch-finally 中 finally的使用

  • finally 是可选的
  • finally 中声明的是一定会被执行的代码,即使catch中出现异常,或try、catch 中出现 return 语句,也会先执行 finally 中的代码后才执行 return 语句
  • 垃圾回收中对物理连接,如:数据库连接、输入输出流、Socket连接操作的资源,JVM是不能自动回收的,需要手动关闭(必须要执行的操作),可以放在 finally中执行
public class FinallyTest {
	@Test
    // finally
    public void test1 () {
    	// 要赋值
        FileInputStream fis = null;
    	try {
        	File f1 = new File("Hello.txt");
            fis = new FileInputStream(f1);
            int data = fis.read();
            while (data != -1) {
            	System.out.println((char)data);
                data = fis.read();
            }
            fis.close();
        } catch (FileNotFoundException e) {
        	e.printStackTrace();
        } catch (IOException e) {
        	e.printStackTrace();
        } finally {
        	// fis.close();
            try {
            	// 避免空指针异常
            	if (fis != null) {
                	fis.close();
                }
            } catch (IOException e) {
            	e.printStackTrace();
            }
        }
    }
	@Test
    public int testin () {
    	try {
        	int[] arr = new int[5];
            System.out.println(arr[5]);
            return 1;
        } catch (ArrayIndexOutOfBoundsException e) {
        	e.printStackTrace();
            return 0;
        } finally {
        	System.out.println("俺一定会执行");
            // return 2;
        }
        // 俺一定会执行
        // 0
    }
  @Test
  public void test0 () {
    try {
    	int a = 10;
        int b = 0;
        System.out.println(a / b);
    } catch (ArithmeticException e) {
    	e.printStackTrace();
        int[] arr = new int[10];
        System.out.println(arr[10]);
    } catch (Exception e) {
    	e.printStackTrace();
    } finally {
   		// 一定会被执行的代码
    	System.out.println("我最帅!");
    }
    // 我最帅!
  }
}
public class ExceptionTest {
	String str = "123";
    str = "abc";
    int num = 0;
    try {
    	num = Integer.parseInt(str);
        System.out.println("try异常");
    } catch (NumberFormatException e) {
    	System.out.println("数值转换异常!");
        // 1. String getMessage();
        System.out.println(e.getMessage());
        // For input string "abc"
        // 2. printStackTrace();报错的详细信息 
        e.printStackTrace();
    } catch (NullPointerException e) {
    	System.out.println("NullPointerException");
    } catch (Exception e) {
    	// ...
    }
    System.out.println("异常处理后"); 
    // 输出:
    // 数值转换异常!
    // 异常处理后
    public void test1 () {
    	try {
        	File f1 = new File("Hello.txt");
            FileInputStream fis = new FileInputStream(fis);
            int data = fis.read();
            while (data != -1) {
            	System.out.println((char)data);
                data = fis.read();
            }
            fis.close();
        } catch (FileNotFoundException e) {
        	e.printStackTrace();
        } catch (IOException e) {
        	e.printStackTrace();
        }
    }
}

二、throws + 异常类型(向上求助)

  • 见下

异常处理机制二*:throws

  • 系统自动生成的异常
  • 写在方法的声明处,指明此方法在执行时可能出现的异常,出现异常时仍会在异常代码生成处生成一个异常类的对象,此对象满足throws后的异常类型时,会将异常抛给调用者,异常对象生成处之后的代码就不再执行
  • 并没有真正解决问题,只是交给上级去处理
  • try-catch-finally的方式是真正的将异常处理掉;throws的方式只是将异常抛给了方法的调用者,并没有自己解决异常
public class ExceptionTest2 {
    public static void main (String[] args) {
    	try {
        	method2();
        } catch (FileNotFoundException e) {
        	e.printStackTrace();
        } catch (IOException e) {
        	e.printStackTrace();
        }
    }  
    public static void method2 () throws FileNotFoundException, IOException  {
    	method2();
    }
    public static void method1 () throws FileNotFoundException, IOException {
        File f1 = new File("Hello.txt");
        FileInputStream fis = new FileInputStream(fis);
        int data = fis.read();
        while (data != -1) {
        	System.out.println((char)data);
            data = fis.read();
        }
        fis.close();
    }
}

实际应用:

  • 方法重写的规则之一: 子类重写的方法抛出的异常类型,不大于父类抛出的类型

  • 开发中如何选择try-catch-finally和throws

    1. 父类中被重写的方法没有throws,那子类中只能使用try-catch-finally
    2. 多个方法递进调用时,各方法使用throws,在调用处使用try-catch统一处理
public class OverrideTest {
	public static void main (String[] args) {
		OverrideTest t1 = new OverrideTest();
		t1.display(new SubClass());
	}
	public void display (SuperClass s) {
		// 如果子类的异常高于父类的异常,此时异常处理代码失效
          try {
		        s.method();
	  } catch (IOException e) {
			e.printStackTrace();
	  }
	}
}
class SuperClass {
	public void method () throws IOException {
		//
	}
}
class SubClass extends SuperClass {
	public void method () throws FileNotFoundException {
		//	
	}
}

手动抛出异常(手动生成异常):throw

public class StudentTest {
    try {
    	Student s1 = new Student();
        s1.regist(-1001);
        System.out.println(s); // Student[id = 0]
    } catch (Exception e) {
    	e.printStackTrace();
        System.out.println(e.getMessage()); // input illegal data!
    }
}
class Student {
    private int id;
    public void regist (int id) throws Exception { // 体现异常的处理
    	if (id > 0) {
        	this.id = id;
        } else {
            System.out.println("illegal data!");
            // 手动抛出异常(运行)
            // throw new RuntimeException("input illegal data!")
            // 生成异常对象(编译)
            // throw new Exception("input illegal data!");
            // 自定义异常类
            throw new MyException("input illegal data!");
        }
    }
    public String toString () {
    	return "Student[ id = " + id + "]"
    }
}

用户自定义异常类

如何自定义异常类

  • 继承于现有的异常结构: RuntimeException(运行时异常)、Exception(编译异常)
  • 模仿其它异常继承类
    1. 提供全局常量serialVersionUID,唯一标识此类
    2. 提供重载的构造器
// 如上面使用
class MyException extends RuntimeException {
	static final long serialVersionUID = -777777777777777777L;
    public MyException () {}
    public MyException (String msg) {
    	super(msg);
    }
}

总结: 异常处理5个关键字

捕获异常

  • try:包裹执行可能产生异常的代码
  • catch:捕获异常
  • finally:无论是否发生异常或return,代码总被执行

抛出异常

  • throw:异常的生成阶段,手动抛出异常对象

声明异常

  • throws:异常的处理方式,声明方法可能要抛出的各种异常类

上游排污(throw),下游治污(try、throws)

throw和throws对比

  • throws写在方法的声名处
  • throw写在方法中
  • throws是异常的处理方式之一
  • throw是手动生成异常对象并抛出

final、finally、finalize三者的区别

  • final: 修饰类、变量
  • finally:异常处理try-catch-finally
  • finalize:单词

小悟

  • 世上最远的距离,我在if你在else,似乎一直相伴又永远分离
  • 世上最痴心的等待,我当case你是switch,获取永远都选不上自己
  • 世上最真情的相依,你在try我在catch;无论你发神马脾气,我都默默承受,静静处理;到时再来期待我们的finally

练习

// 1.
public class ReturnExceptionDemo {
	static void methodA () {
    	try {
        	System.out.println("进入方法A");
            throw new RuntimeException("制造异常");
        } finally {
        	System.out.println("使用A方法的finally");
        }
    }
    static void methodB () {
    	try {
        	System.out.println("进入方法B");
            return;
        } finally {
        	System.out.println("调用B方法的finally");
        }
    }
    public static void main (String[] args) {
    	try {
        	methodA();
        } catch (Exception e) {
        	System.out.println(e.getMessage());
        }
        methodB();
    }
    // 进入方法A
    // 用A方法的finally
    // 制造异常
    // 进入方法B
    // 调用B方法的finally
}
// 2.
// 异常的类型
// 异常的处理 try-catch-finally、throws、throw
// 自定义异常类
public class EcmDef {
	public static void main (String[] args) {
		try {
			int i = Integer.parseInt(args[0]);
			int j = Integer.parseInt(args[1]);
			int result = ecm(i, j);
			System.out.println(result);
		} catch (NumberFormatException e) {
			// e.printStackTrace();
			System.out.println("NumberFormatException");
		} catch (ArrayIndexOutOfBoundsException e) {
			// e.printStackTrace();
			System.out.println("ArrayIndexOutOfBounds");
		} catch (ArithmeticException e) {
			// e.printStackTrace();
			System.out.println("ArithmeticException");
		} catch (EcDef e) {
			System.out.println("EcDef" + e.getMessage());
		} finally {
			System.out.println("done");
		}
		System.out.println("after");
        // 执行顺序:
		// ArrayIndexOutOfBounds
		// done
		// after
	}
	public static int ecm (int i, int j) throws EcDef {
		if (i < 0 || j < 0) {
			throw new EcDef("cannot input fushu!");
		} else {
			return i / j;
		}
	}
}

class EcDef extends Exception {
	static final long serialVersionUID = -46546131453132135L;

	public EcDef () {}
	public EcDef (String msg) {
		super(msg);
	}
}