Java异常:当我们编写程序出现错误的时候,系统就会报异常,异常也就是程序在运行时出现的不正常情况,出现的问题有很多种,比较常见的有空指针异常、数组越界异常、文件未找到、算术条件异常等。这些问题都有共性内容:每个问题都有对应的名称,都有问题描述的信息,问题的位置,所以可以不断地向上抽取,形成了异常体系。
Java 异常其实是对不正常情况的一种描述,并将其封装成对象; Java 在设计异常体系时,将容易出现的异常情况都封装成了对象。异常是要尽量避免的,如果避免不了,就需要提前给出处理方式。
我们的Java异常体系分为两大类(可抛出,都继承Throwable父类):
一、Error:错误,一般情况下,不编写针对性的代码进行处理,通常是JVM发生的,程序无法处理的,比如系统崩溃、虚拟机错误、动态链接失败等,这个我们是阻止不了的,这种错误无法恢复或不可能捕获,它将导致应用程序中断。
通常应用程序是无法处理这些错误的,因此应用程序不应该试图使用catch块来捕获Error对象。在定义该方法时,也无需在其throws子句中声明该方法可能抛出Error及其任何子类。
二、Exception:异常,我们可以进行针对性的处理。
处理方式有两种:捕捉异常(try…catch…)和抛出异常(throw、throws)
- 捕捉异常(try…catch…):直接处理可能出现的异常;
定义格式:可以同时捕捉多个异常
try{
需要被检测的代码;
}catch(异常类 变量名){
异常处理代码1;
}
...
catch(异常类 变量名){
异常处理代码2;
}finally{
一定会执行的代码(通常用来关闭资源)
}
数组索引越界异常:
public class ExceptionDemo {
public static void main(String[] args) {
//数组索引越界ArrayIndexOutOfBoundsException
String[] strs={"1"};
intDivide(strs);
}
public static void intDivide(String[] strs){//String数组
try {//try块
int a=Integer.parseInt(strs[0]);
int b=Integer.parseInt(strs[1]);
} catch(ArrayIndexOutOfBoundsException ae){
System.out.println("数组索引越界:输入的参数个数不够");
//将该异常的跟踪栈信息输出到标准错误输出
ae.printStackTrace();//打印错误的追踪信息
try{//异常中嵌套异常,打印异常txt文件
ae.printStackTrace(new PrintStream(new File("E:/z/ArrayIndexOutOfBoundsException.txt")));
}catch(FileNotFoundException e1){
e1.printStackTrace();
}
} catch(Exception e){ //其它异常
e.printStackTrace();
}
}
}
执行结果:
使用try…catch捕获异常处理原则:
功能抛出几个异常,功能调用如果进行 try 处理,需要与之对应的 catch 处理代码块,这样的处理有针对性,抛几个就处理几个。
- 不要过度使用异常,不能使用异常处理机制来代替正常的流程控制语句
- 异常捕获时,一定要先捕获小异常,再捕获大异常。否则小异常将无法被捕获 3. 避免出现庞大的try块
- 避免使用catch(Exception e){},要有针对性的处理,抛出什么异常就应该要有对应的catch处理
- 不要忽略异常
抛出异常(throw、throws):
throw:用于抛出异常对象,后面跟的是异常对象; throw用在函数内。抛出的是一个异常实例,而且每次只能抛出一个异常实例。
public class ThrowDemo {
public static void main(String[] args) throws Exception{
String[] strs={"1"};//数组索引越界ArrayIndexOutOfBoundsException
try {
intDivide(strs);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void intDivide(String[] strs) throws Exception {//手动抛异常
try {//try块
int a=Integer.parseInt(strs[0]);
int b=Integer.parseInt(strs[1]);
} catch (ArrayIndexOutOfBoundsException ae) {
throw new Exception("数组索引越界");
}catch (Exception e) {
System.out.println("其他异常");
e.printStackTrace();
}
}
}
执行结果:
throws:用于抛出异常类,在方法上抛出可能出现异常的类型,后面跟的是异常类名,可以同时有多个异常类,用逗号隔开。 throws 用在函数上。当前方法不知道如何处理这种类型的异常,该异常应该由上级调用者处理;如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理
定义格式:
public 返回值类型 方法名(参数列表) throws 异常类1,异常类2,异常类3,…{ }
public class ThrowsDemo {
public static void main(String[] args) throws Exception{//抛给Java虚拟机处理
//算术异常(除零)ArithmeticException
String[] strs={"21","0"};
try {
intDivide(strs);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void intDivide(String[] strs) throws ArithmeticException{
int c=a/b;
}
}
执行结果:
定义异常处理时,什么时候使用try…catch,什么时候使用throws 呢?
功能内部如果出现异常,如果内部可以处理,就用try ;
如果功能内部处理不了,就必须声明出来,让调用者处理。使用 throws 抛出,交给调用者处理。谁调用了这个功能谁就是调用者;
Java的Exception异常分为两种:Checked异常和Runtime异常(运行时异常)。
Runtime异常:运行时异常,所有的RuntimeException类及其子类的实例,也就说这个异常是编译时不被检查的异常;
Checked异常:编译时被检查的异常,只要是 Exception 及其子类都是编译时被检测的异常,也就是除了RuntimeException类及其子类的异常实例
Runtime异常和Checked异常两者之间的区别:
1、Checked异常在函数内被抛出,函数必须要声明,否则编译失败。只有Java语言提供了Checked异常,Java认为Checked异常都是可以被处理(修复)的异常,所以Java程序必须显式处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误,无法通过编译。
声明的原因:是需要调用者对该异常进行处理。
2、Runtime异常则更加灵活,Runtime异常如果在函数内被抛出,在函数上无需显式声明抛出,如果程序需要捕获Runtime异常,也可以使用try…catch块来实现,但是一旦出现异常,程序将会中断,若采用异常处理,则会被相应的程序执行处理。
不声明的原因:不需要调用者处理,运行时异常发生,已经无法再让程序继续运行,所以不让调用处理的,直接让程序停止,由调用者对代码进行修正。
自定义异常:当开发时,项目中出现了java中没有定义过的问题时,这时就需要我们按照 java 异常建立思想,将项目的中的特有问题也进行对象的封装。这个异常,称为自定义异常。
用户自定义异常都应该继承Exception基类;如果希望自定义Runtime异常,则应该继承RuntimeException基类。定义异常类时通常需要提供两个构造器:一个是无参数的构造器;另一个是带字符串参数的构造器,这个字符串将作为该异常对象的描述信息(也就是异常对象的getMessage()方法的返回值)
自定义除零异常:
public class CustomException {
public static void main(String[] args) {
try {
sink(3,0);
} catch (DivideZeroException e) {//捕获自定义异常
e.printStackTrace();
}
}
public static int sink(int x,int y) throws DivideZeroException{
if(y==0){ // 抛出自定义DivideZeroException异常
throw new DivideZeroException("您输入的除数是"+y+",除数不能为0!");
}
int m=x/y;
return m;
}
}
//自定义除零异常
class DivideZeroException extends Exception{
private static final long serialVersionUID = 1L;
//提供一个无参构造器
public DivideZeroException(){
}
//一个字符串参数的构造器
public DivideZeroException(String msg){
super(msg);//把参数调用给super,把参数加入堆栈信息
}
}
Java7多异常捕获:一个catch块可以捕获多种类型的异常(不建议使用),多异常捕获之后不好区分异常的类型。
捕获多种类型的异常时需要注意:多种异常类型之间用竖线(|)隔开;异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值。
finally:它是异常的统一出口,不管try…catch代码块出现何种异常,最后的finally块中的语句总是会执行。除非在try…catch块中调用了JVM退出的方法System.exit(0);否则finally块是一定会执行的, 它主要是用来关闭物理资源,无论是否发生异常,资源都必须进行关闭。
public class FinallyDemo {
public static void main(String[] args){
RandomAccessFile accessFile=null;
try {//如果对象文件不存在,在打开流的情况下,IO流是不能被关闭的,因为根本就执行不到关闭流的代码,它已经出现了异常
accessFile=new RandomAccessFile("E:/z/Array.java","r");//只读模式
accessFile.writeChars("12345");//写入字符串
System.out.println("-------");
accessFile.close();//不会执行到这
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{//一定会执行的代码块
if(accessFile!=null){
System.out.println("--12345--");
try {//关闭流
accessFile.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
}
}
}
执行结果: