【Java SE】异常

目录

1.异常概述

1.1 生活中的异常

1.2 程序的异常

1.3 异常的抛出机制

1.4 如何对待异常

2.异常体系

2.1 异常体系图

2.2 Throwable

2.3 异常分类

2.3 常见的Error

2.3.1 StatckOverflowError

2.3.2  OutOfMemoryError

2.4 编译时异常

2.5 运行时异常

3.常见的运行时异常、编译时异常

3.1 运行时异常

3.1.1 NullPointerException

3.1.2  ArithmeticException

3.1.3 ArrayIndexOutOfBoundsException

3.1.4 ClassCastException

3.1.5 NumberFormatException

3.2 编译时异常  

4.异常的处理机制

4.1 异常处理概述

4.2 方式1:try-catch-finally

4.2.1 基本语法

4.2.2 整体的执行流程

4.2.3 try

4.2.4 catch(Exceptiontype e)

4.2.5 使用举例

4.2.6 finally使用及案例

4.3 方式2:throws+异常

4.3.1 throws处理机制图

4.3.2 基本语法

4.3.3 使用细节

5.手动抛出异常对象:throw

5.1 语法格式

5.2 使用的注意事项

6.自定义异常

6.1 为什么需要自定义异常

6.2 如何自定义异常类

6.3 注意事项

6.4 案例

 7.throws和throw的对比

8.练习题

8.1 练习题1 

8.2 练习题2

8.3 练习题3

8.4 练习题4


1.异常概述

1.1 生活中的异常

小明每天开车上班,正常车程 1 小时,但是可能会出意外情况。比如说堵车了、车没油了、出现交通事故了等等

出现意外,即为异常情况。如果不处理异常情况,则到不了公司。所以需要对不同的异常情况做相应的处理,处理完后则可以正常开车去公司

1.2 程序的异常

异常 :指的是程序在执行过程中,出现的非正常情况,如果不处理最终会导致 JVM 的非正常停止

在使用计算机语言进行开发的过程中,即使程序员把代码写得 "尽善尽美" ,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式问题,读取文件是否存在,网络是否始终保持通畅等等

补充:异常指的并不是语法错误和逻辑错误

语法错误:编译不通过,不会产生 .class 字节码文件,不能执行程序

逻辑错误:只是没有得到想要的结果,例如:求 ab 的和 a+b,写成了 a-b

1.3 异常的抛出机制

Java中是如何表示不同的异常情况,又是如何让程序员得知,并处理异常的呢?

Java 中把不同的异常用不同的类表示,一旦发生某种异常,就会创建该异常类型的对象,并且抛出(throw)。然后程序员可以捕获 (catch)到这个异常对象,并处理;如果没有捕获(catch)这个异常对象,那么这个异常对象将会导致程序终止

代码案例

下面的代码会产生一个数组下标越界异常 ArrayIndexOfBoundsException 。通过图解来解析异常产生和抛出的过程  

class ArrayTools{
    //对给定的数组通过给定的下标获取元素
    public static int getElement(int[] arr,int index){
        int element = arr[index];
        return element;
    }
}

class ExceptionDemo{
    public static void main(String[] args){
        int[] arr = {34,12,67};
        int num = ArrayTools.getElement(arr,4);
        System.out.println("num="+num);
        System.out.println("over");
    }
}



图解异常产生和抛出的过程

1.4 如何对待异常

对于程序出现的异常,一般有两种解决方法:

① 遇到错误就终止程序的运行,但这样处理系统健壮性太差

② 程序员在编写程序时,就充分考虑到各种可能发生的异常和错误,极力预防和避免。实在无法避免的,要编写相应的代码进行异常的检测、以及异常的处理,保证代码的健壮性。Java 中通常采用的处理异常方式: throws+异常类型、 try-catch-finally块

2.异常体系

2.1 异常体系图

2.2 Throwable

java.lang.Throwable 类是 Java 程序执行过程中发生的异常事件对应的类的根父类 

Throwable中的常用方法:

public void printStackTrace():打印异常的详细信息

    包含了异常的类型、异常的原因、异常出现的位置、在开发和调试阶段都得使用 printStackTrace

② public String getMessage():返回异常的字符串描述信息


2.3 异常分类

Java 中的异常情况可分为两个大类:ErrorException,分别对应着 java.lang.Errorjava.lang.Exception 两个类

Error:Java 虚拟机无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等严重情况。比如栈溢出 StackOverflowError 和 内存不足OOM(out of memory)。Error 是严重错误,程序会直接崩溃,所以一般不编写针对性的代码进行处理

Exception:因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理,使程序继续运行。否则一旦发生异常,程序也会挂掉。例如:空指针访问、试图读取不存在的文件、网络连接中断、数组下标越界等等

Exception又包含两类:编译时异常【编程时,编译器检测出的异常】和运行时异常【程序执行时,发生的异常】

2.3 常见的Error

最常见的就是 VirtualMachineError,它有 2 个经典的子类:

① StackOverflowError:栈溢出

② OutOfMemoryError:内存不足


2.3.1 StatckOverflowError

案例

public class demo {
    public static void main(String[] args) {
        new TestStackOverflowError().recursion();
    }
}
class TestStackOverflowError {
    public void recursion(){ //递归方法
        recursion();
    }
}

 执行结果

2.3.2  OutOfMemoryError

案例

public class demo{
    public static void main(String[] args) {
        //OutOfMemoryError
        int[] arr = new int[Integer.MAX_VALUE];
    }
}

执行结果  

2.4 编译时异常

编译时异常:也叫 checked(受检) 异常

在编译阶段,编译器就能明确警示当前代码可能发生(不是一定发生)xx异常,并要求程序员必须提前编写处理该异常的代码。如果没有编写对应的异常处理代码,则编译器就会直接判定编译失败,从而不能生成字节码 .class 文件。

 编译时异常的发生通常不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如:FileNotFoundException(文件找不到时发生的异常) 

代码案例

import java.io.FileInputStream;

public class demo {
    public static void main(String[] args) {
        FileInputStream fis;
        fis = new FileInputStream("d:\\aa.jpg");
        int len;
        while ((len = fis.read()) != -1){
            System.out.println(len);
        }
        fis.close();
    }
}

解决方案

2.5 运行时异常

运行时异常:也叫 unchecked(非受检) 异常

在代码编写阶段,运行时异常编译器检查不出来。只有等代码运行起来并确实发生了xx异常,它才能被发现

运行时异常通常是由程序员的代码编写不当引起的,只要细心检查就可以避免

 java.lang.RuntimeException 类及它的子类都是运行时异常。对于运行时异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响

代码案例 

最经典的运行时异常案例:两数相除,分母为 0 ,发生 ArithmeticException 异常 

public class demo {
    public static void main(String[] args) {
        int num1 = 100;
        int num2 = 0;
        System.out.println(num1/num2);
    }
}
/*控制台输出
Exception in thread "main" java.lang.ArithmeticException: / by zero
*/

3.常见的运行时异常、编译时异常

3.1 运行时异常

① NullPointerException:空指针异常

② ArithmeticException:数学运算异常

③ ArrayIndexOutOfBoundsException:数组下标越界异常

④ ClassCastException:类型转换异常

⑤ NumberFormatException:数字格式不正确异常


3.1.1 NullPointerException

NullPointerException:空指针异常

发生时机:当程序试图在需要对象的地方使用 null 时,抛出该异常

案例 

3.1.2  ArithmeticException

ArithmeticException:数学运算异常

发生时机:当出现异常的运算条件时,抛出该异常。最经典的例子就是两数相除,分母为 0

案例

3.1.3 ArrayIndexOutOfBoundsException

ArrayIndexOutOfBoundsException:数组下标越界异常

发生时机:用非法索引访问数组时抛出的异常。非法索引:索引为负或者大于等于数组大小

案例

3.1.4 ClassCastException

ClassCastException:类型转换异常

发生时机:当试图将对象强制转换为不是实例的子类时,抛出该异常

案例 

3.1.5 NumberFormatException

NumberFormatException:数字格式不正确异常

发生时机:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常

案例 

3.2 编译时异常  

① SQLException:操作数据库时,查询表可能发生的异常

② IOException:操作文件时,可能发生的异常

③ FileNotFoundException:找不到文件的异常

④ IllegalArgumentException:参数异常


4.异常的处理机制

4.1 异常处理概述

在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行 x/y 运算时,要检测分母是否为0,数据为空,输入的不是数据而是字符

