优秀的编程知识分享平台

网站首页 > 技术文章 正文

Java基础——异常

nanyue 2024-11-25 15:29:07 技术文章 2 ℃

一、异常机制

异常机制的本质是:当程序出现异常,程序安全的退出、处理完后继续执行的机制。

1.1异常(Exception)的概念

异常指程序运行过程中出现的非正常现象,例如用户输入错误、除数为零、需要处理的文件不存在、数组下标越界等。

在 Java 的异常处理机制中,引进了很多用来描述和处理异常的类,称为异常类。异常类定义中包含了该类异常的信息和对异常进行处理的方法。

所谓异常处理,就是指程序在出现问题时依然可以正确的执行完。

示例:一个异常对象,分析异常机制是如何工作的

package cn.pxy.test;

public class Test {
	public static void main(String[] args) {
		System.out.println("111");
		int a=1/0;//ArithmeticException算术异常
		System.out.println("222");
	}
}

运行结果:

根据结果,我们可以看到执行“1/0”时发生了异常,程序终止了,没有执行后面的打印“222”的动作。

如果使用 try-catch 来处理,程序遇到异常可以正常的处理,处理完成后,程序继续往下执行:

package cn.pxy.test;

public class Test {
	public static void main(String[] args) {
		System.out.println("111");
		try {
			int a=1/0;
		}catch(Exception e) {
			e.printStackTrace();
		}
		System.out.println("222");
	}
}

运行结果:

程序在执行“1/0”仍然遇到异常,然后进行 try-catch 处理。处理完毕后,程序继续往下执行,打印了“222”内容。

Java 是采用面向对象的方式来处理异常的。处理过程:

1.抛出异常:在执行一个方法时,如果发生异常,则这个方法生成代表该异常的一个对象,停止当前执行路径,并把异常对象提交给 JRE。

2.捕获异常:JRE 得到该异常后,寻找相应的代码来处理该异常。JRE 在方法的调用栈中查找,从生成异常的方法开始回溯,直到找到相应的异常处理代码为止。

1.2 异常分类

JDK 中定义了很多异常类,这些类对应了各种各样可能出现的异常事件,所有异常对象都是派生于 Throwable 类的一个实例。如果内置的异常类不能够满足需要,还可以创建自己的异常类。

Java 对异常进行了分类,不同类型的异常分别用不同的 Java 类表示,所有异常的根类为 java.lang.Throwable,Throwable 下面又派生了两个子类:Error 和 Exception。Java异常类的层次结构如图:

1.2.1Error

Error 是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。Error 表明系统 JVM 已经处于不可恢复的崩溃状态中。我们不需要管他。

1.2.2Exception

Exception 是程序本身能够处理的异常,如:空指针异常(NullPointerException)、数 组 下 标 越 界 异 常 ( ArrayIndexOutOfBoundsException ) 、 类 型 转 换 异 常(ClassCastException)、算术异常(ArithmeticException)等。

Exception 类是所有异常类的父类,其子类对应了各种各样可能出现的异常事件。 通常 Java 的异常可分为:

1. RuntimeException;运行时异常

2. CheckedException;已检查异常

1.2.3RuntimeException运行时异常

派生于 RuntimeException 的异常,如被 0 除、数组下标越界、空指针等,其产生比较频繁,处理麻烦,如果显式的声明或捕获将会对程序可读性和运行效率影响很大。 因此由系统自动检测并将它们交给缺省的异常处理程序(用户可不必对其处理)。

这类异常通常是由编程错误导致的,所以在编写程序时,并不要求必须使用异常处理机制来处理这类异常,经常需要通过增加“逻辑处理来避免这些异常”。

1.2.4CheckedException已检查异常

所有不是 RuntimeException 的异常,统称为 Checked Exception,又被称为“已检查异常”,如 IOException、SQLException 等以及用户自定义的 Exception 异常。 这类异常在编译时就必须做出处理,否则无法通过编译。

异常的处理方式有两种:使用“try/catch”捕获异常、使用“throws”声明异常。

二、异常的处理

2.1 异常的处理方式之一:捕获异常

捕获异常是通过 3 个关键词来实现的:try-catch-finally。用 try 来执行一段程序,如果出现异常,系统抛出一个异常,可以通过它的类型来捕捉(catch)并处理它,最后一步是通过 finally 语句为异常处理提供一个统一的出口,finally 所指定的代码都要被执行(catch 语句可有多条;finally 语句最多只能有一条,根据自己的需要可有可无)。如图:

上面过程详解:

1.try:try 语句指定了一段代码,该段代码就是异常捕获并处理的范围。在执行过程中,当任意一条语句产生异常时,就会跳过该条语句中后面的代码。代码中可能会产生并抛出一种或几种类型的异常对象,它后面的 catch 语句要分别对这些异常做相应的处理。一个 try 语句必须带有至少一个 catch 语句块或一个 finally 语句块 。

注:当异常处理的代码执行结束以后,不会回到 try 语句去执行尚未执行的代码。

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

常用方法,这些方法均继承自 Throwable 类 。

toString ()方法,显示异常的类名和产生异常的原因。

getMessage()方法,只显示产生异常的原因,但不显示类名。

printStackTrace()方法,用来跟踪异常事件发生时堆栈的内容。

catch 捕获异常时的捕获顺序:如果异常类之间有继承关系,在顺序安排上需注意。越是顶层的类,越放在下面,再不然就直接把多余的 catch 省略掉。 也就是先捕获子类异常再捕获父类异常。

3.finally:有些语句,不管是否发生了异常,都必须要执行,那么就可以把这样的语句放到 finally 语句块中。通常在 finally 中关闭程序块已打开的资源,比如:关闭文件流、释放数据库连接等。

4.try-catch-finally 语句块的执行过程:

程序首先执行可能发生异常的 try 语句块。如果 try 语句没有出现异常则执行完后跳至finally 语句块执行;如果 try 语句出现异常,则中断执行并根据发生的异常类型跳至相应的catch 语句块执行处理。catch 语句块可以有多个,分别捕获不同类型的异常。catch 语句块执行完后程序会继续执行 finally 语句块。finally 语句是可选的,如果有的话,则不管是否发生异常,finally 语句都会被执行。

注:1.即使 try 和 catch 块中存在 return 语句,finally 语句也会执行。是在执行完 finally语句后再通过 return 退出。2.finally 语句块只有一种情况是不会执行的,那就是在执行 finally 之前遇到了System.exit(0)结束程序运行。

