异常
概述
java中将程序执行中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不是异常)
分类
异常事件可以分为两类
**Error:**Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如StackOverflowError和OOM。一般不编写针对性代码进行处理。
package a01;
public class Test1 {
public static void main(String[] args) {
main(args);//栈溢出:java.lang.StackOverflowError
}
}
Exception:其他因变成错误或者偶然的外在因素导致的一般性问题,可以使用针对性代码进行处理。
- 空指针访问
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
package a01;
public class Test1 {
public static void main(String[] args) {
Integer[] i = new Integer[1024*1024*1024];//堆溢出:java.lang.OutOfMemoryError(因为new这个动作是在堆中进行的)
}
异常体系结构
java.lang.Throwable
- java.lang.Error
- java.lang.Exception
- 编译时异常(受检异常)
- IOException
- ClassNotFoundException
- ……
- 运行时异常(非受检异常)
- NullPointerException
- ArrayIndexOutOfBoundsException
- ……
- 编译时异常(受检异常)
编译时异常:执行javac.exe命令时,可能出现的异常。
运行时异常:执行java.exe命令出现的异常。
Java异常处理的方式
一、异常的处理:抓抛模型
过程一:“抛”
程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对于异常累的对象,并将此对象抛出。
一旦抛出对象以后,其后的代码不再执行。
过程二:“抓”
可以理解为异常的处理方式:①try-catch-finally ②throws
二、try-catch-finally
代码:
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}catch(异常类型3 变量名3){
//处理异常的方式3
}
...
finally{
//一定会执行的代码
}
说明:
1、finally是可选的。
2、使用try将可能出现异常的代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。
3、一旦try中的异常对象匹配到某一个catch时,就会进入catch中进行异常的处理。一旦处理完成,就会跳出当前的try-catch结构(在没有写finally的情况),继续执行其后的代码。
4、catch中的异常类型如果满足父子类关系,就一定要把子类写在父类之上,否则报错
try{
...
}catch(Exception ex){
...
}catch(RuntimeException ex){
...
}
//错误顺序
try{
...
}catch(RuntimeException ex){
...
}catch(Exception ex){
...
}
//正确顺序
5、常用的异常对象处理的方式:①String getMessage() ②printStackTrace()。
6、在try结构中声明的变量,再出了try结构以后,就不能再被调用。
7、try-catch-finally结构可以相互嵌套。
8、e.printStackTrace()可以打印出详细的异常,包括异常的名称,出错的位置,便于程序调试用
e.getMessage() 只会获得具体的异常名称。
finally的使用:
1、finally是可选的。
2、finally中声明的是一定会被执行的代码。即使catch中又出现了异常,try/catch中有return语句等情况。
package a01;
public class Test1 {
public static void main(String[] args) {
int num = method();
System.out.println(num);
}
public static int method() {
try {
int[] arr = new int[10];
System.out.println(arr[10]);
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
return 2;
}finally {
System.out.println("这条语句一定被执行");
// return 3;
//如果此处有return 3,则执行结果为
//这条语句一定被执行
//3;
}
}
}
/*
执行结果为:
java.lang.ArrayIndexOutOfBoundsException: 10
这条语句一定被执行
2
at a01.Test1.method(Test1.java:19)
at a01.Test1.main(Test1.java:13)
*/
3、像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行释放,此时的资源释放就需要声明在finally中。
package a01;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Test2 {
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.print((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();
}
}
}
}
体会:
1、使用try-catch语句处理编译时可能出现的异常,但不代表运行时不会出现。
2、开发时,由于运行时异常比较常见,一般不对运行时异常编写try-catch-finally。针对编译时异常,我们要考虑异常的情况。
三、throws+异常类型
1、"throw+异常类型"写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦方法体执行时,出现异常,就会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出,异常后续的代码就不再执行了。
2、try-catch-finally真正将异常解决掉了。
throws的方式只是将异常抛给了调用者。
3、开发中如何选择使用try-catch-finally还是使用throws?
3.1如果父类中被重写的方法没有throws方法处理异常,则子类出现异常要使用try-catch-finally方法。
3.2执行方法a中,先后调用了另外的几个方法,这几个方法是递进关系。我们建议这几个方法使用throws的方法进行处理,方法a使用try-catch-finally的方式处理。
package a02;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Test1 {
public static void main(String[] args) {
try {
method2();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void method2() throws IOException{//向上抛出异常
method1();
}
public static void method1() throws IOException{//向上抛出异常
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1) {
System.out.print((char)data);
data = fis.read();
}
fis.close();
}
}
方法重写的规则之一:
子类重写的方法抛出的异常不大于父类被重写的方法抛出的异常类型。如果父类没有抛出异常,子类也不能抛出异常。
手动抛出异常对象
关于异常对象的产生:
①系统自动生成的异常对象
②手动的生成一个异常对象,并抛出(throw)
如何自定义异常
1、继承现有的异常结构:Exception、RuntimeException
2、提供全局变量:serialVersionUID
3、提供重载的构造器:一个无参的构造方法和一个带消息参数的构造方法
练习题
//接受命令行两个参数,要求不输入负数,求两个数相除
package a01;
public class EcmDef {
public static int ecm(int a, int b) throws EcDef{
if(a < 0 || b < 0) {
throw new EcDef("输入的数为负数");
}
return a / b;
}
public static void main(String[] args) {
try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
int result = ecm(i, j);
System.out.println(result);
} catch (NumberFormatException e) {
System.out.println("数据类型不一致");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("缺少命令行参数");
} catch (ArithmeticException e) {
System.out.println("除0");
} catch (EcDef e) {
System.out.println(e.getMessage());
}
}
}
package a01;
//输入负数的自定义异常
public class EcDef extends Exception{
static final long serialVersionUID = -338751694229948L;
public EcDef() {}//无参
public EcDef(String msg) {//带消息参数
super(msg);
}
}
【面试题】
1、常见的异常有哪些?举例说明
2、throw 和 throws的区别:throw表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。throws属于异常处理的一种方式,声明在方法的声明处。