Java异常处理详解
异常处理
异常概述和异常体系结构
在使用计算机语育进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。
- 异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)
- Java程序在执行过程中所发生的异常事件可分为两类:
- Error: Java慮拟机无法解决的严重问题。如: JVM系统内部错误、资源耗尽等严重情况。比如: StackOverflowError和OOM(内存耗尽). 一般不编写针对性的代码进行处理。
- Exception: 其它因编程错误或偶然的外在因素导致的一一般性问题,可以使用针对性的代码进行处理。例如:
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
Error的例子
栈溢出
package com.dreamcold.exception;
public class Demo01 {
public static void main(String[] args) {
main(args);
}
}
结果
一直递归调用,而不弹栈,就会导致栈的溢出错误
堆溢出
package com.dreamcold.exception;
public class Demo01 {
public static void main(String[] args) {
int[] arr=new int[1024*1024*1024];
}
}
结果:new关键字申请内存在堆中,堆中没有那么多的内存,内存不够,简称OOM
如何处理这些异常
- 对于这些错误,一般有两种解决方法:一是遇到错误就终止程序的运行。另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。
- 捕获错误最理想的是在编译期间, 但有的错误只有在运行时才会发生。例如:除数为0,数组下标越界等,捕捉异常可以分为
- 编译时异常
- 运行时异常
- Throwable作为异常的顶级父类
- 程序运行过程包含了两个过程分别是编译和运行,编译时候出现异常了就叫做编译时异常,否则就叫做运行时异常
- 当执行java.exe的时候出现的异常叫做运行时异常
- 上图中红色的时候就叫做编译时异常,蓝色的就叫做运行时异常
- 红色的异常叫做受检异常,蓝色的叫做非受检异常
- 最顶层的异常是java.lang.Throwable,以下两个类继承了该父类
- java.lang.Exception:一般不编写代码进行处理
- java.lang.Error:可以进行异常梳理
- 编译时异常(checked)(受检异常)
- IOException
- FileNotFoundException
- ClassNotFoundException
- IOException
- 运行时异常(unchecked)
- NullPointerException
- ArrayIndexOutOfBoundException
- ClassCastException
- NumberFormatException
- InputMissMatchException
- ArithmeticException
- 编译时异常(checked)(受检异常)
常见异常
运行时异常
空指针异常
package com.dreamcold.exception;
public class Demo02 {
public static void main(String[] args) {
int[] arr=null;
System.out.println(arr[3]);
}
}
结果:
数组越界
package com.dreamcold.exception;
public class Demo02 {
public static void main(String[] args) {
int[] arr=new int[10];
System.out.println(arr[10]);
}
}
结果
类型转换异常
package com.dreamcold.exception;
import java.util.Date;
public class Demo02 {
public static void main(String[] args) {
Object date=new Date();
String str=(String)date;
}
}
结果
数字格式异常
package com.dreamcold.exception;
import java.util.Date;
public class Demo02 {
public static void main(String[] args) {
String numbers="abcdefg";
Integer i=Integer.parseInt(numbers);
}
}
结果
输入匹配异常
package com.dreamcold.exception;
import java.util.Date;
import java.util.Scanner;
public class Demo02 {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int a=scanner.nextInt();
System.out.println(a);
}
}
测试结果:
算数异常
package com.dreamcold.exception;
import java.util.Date;
import java.util.Scanner;
public class Demo02 {
public static void main(String[] args) {
int a=10;
int b=0;
System.out.println(a/b);
}
}
结果
编译时异常
比如我们逐个字符读取文件的例子
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;
public class Demo02 {
public static void main(String[] args) {
File file=new File("hello.txt");
FileInputStream fs=new FileInputStream(file);
int data=fs.read();
while (data!=-1){
System.out.println((char)data);
data=fs.read();
}
fs.close();
}
}
编译器提示我们没有处理对应的IOException
异常处理机制:try-catch-finally
在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y运算时, 要检测分母为0,数据为空,输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长、臃肿,可读性差。因此采用异常处理机制。
Java异常处理
Java采用的异常处理机制,是将异常处理的程序代码集中在一起.与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
异常处理设计抓抛模型:
- 抛的过程:”抛“:程序在正常执行的过程中,一旦出现了异常,就会在异常代码处生成一个对应的异常对称,并将此对象抛出,一旦抛出对象后,该行之后的代码就不再执行了
- 抓的过程:理解为异常的处理方式,第一种方式try-catch-finally
格式
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(异常类型3 变量名3){
//处理异常的方式3
}finally{
//一定会执行的代码
}
代码示例1
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;
public class Demo02 {
public static void main(String[] args) {
String str="abc";
try{
int a=Integer.parseInt(str);
System.out.println("发生异常的后的第一行代码");
}catch (NumberFormatException e){
System.out.println("遇到数字格式异常...");
}
System.out.println("处理完结束的代码");
}
}
结果
示例二
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;
public class Demo02 {
public static void main(String[] args) {
String str="abc";
try{
int a=Integer.parseInt(str);
System.out.println("发生异常的后的第一行代码");
}catch (NullPointerException e){
System.out.println("遇到空指针格式异常...");
}
System.out.println("处理完结束的代码");
}
}
结果:异常类型不匹配,会导致无法捕获对应的异常进行处理
修改上述代码为
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;
public class Demo02 {
public static void main(String[] args) {
String str="abc";
try{
int a=Integer.parseInt(str);
System.out.println("发生异常的后的第一行代码");
}catch (NullPointerException e){
System.out.println("遇到空指针格式异常...");
}catch (NumberFormatException e){
System.out.println("遇到数字格式异常...");
}
System.out.println("处理完结束的代码");
}
}
结果
示例三:
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;
public class Demo02 {
public static void main(String[] args) {
String str="abc";
try{
int a=Integer.parseInt(str);
System.out.println("发生异常的后的第一行代码");
}catch (NullPointerException e){
System.out.println("遇到空指针格式异常...");
}catch (NumberFormatException e){
System.out.println("遇到数字格式异常...");
}catch (Exception e){
System.out.println("发生了异常");
}
System.out.println("处理完结束的代码");
}
}
结果
示例四:
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;
public class Demo02 {
public static void main(String[] args) {
String str="abc";
try{
int a=Integer.parseInt(str);
System.out.println("发生异常的后的第一行代码");
}catch (NullPointerException e){
System.out.println("遇到空指针格式异常...");
}catch (NumberFormatException e){
System.out.println(e.getMessage());
// System.out.println("遇到数字格式异常...");
}catch (Exception e){
System.out.println("发生了异常");
}
System.out.println("处理完结束的代码");
}
}
结果
示例五:
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;
public class Demo02 {
public static void main(String[] args) {
String str="abc";
try{
int a=Integer.parseInt(str);
System.out.println("发生异常的后的第一行代码");
}catch (NullPointerException e){
System.out.println("遇到空指针格式异常...");
}catch (NumberFormatException e){
// System.out.println(e.getMessage());
// System.out.println("遇到数字格式异常...");
e.printStackTrace();
}catch (Exception e){
System.out.println("发生了异常");
}
System.out.println("处理完结束的代码");
}
}
结果
示例六
离开try大括号是无法访问到定义的变量a的
推荐写法
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.util.Date;
import java.util.Scanner;
public class Demo02 {
public static void main(String[] args) {
String str="abc";
int a=0;
try{
a=Integer.parseInt(str);
System.out.println("发生异常的后的第一行代码");
}catch (NullPointerException e){
System.out.println("遇到空指针格式异常...");
}catch (NumberFormatException e){
// System.out.println(e.getMessage());
// System.out.println("遇到数字格式异常...");
e.printStackTrace();
}catch (Exception e){
System.out.println("发生了异常");
}
System.out.println(a);
System.out.println("处理完结束的代码");
}
}
结果
说明:
- finally是可选的
- 使用try将可能出现异常的代码包装起来,在执行的过程中,一旦出现异常,就会生成一个对应的异常类对象,根据此对象的类型去catch匹配
- 一旦try中的异常对象匹配到某一个catch时候,就进入catch中进行异常的处理,一旦处理完成,就跳出当前的try-catch的结构(在finally没写的情况下),继续执行之后的代码
- catch中的异常类型,如果没有子父类的关系,则谁声明在上,谁声明在下,无所谓,如果满足子父类的关系则要求子类一定生成父类的上面否则就会报错
- 处理异常包含两个方法第一个就是
- getMessage方法,返回一个字符串
- printStackTrace方法,打印异常栈
- 在try中声明的变量在出了try大括号之后,就没办法继续使用了
- 体会使用try-catch-finally处理编译的时候的异常,使得程序在编译的时候不再报错,在运行的时候可能依旧报错,相当于我们使用try-catch将原本编译时会报的错误,延迟到运行的时候报错
- try-catch结构可以相互嵌套
- 开发中由于运行时候的异常比较常见,所以我们就通常不针对运行时候的异常编写try-catch-finally针对异常时候的异常,我们一定要考虑异常的处理。
finally的使用
示例1
package com.dreamcold.exception;
public class Demo03 {
public static void main(String[] args) {
try{
int a=10;
int b=0;
System.out.println(a/b);
}catch (ArithmeticException e){
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("finally....");
}
}
}
结果
示例二
package com.dreamcold.exception;
public class Demo03 {
public static void main(String[] args) {
try{
int a=10;
int b=0;
System.out.println(a/b);
}catch (ArithmeticException e){
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("finally...");
}
}
结果
示例三:
package com.dreamcold.exception;
public class Demo03 {
public static void main(String[] args) {
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();
}
System.out.println("finally...");
}
}
结果
示例四
package com.dreamcold.exception;
public class Demo03 {
public static void main(String[] args) {
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("finally...");
}
}
}
结果
示例五
package com.dreamcold.exception;
public class Demo03 {
public static void main(String[] args) {
int num=test();
System.out.println(num);
}
public static int test(){
try{
int[] arr=new int[10];
System.out.println(arr[10]);
return 1;
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
return 2;
}finally {
System.out.println("finally...");
return 3;
}
}
}
结果
示例六
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Demo03 {
public static void main(String[] args) {
FileInputStream fis=null;
try {
File file=new File("Hello.txt");
fis=new FileInputStream(file);
int data=fis.read();
while (data!=-1){
System.out.println((char)data);
data=fis.read();
}
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally {
try {
if(fis!=null){
fis.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
}
结果
- finally是可选的
- finally中声明的是一定会执行的的代码,即使catch中又出现异常了,try中有return语句,catch中有return语句等情况
- 像数据库连接、输入输出流、网络编程socket等资源,JVM是不能自动释放的,我们需要自己手动的进行资源的释放
异常处理机制:throws
异常处理方式二,throws+异常类型
示例1
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Demo04 {
public static void main(String[] args) throws FileNotFoundException,IOException {
FileInputStream fis=null;
File file=new File("Hello.txt");
fis=new FileInputStream(file);
int data=fis.read();
while (data!=-1){
System.out.println((char)data);
data=fis.read();
}
fis.close();
}
}
示例二
package com.dreamcold.exception;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Demo04 {
public static void main(String[] args) {
try {
method();
}catch (IOException e){
e.printStackTrace();
}
}
public static void method1(){
try{
test();
}catch (IOException e){
e.printStackTrace();
}
}
public static void method() throws FileNotFoundException,IOException{
test();
}
public static void test() throws FileNotFoundException,IOException{
FileInputStream fis=null;
File file=new File("Hello.txt");
fis=new FileInputStream(file);
int data=fis.read();
while (data!=-1){
System.out.println((char)data);
data=fis.read();
}
fis.close();
}
}
示例三
package com.dreamcold.exception;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Demo05 {
public static void main(String[] args) {
SuperClass superClass=new SubClass();
display(superClass);
}
public static void display(SuperClass superClass){
try {
superClass.method();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class SuperClass{
public void method() throws IOException{
}
}
class SubClass extends SuperClass{
public void method() throws FileNotFoundException{
}
}
示例四
- throws+异常类型写在方法的声明处,指明在方法执行的时候可能会抛出的异常,一旦当方法体执行的时候,出现异常,仍然会在异常代码处生成一个异常类的对象,此对象满足throws后的异常类型的时候就会被抛出,异常代码后序的代码就不会执行了
- try-catch-finally的方式:是真正的将异常处理掉了,throws的方式仅仅是将异常抛给了方法的调用者,并没有将异常处理掉
- 方法重写的规则之一,子类重写的方法抛出的异常类型不大于父类被重写的方法的异常类型
- 开发中如何选择try-catch-finally还是throws?
- 如果父类中被重写的方法没有throws方式处理异常,则子类重写方法也不能使用throws,意味如果子类重写的方法中有异常必须采用try-catch-finally的方式来处理异常
- 执行方法的a中,先后调用了另外的几个方法,这几个方法的语法是递进的关系,我们建议这几个方法采用throws的方式处理进行处理,而执行的方法a可以考虑try-catch-finally的方式进行处理
手动抛出异常:throw
过程一:抛的过程,程序在正常执行的过程中,一旦出现异常的话,就会在异常的代码处生成一个对弈的异常的对象,并将此对象进行抛出,一旦抛出对象后其后的代码就不再执行
关于对象的产生:
- 系统自动生成的异常对象
- 手动生成一个异常对象,并抛出(throw)
示例1
package com.dreamcold.exception;
public class Demo06 {
public static void main(String[] args) {
Student student=new Student();
try {
student.regist(-100);
}catch (RuntimeException e){
e.printStackTrace();
}
}
}
class Student{
private int id;
public void regist(int id){
if(id>0){
this.id=id;
}else {
throw new RuntimeException("参数格式不对");
}
}
}
结果
用户自定义异常
- 继承于现有的异常结构比如RuntimeException或者Exception
- 提供全局常量serialVersionUID号,相当于对类的标识,为了将来序列化
- 重载构造器
示例1
package com.dreamcold.exception;
public class MyException extends RuntimeException {
static final long serialVersionUID = -7034897190745766939L;
public MyException() {
super();
}
public MyException(String message) {
super(message);
}
}
package com.dreamcold.exception;
public class Demo06 {
public static void main(String[] args) {
Student student=new Student();
try {
student.regist(-100);
}catch (RuntimeException e){
e.printStackTrace();
}
}
}
class Student{
private int id;
public void regist(int id){
if(id>0){
this.id=id;
}else {
throw new MyException("参数格式不对!");
}
}
}