package cn.pxy.test;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Test {
	public static void main(String[] args) {
		FileReader reader=null;
		try {
			reader=new FileReader("d:/a.txt");
			char c=(char)reader.read();
			char c2=(char)reader.read();
			System.out.println(""+c+c2);
		} catch (FileNotFoundException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}finally {
			try {
				if(reader!=null) {
					reader.close();
				}
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
}

常用开发环境中,自动增加 try-catch 代码块的快捷键:

1. 将需要处理异常的代码选中。

2. IDEA 中,使用:ctrl+alt+t

3. eclipse 中,使用:ctrl+shift+z

2.2 异常的处理方式之二:声明异常(throws子句)

当 CheckedException 产生时,不一定立刻处理它,可以再把异常 throws 出去。在方法中使用 try-catch-finally 是由这个方法来处理异常。但是在一些情况下,当前方法并不需要处理发生的异常,而是向上传递给调用它的方法处理。

如果一个方法中可能产生某种异常,但是并不能确定如何处理这种异常,则应根据异常规范在方法的首部声明该方法可能抛出的异常。

如果一个方法抛出多个已检查异常,就必须在方法的首部列出所有的异常,之间以逗号隔开。

方法重写中声明异常原则:子类重写父类方法时,如果父类方法有声明异常,那么子类声明的异常范围不能超过父类声明的范围。

package cn.pxy.test;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Test {
	public static void main(String[] args) {
		try {
			readFile("joke.txt");
		}catch(FileNotFoundException e){
			System.out.println("所需文件不存在!");
		}catch(IOException e) {
			System.out.println("文件读写错误!");
		}
	}
	public static void readFile(String fileName) throws FileNotFoundException, IOException {
		FileReader in=new FileReader(fileName);
		int tem=0;
		try {
			tem=in.read();
			while(tem!=-1) {
				System.out.print((char)tem);
				tem=in.read();
			}
		}finally {
			if(in!=null) {
				in.close();
			}
		}
	}
}

2.3 try-with-resource 自动关闭Closable接口的资源

JAVA 中,JVM 的垃圾回收机制可以对内部资源实现自动回收,给开发者带来了极大的便利。但是 JVM 对外部资源(调用了底层操作系统的资源)的引用却无法自动回收,例如数据库连接,网络连接以及输入输出 IO 流等。这些连接就需要我们手动去关闭,不然会导致外部资源泄露,连接池溢出以及文件被异常占用等。

JDK7 之后,新增了“try-with-reasource”。它可以自动关闭实现了AutoClosable 接口的类,实现类需要实现 close()方法。”try-with-resources声明”,将 try-catch-finally 简化为 try-catch,这其实是一种语法糖,在编译时仍然会进行转化为 try-catch-finally 语句。

package cn.pxy.test;

import java.io.FileReader;

public class Test {
	public static void main(String[] args) {
		try(FileReader reader=new FileReader("d:/a.txt");){
			char c=(char)reader.read();
			char c2=(char)reader.read();
			System.out.println(""+c+c2);
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

2.4 自定义异常

在程序中,可能会遇到 JDK 提供的任何标准异常类都无法充分描述清楚我们想要表达的问题,这种情况下可以创建自己的异常类,即自定义异常类。

自定义异常类只需从 Exception 类或者它的子类派生一个子类即可。

自定义异常类如果继承 Exception 类,则为受检查异常,必须对其进行处理;如果不想处理,可以让自定义异常类继承运行时异常 RuntimeException 类。

习惯上,自定义异常类应该包含 2 个构造器:一个是默认的构造器,另一个是带有详细信息的构造器。

package cn.pxy.test;

public class TestMyException {
	public static void main(String[] args) {
		Person p=new Person();
		try {
			p.setName("Lincoln");
			p.setAge(-1);
		}catch(IllegalAgeException e) {
			e.printStackTrace();
		}
		System.out.println(p);
	}
}
class Person{
	private String name;
	private int age;
	
	public void setName(String name) {
		this.name=name;
	}
	
	public void setAge(int age) throws IllegalAgeException{
		if(age<0) {
			throw new IllegalAgeException("人的年龄不应该为负数");
		}
		this.age=age;
	}
	
	public String toString() {
		return "name is"+name+"and age is "+age;
	}
}
/**
 * 非法年龄异常,继承Exception
 * @author 胖咸鱼
 *
 */
class IllegalAgeException extends Exception{
	//默认构造器
	public IllegalAgeException() {
		
	}
	//带有详细信息的构造器,信息存储在message中
	public IllegalAgeException(String message) {
		super(message);
	}
}

运行结果:

注:要避免使用异常处理代替错误处理,这样会降低程序的清晰性,并且效率低下。处理异常不可以代替简单测试---只在异常情况下使用异常机制。不要进行小粒度的异常处理---应该将整个任务包装在一个 try 语句块中。异常往往在高层处理。

三、IDEA调试功能

进行调试的核心是设置断点。程序执行到断点时,暂时挂起,停止执行。就像看视频按下停止一样,可以详细的观看停止处的每一个细节。

3.1断点 breakpoint

程序运行到此处,暂时挂起,停止执行。我们可以详细在此时观察程序的运行情况,方便做出进一步的判断。

1. 设置断点:

(1) 在行号后面单击即可增加断点

(2) 在断点上再单击即可取消断点

3.2进入调试视图

通过如下三种方式都可以进入调试视图:

(1) 单击工具栏上的按钮:

(2) 右键单击编辑区,点击:debug ‘模块名’。

进入调试视图后,布局如下:

左侧为“浏览帧”:调试器列出断点处,当前线程正在运行的方法,每个方法对应一个“栈帧”。最上面的是当前断点所处的方法。

变量值观察区:调试器列出了断点处所在方法相关的变量值。我们可以通过它,查看变量的值的变化。

3.3 调试操作区

通过上图中的按钮进行调试操作,它们的含义如下:

中文名称

英文名称

图标

说明

单步调试:遇到方法跳过

step over


若当前执行的是一个方法,则会把这个

方法当做整体一步执行完。不会进入这

个方法内部

单步调试:遇到函数进入

step into


若当前执行的是一个自定义方法,则会

进入方法内部。JDK 内置方法不会进去

强制进入

force step into


可以跳进任何方法,包含 JDK 内置方法

跳出函数

step out


当单步执行到子方法内时,用 step out

就可以执行完子方法余下部分,并返回

到上一层方法

删除栈帧

drop frame


删除当前栈帧。跳回到上一个栈帧

执行的光标处

run to cursor


一直执行,到光标处停止,用在循环内部时,点击一次就执行一个循环

重新执行程序

rerun


重新执行所有程序

继续执行

resume


继续执行到下一个断点或者程序结束

停止程序

stop



查看所有断点信息

view breakpoints



最近发表
标签列表