提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、概述
在Java中,异常(Exception)和错误(Error)是两个不同的概念。错误分为语法错误、运行时错误、逻辑错误三类:
- 语法错误(编译错误):指未按照Java语法规则书写代码而产生的错误,一般IDE均能提示和标注语法错误之处。
- 运行时逻辑错误(语义错误):程序无语法错误,但运行时从外部获得不正确的数据从而导致错误,如将一个11位电话号码赋值给一个整形变量就会超出其数据范围而引发错误,这类错误会导致程序终止。
- 逻辑错误:人为编码时导致的错误,如,a+b写成a-b,这类错误并不会导致程序终止,但不能得到正确结果。
和错误不同,异常是指运行环境正常的情况下遇到的运行时错误,是非致命的,但也会导致程序的非正常终止,Java可捕获和处理异常,而错误是无法被程序本身所处理的。事实上,sun公司会将这些错误封装成Error对象,供sun公司自己使用,并不在程序员的考虑范围之内。
在Java中,通常用Exception及其子类来封装异常,使得异常以类的形式存在,每个异常类都可以创建异常对象。例:
package org.example.SimpleCode;
public class Demo {
public static void main(String[] args) {
RuntimeException runtimeException=new RuntimeException();
System.out.println(runtimeException);
}
}
二、基本使用
(一)常见继承结构图
其中,Object有子类Throwable(可抛出的),意为,无论是错误还是异常,它们都是可抛出的,其子类包括Error(错误,不可处理,直接退出JVM)、Exception(异常,可处理)。异常的两个子类常见子类:
- RuntimeException及其子类:运行时异常,编译阶段不会出现异常提醒,是指运行时出现的异常,即运行时才知道发生了异常,如,数组索引越界异常(java.lang.ArrayIndexOutOfBoundException)、空值对象的引用等(java.lang.NullPointerException)。
- ExceptionSubClass(这不是一个真正的类,而是一个泛指):编译时异常(javac编译),是Exception的直接子类,要求程序员在编写程序阶段必须先对这些异常进行处理,即,需要手动处理才能运行,否则编译器会报错。
(二)常用方法
由于异常(Exception)继承于父类Throwable,故而它继承了一些常用的方法:
package org.example.SimpleCode;
public class Demo {
public static void main(String[] args) {
try {
int i=2/0;
}catch (ArithmeticException arithmeticException){
System.out.println("运算出错");
System.out.println(arithmeticException.getMessage());
String string=arithmeticException.toString();
System.out.println(string);
arithmeticException.printStackTrace();
System.out.println("执行完毕");
}
}
}
(三)异常处理
①JVM默认的处理方式
JVM会将异常的名称、原因及其出现的位置等信息输出在控制台中,且,程序会在异常处停止运行。
②捕获异常
目的:使得当代码出现异常时会继续向下运行,而不会退出JVM。
语法格式1
try{
可能出现异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}
执行顺序:
- 当try代码块中代码全部执行完且未遇见问题时,就不会执行catch代码块。
- 当try代码块执行过程中出现异常时若该异常是catch中声明异常的子类时,则会被捕捉,转而执行catch内的代码。
例:
package org.example.SimpleCode;
public class Demo {
public static void main(String[] args) {
try {
System.out.println(2/0);
//此处出现了异常,程序就会在此处创建一个ArithmeticException对象,即,new ArithmeticException();此时会拿着此对象和catch中进行对比,若括号中变量可接受,就表示异常被捕获(此处注意父子类继承关系,若catch中是Exception e,则任何异常都可被捕获),再执行catch中的代码,当try...catch中的代码都被执行完,便会执行体系外的代码.
System.out.println("正在运行");
//可见,此代码并未被执行
}catch (ArithmeticException arithmeticException){
System.out.println("运算出错");
}
System.out.println("执行完毕");
}
}
语法格式2
try {
// 可能会发生异常的程序代码
} catch (Type1 id1){
// 捕获并处置try抛出的异常类型Type1
} catch (Type2 id2){
//捕获并处置try抛出的异常类型Type2
}finally {
// 无论是否发生异常,都将执行的语句块
}
执行顺序:
- 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句。
- 当try捕获到异常,catch语句块里没有处理此异常的情况:此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行。
- 当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句(即,catch语句块最多只会执行其中的一个)。
注,无论是否捕获异常,finally都会被执行,而当try块或catch中遇到return语句时,finally语句块会在方法返回之前被执行(若finally中有return语句,则try与catch中的return均不会被执行),只有以下四种情况finally代码块不会被执行:
- finally中出现异常。
- 前面代码使用了System.exit()退出了程序。
- 程序所有进程死亡。
- 关闭CPU。
且,在try、catch、finally中声明的变量都是临时变量,而它们的作用域都被包括在方法的作用域之内。
③抛出异常
throws:写在方法定义处,表示声明异常,告诉调用者,使用本方法可能会有哪些异常。
public void 方法()throws 异常类名1,异常类名2...{
...
}
throw:写在方法内,用于结束方法,手动抛出异常对象,交给调用者,且,,抛出语句下面的代码不会再执行。
public void 方法(){
throw new NullPointerException();
}
例:
package org.example.SimpleCode;
public class Demo {
public static void main(String[] args) {
int[] arr=null;
int max=0;
try{
max=getMax(arr);
}catch (NullPointerException nullPointerException){
System.out.println("空指针异常,无最大值");
System.exit(0);
}catch (ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException){
System.out.println("索引越界异常");
System.exit(0);
}
System.out.println("最大值为:"+max);
}
public static int getMax(int[] arr) throws NullPointerException,ArrayIndexOutOfBoundsException{
if(arr==null){
//手动创建一个异常对象,交给方法调用者处理,此时本方法将结束运行
throw new NullPointerException();
}
if(arr.length==0){
throw new ArrayIndexOutOfBoundsException();
}
int max=arr[0];
for(int i=0;i<arr.length;i++){
if(arr[i]>max)max=arr[i];
}
return max;
}
}
注:JDK8新特性,catch()异常之间可自小到大用"|"分割,减少代码冗余,例:
try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\\Download\\Javabean-addperson案例解析.docx");
// 进行数学运算
System.out.println(100 / 0); // 这个异常是运行时异常,编写程序时可以处理,也可以不处理。
} catch(FileNotFoundException | ArithmeticException | NullPointerException e) {
System.out.println("文件不存在?数学异常?空指针异常?都有可能!");
}
(四)自定义异常
在实际开发过程中,可根据需求来自行定义异常。
步骤:
- 定义异常类
- 声明继承关系
- 空参构造
- 带参构造
异常类NameFormatException.java,表示姓名格式出错
package org.example.MyException;
public class NameFormatException extends RuntimeException{
/*
* NameFormat:当前异常的名字,表示姓名格式化问题
* Exception:表示当前类是一个异常类
*
* 运行时异常:继承RuntimeException
* 编译时异常:继承Exception
* */
public NameFormatException(){
}
public NameFormatException(String message){
super(message);
}
}
异常类AgeOutOfBoundException.java,表示年龄范围越界
package org.example.MyException;
public class AgeOutOfBoundException extends RuntimeException{
public AgeOutOfBoundException() {
}
public AgeOutOfBoundException(String message) {
super(message);
}
}
实体类Student.java
public void setName(String name) throws NameFormatException {
int len=name.length();
if(len<2||len>10){
throw new NameFormatException(name+"格式有误,长度应在3~9之间");
}
Name = name;
}
public void setAge(int age) throws AgeOutOfBoundException{
if(age<18||age>40){
throw new AgeOutOfBoundException(age+"超出了范围");
}
this.age = age;
}
主函数
package org.example;
import org.example.MyException.AgeOutOfBoundException;
import org.example.MyException.NameFormatException;
import org.example.polo.Student;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
Student student=new Student();
System.out.println("请输入学生姓名:");
String name=scanner.nextLine();
try{
student.setName(name);
}catch (NameFormatException nameFormatException){
System.out.println(nameFormatException);
System.out.println("输入姓名错误");
}
System.out.println("请输入学生年龄");
String i=scanner.nextLine();
Integer age=Integer.parseInt(i);
try{
student.setAge(age);
}catch (AgeOutOfBoundException ageOutOfBoundException){
System.out.println(ageOutOfBoundException);
System.out.println("输入年龄错误");
}
System.out.println(student);
}
}
三、源码分析
算是留一个坑,考研+技术同时抓的话还是要以推进度为主,这里的源码分析在我完成后端技术的复习之后再来填这个坑。