过多的 if-else 分支会导致程序的代码加长、臃肿,可读性差,程序员需要花很大的精力"堵漏洞"。因此采用异常处理机制

Java异常处理

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

Java异常处理方式 

① try-catch-finally

② throws + 异常类型

4.2 方式1:try-catch-finally

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

如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。这一过程称为:捕获(catch)异常

4.2.1 基本语法

捕获异常语法如下:

try{
	......	//可能产生异常的代码
}
catch( 异常类型1 e ){
	......	//当产生异常类型1型异常时的处置措施
}
catch( 异常类型2 e ){
	...... 	//当产生异常类型2型异常时的处置措施
}  
finally{
	...... //无论是否发生异常,finally都会执行
} 

4.2.2 整体的执行流程

如果在程序运行时,try块 中的代码没有发生异常,那么 catch 所有的分支都不执行
如果在程序运行时,try块 中的代码发生异常,根据异常对象的类型,将从上到下选择第一个匹配的 catch 分支执行。此时 try块 中发生异常的语句之后的代码将不执行,而在整个 try-catch块 之后的代码可以继续运行
如果在程序运行时,try块 中的代码发生了异常,如果所有 catch 分支都无法匹配(捕获)这个异常,那么 JVM 将会终止当前方法的执行,并把异常对象抛 (throw) 给调用者。如果调用者不处理,程序终止

4.2.3 try

捕获异常的第一步是用 try{...} 选定捕获异常的范围,将可能出现异常的业务逻辑代码放在 try{...}

4.2.4 catch(Exceptiontype e)

catch(){}分支,分为两个部分, catch() 中编写异常类型和异常形参名, {} 中编写处理该异常的代码

如果明确知道产生的是何种异常,可以用该异常类作为 catch 的参数类型,也可以用该异常的父类作为 catch 的参数类型

public class demo {
    public static void main(String[] args) {
        try{
            System.out.println(100/0);//发生ArithmeticException
        }catch (RuntimeException e){//catch可以用其父类RuntimeException来接收
            //处理异常代码
        }
    }
}


每个 try 语句块可以伴随一个或多个 catch 语句,用于处理可能产生的不同类型的异常对象

如果有多个 catch 分支,并且多个异常类型有父子类关系,必须保证小的子异常类型在上,大的父异常类型在下。否则编译不通过

catch 中常用异常处理的方式:

  • public String getMessage():返回异常的字符串描述信息

  • public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。包含了异常的类型、异常的原因、异常出现位置,在开发和调试阶段,都得使用 printStackTrace()

4.2.5 使用举例

案例1:数组下标访问越界ArrayIndexOutOfBoundsException

public class demo {
    public static void main(String[] args) {
        String friends[] = { "lisa", "bily", "kessy" };
        try {
            System.out.println(friends[5]);//数组下标访问越界,发生异常
            System.out.println("异常语句后的代码");
        }catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("index err");
        }catch (NullPointerException e){
            System.out.println("null err");
        }

        System.out.println("try-catch块后的代码");
    }
}

案例1执行结果 

 案例2:空指针异常NullPointerException

public class demo{
    public static void main(String[] args) {
        try{
            String str1 = null;
            System.out.println(str1.charAt(0));//空指针异常
        }catch(NullPointerException e){
            //异常的处理方式1
            System.out.println("出现了空指针异常");
        }catch(ClassCastException e){
            //异常的处理方式2
            System.out.println("出现了类型转换的异常");
        }catch(RuntimeException e){
            //异常的处理方式3
            System.out.println("出现了运行时异常");
        }

        System.out.println("try-catch块后的代码");

    }
}

 案例2执行结果

4.2.6 finally使用及案例

因为异常会引发程序跳转,从而会导致有些语句执行不到。而程序中有一些特定的代码无论异常是否发生都需要执行。例如,数据库连接、输入流输出流、Socket连接、Lock锁的关闭等,所以这样的代码就可以放到 finally 中。所以,通常将一定要被执行的代码声明在 finally

finally 语句和 catch 语句是可选的,但 finally、catch 都不能单独出现,只能和 try 配合使用

不论在 try 代码块中是否发生了异常事件,是否有 catch 块, catch 块语句是否执行,catch 语句是否有异常,catch 语句中是否有 returnfinally 块中的语句都会被执行。唯一的例外就是,使用 System.exit(0) 来终止当前正在运行的 JVM ,则 finally 中的代码不会执行

public class demo{
    public static void main(String[] args) {
        try{
            System.exit(0);
        }catch (Exception e){
            System.out.println("catch");
        }finally {
            System.out.println("finally");
        }
    }
}

4.3 方式2:throws+异常

如果一个方法中某句代码可能发生某种异常,但是并不能确定如何处理这种异常,则该方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理

4.3.1 throws处理机制图

4.3.2 基本语法

修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{
    
}  

throws 后面可以写多个异常类型,用逗号隔开。与 catch 中一致,throws 抛出的异常类型可以是其父类  

public void readFile(String file)  throws FileNotFoundException,IOException {
	...
	// 读文件的操作可能产生FileNotFoundException或IOException类型的异常
	FileInputStream fis = new FileInputStream(file);
    //...
}

4.3.3 使用细节

(1)对于编译时异常,程序中必须处理,使用 try-catch 或者 throws

    (2)如果方法 f1 使用 throws 处理了编译时异常,而 f2 方法调用了 f1 方法,这相当于在 f2 方法中抛出了一个编译时异常,f2 必须使用 try-catch 或者 throws 处理,否则编译不通过

对于运行时异常Java 中是有默认处理机制的:程序中如果没有处理,默认就是 throws 的方式处理

证明如下,两种情况下控制台的输出结果是完全一样的,这也证明是正确的:

子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的类型要么和父类的保持一致,要么为父类抛出的异常类型的子类型。这跟子类重写父类方法时不能缩小方法的访问权限是一个道理

throws 过程中,如果有方法使用 try-catch 处理了异常,就可以不必再使用 throws 了。另外异常在此处终止了向上 throws  

5.手动抛出异常对象:throw

Java 中异常对象的生成有两种方式:

JVM 自动生成:程序运行过程中,JVM 检测到程序发生了问题,那么针对当前代码,就会在底层自动创建一个对应的异常类型的实例对象并抛出
由开发人员手动创建: throw new 异常类型([实参列表]);

5.1 语法格式

throw new 异常类名(参数);

5.2 使用的注意事项

throw语句抛出的异常对象,其处理方式如下:

  • 如果是编译时异常类型的对象,同样需要使用 throws 或者 try-catch 处理,否则编译不通过

  • 如果是运行时异常类型的对象,不做处理编译仍然可以通过

  • 可以抛出的异常必须是Throwable或是其子类。下面的语句在编译时将会产生语法错误:

throw new String("want to throw");

throw 的无论是编译时异常类型的对象,还是运行时异常类型的对象,如果没有使用 try-catch  处理,都会导致程序崩溃  

(1)使用 try-catch 处理 throw 的异常:在 try 块中 throw 语句后的代码不执行,finally 中的代码正常执行,finally 后的代码正常执行

public class demo{
    public static void main(String[] args){
        try{
            System.out.println("hello");
            throw new RuntimeException("抛出异常");
        }catch (RuntimeException e){//上面的throw的异常会被这里的catch捕获到
            System.out.println(e.getMessage());//输出"抛出异常"
        }finally {
            System.out.println("finally代码块");
        }

        System.out.println("程序继续执行");

    }
}

 (2)没有使用 try-catch 处理 throw 的异常:throw 语句后不能编写代码,否则编译不通过

 (3)使用 throws 处理 throw 的异常:throw 语句后仍然不能编写代码,否则编译不通过

如果当前方法没有使用 try-catch 处理 throw 的异常,throw 语句就会代替 return 语句提前终止当前方法的执行,并返回一个异常对象给调用者


6.自定义异常

6.1 为什么需要自定义异常

Java 中不同的异常类,分别表示着某一种具体的异常情况

但是在开发中总是有些异常情况是在 Throwable 及其子类中没有定义好的,此时需要根据自己业务的异常情况来定义异常类。例如年龄范围问题,考试成绩负数问题,某员工已在团队中

6.2 如何自定义异常类

要选择继承一个异常类型

  • 自定义一个编译时异常类型:自定义类继承 java.lang.Exception

  • 自定义一个运行时异常类型:自定义类继承 java.lang.RuntimeException

建议提供至少两个构造器,一个是无参构造,一个是(String message)构造器

自定义异常需要提供 serialVersionUID

在6.4部分会举案例

6.3 注意事项

自定义的异常只能通过 throw 抛出

自定义异常最重要的是异常类的名字和 message 属性。message 继承认于父类,用来描述异常原因。当异常出现时,可以输出异常原因。比如:AgeException("年龄不符合正常范围")、GradeException("成绩需为正数")

自定义异常对象只能由程序员用 throw 手动抛出。抛出后由 try-catch 处理,也可以甩锅 throws 给调用者处理

6.4 案例

使用 throws 处理异常,throw 后的所有代码不执行  

public class demo {
    public static void main(String args[]) throws MyException {
        int age = 130;
        //要求age范围在18-120之间,否则抛出一个自定义异常
        if(!(age>=18&&age<=120)){
            throw new MyException("年龄需要在18-120之间");
        }

        System.out.println("程序继续执行");
    }
}

//自定义一个编译时异常类
class MyException extends Exception {
    static final long serialVersionUID = 23423423435L;
    public MyException(){}

    public MyException(String message) {
        super(message);
    }
}

使用 try-catch-finally 处理异常,try-catch-finally 后的代码正常执行  

public class demo {
    public static void main(String args[]){
        try{
            int age = 130;
            //要求age范围在18-120之间,否则抛出一个自定义异常
            if(!(age>=18&&age<=120)){
                throw new MyException("年龄需要在18-120之间");
            }
        }catch (Exception e){//上面抛出的异常会被catch捕获,e.getMessage =  "年龄需要在18-120之间"
            System.out.println(e.getMessage());
        }finally{
            System.out.println("finally");
        }

        System.out.println("程序继续执行");
    }
}

//自定义一个编译时异常类
class MyException extends Exception {
    static final long serialVersionUID = 23423423435L;
    public MyException(){}

    public MyException(String message) {
        super(message);
    }
}

 7.throws和throw的对比

意义 位置 后面跟的东西
throws 处理异常的方式之一 方法声明处 异常类型
throw 手动生成异常对象 方法体中 异常对象

8.练习题

8.1 练习题1 

写出程序结果

public class ReturnExceptionDemo {
	public static void main(String[] args) {
    	try {
    	    methodA();
   		} catch (Exception e) {//A方法throw的异常被这个catch块捕获,e.getMessage = "制造异常"
    	  	System.out.println(e.getMessage());//(3)
    	}
    	methodB();
  	}
    static void methodA() {
        try {
            System.out.println("进入方法A");//(1)
            throw new RuntimeException("制造异常");
        }finally {
            System.out.println("用A方法的finally");//(2)
        }
    }

    static void methodB() {
        try {
            System.out.println("进入方法B");//(4)
            return;
        } finally {
            System.out.println("调用B方法的finally");//(5)
        }
    }    
}
/*输出结果
进入方法A
用A方法的finally
制造异常
进入方法B
调用B方法的finally
*/

8.2 练习题2

写出程序结果

public class demo {
    public static void main(String[] args) {
        try{
            showExce();
            System.out.println("A");
        } catch (Exception e) {
            System.out.println("B");
        }finally {
            System.out.println("C");
        }
        System.out.println("D");
    }
    public static void showExce() throws Exception{
        throw new Exception();
    }
}
/*
B
C
D
*/

8.3 练习题3

 写出程序结果

public class demo {
    public static void main(String[] args) {
        try{
            func();
            System.out.println("A");
        } catch (Exception e) {
            System.out.println("C");//(2)
        }
        System.out.println("D");//(3)
    }
    public static void func(){
        try{
            throw new RuntimeException();
        }finally {
            System.out.println("B");//(1)
        }
    }
}
/*
B
C
D
*/

8.4 练习题4

以下代码是否会发生异常?如果会,是哪种异常?

public class demo {
    public static void main(String[] args) {
        if (args[4].equals("蔡徐坤")){//❌,发生NullPointerException
            System.out.println("唱跳rap篮球");
        }else{
            System.out.println("哎哟你干嘛");
        }

        Object o = args[2];//✔,向上转型
        Integer i = (Integer) o;//❌,发生classCastException
    }
}

猜你喜欢

转载自blog.csdn.net/m0_55908255/article/details/146520316