Java的异常处理机制
Throwable、 Error、Exception
Java异常结构中定义有Throwable类(顶级父类)
Exception和Error是其派生的两个子类
Exception表示:由于网络故障、文件损坏、设备错误、用户输入非法等情况导致的异常
Error表示:Java运行时环境出现的错误,例如: JVM内存资源耗尽等
异常与错误的区别:
异常可以修复,错误不可以修
异常是程序允许的,错误是程序不允许的
理解为人类的行为就是
错误就是,我死了
异常就是,我病了
一、Java的异常处理机制中的 try-catch
1、语法结构:
try{
程序代码块
}catch(XXXException e){//这里写异常类型和对象名
当try中出现XXXException后的解决代码
}
或
try{
}finally{
}
当JVM执行程序时,发现某个错误,就会实例化对应的异常实例,
并将程序执行过程设置好,然后将该异常抛出
此时若是没有异常处理机制,该异常会被继续抛出到当前方法外(如果是main就抛出到了main方法外)
若最终抛给了虚拟机,则会直接中断
package exception;
public class TryCatchDemo {
public static void main(String[] args) {
System.out.println("程序开始了");
String str = null;
//这句抛出异常java.lang.NullPointerException
System.out.println(str.length());
System.out.println("程序结束了");
}
}
2、使用try-catch,捕获异常
在try语句块中,出错以下代码都不会被执行
package exception;
public class TryCatchDemo {
public static void main(String[] args) {
System.out.println("程序开始了");
try{
String str = null;
System.out.println(str.length());
//注意上面一句抛出异常后,下面代码都不会执行了
System.out.println("!!!!!!!!!");
}catch(NullPointerException e){
System.out.println("空指针异常!");
}
System.out.println("程序结束了");
}
}
3、catch可以写多个,针对try中出现的不同异常,有不同处理方式时,可以分别捕获它们
package exception;
public class TryCatchDemo {
public static void main(String[] args) {
System.out.println("程序开始了");
try{
//String str = null;
String str = "";
System.out.println(str.length()); //空指针异常
//注意上面一句抛出异常后,下面代码都不会执行了
System.out.println("!!!!!!!!!");
System.out.println(str.charAt(0)); //字符串下标越界
}catch(NullPointerException e){
System.out.println("空指针异常!");
}catch(StringIndexOutOfBoundsException e){
System.out.println("字符串下标越界!");
}
System.out.println("程序结束了");
}
}
注意:为了避免存在我们没有发现的异常,我们可以在最后一个catch中写一个大的捕获
catch(Exception e) 但是,这个是用于我们找不出的异常时的处理办法,避免程序终止
package exception;
public class TryCatchDemo {
public static void main(String[] args) {
System.out.println("程序开始了");
try{
//String str = null;
//String str = "";
String str = "a";
System.out.println(str.length()); //空指针异常
//注意上面一句抛出异常后,下面代码都不会执行了
System.out.println("!!!!!!!!!");
System.out.println(str.charAt(0)); //字符串下标越界
System.out.println(Integer.parseInt(str));
}catch(NullPointerException e){
System.out.println("空指针异常!");
}catch(StringIndexOutOfBoundsException e){
System.out.println("字符串下标越界!");
}catch(Exception e){
System.out.println("对不起,我们遇到一个未知错误,请您上传到安全中心!");
}
System.out.println("程序结束了");
}
}
ps.空指针异常与下标越界异常
空指针异常,是操作了一个指向的是空对象的指针报的错误
比如:当一个对象不存在时,又调用其方法;或者访问或者修改一个对象不存在的字段时
空指针异常NullPointerException
下标越界ArrayIndexOutOfBoundsException
他们两不是同一异常
NullPointerException 继承 RuntimeException
ArrayIndexOutOfBoundsException 继承 IndexOutOfBoundsException继承RuntimeException
空指针异常通常发生在对象操作时,有指针时,才会有空指针异常,把指针当做一个箭头就行了
下标越界是发生在数据存储时,访问存储的数据
4、finally块
语法位置:
finally块是异常处理机制中的最后一部分(一条处理机制中finally下面不能再写catch)
它可以直接跟在try语句块之后或者最后一个catch块之后
作用:
finally可以保证只要程序执行到try当中,无论是否出现异常,finally中的代码都必须执行
finally可以保证只要程序执行到try当中,无论是否出现异常,finally中的代码都必须执行
通常我们可以将释放资源(文件或流的关闭)这样的必须操作的代码放在这里
**注意:**如果try里面写了return的话,try-catch机制外的代码是不会运行的,
所以只有把一些必要操作放在finally中才一定会执行
package exception;
public class FinallyDemo {
public static void main(String[] args) {
System.out.println("程序开始了----------");
try{
//String str = null;
String str = "";
System.out.println(str.length());
return;
}catch(Exception e){
System.out.println("出错了");
}finally{
System.out.println("finally块执行");
}
System.out.println("程序结束了----------");
}
}
**finally的实际应用:**在IO中使用异常处理机制
package exception;
import java.io.FileOutputStream;
import java.io.IOException;
public class FinallyDemo2 {
public static void main(String[] args) {
FileOutputStream fos = null;
try{
fos = new FileOutputStream("fos.dat");
fos.write(1);
}catch(Exception e){
System.out.println("出错了!");
}finally{
try {
if(fos!=null){
fos.close();
}
} catch (IOException e) {
}
}
}
}
知识点:(面试中finally常见问题)
第一题:
package exception;
public class FinallyDemo3 {
public static void main(String[] args) {
System.out.println(test("0")+","+test(null)+","+test(""));
}
public static int test(String str){
try{
return str.charAt(0)-'0';
}catch(NullPointerException e){
return 1;
}catch(Exception e){
return 2;
}finally{
return 3;
}
}
}
问输出结果:正确为3,3,3 如果注释掉finally中的return,结果为0,1,2
因为finally是最后一定要运行的,所以finally中写return方法会覆盖掉try-catch语句中的return方法
第二题:问请分别说明final、finally、finalize
final:关键字,可修饰类、方法、属性
finally:try-catch异常处理机制的最后一块,是一定最后会运行的一部分
finalize:是Object类定义的一个方法,该方法是当GC释放该对象资源时,调用此方法,调用后该对象即被释放(一个对象的临终遗言,每个对象都会继承的一个方法)
注意:此方法若重写,里面不应当有耗时的操作(Object文档规定的)(比如死循环这样的操作)
5、AutoCloseable接口特性(为我们关闭流JDK7之后)
JDK7之后推出了一个特性:AutoCloseable
该特性旨在让我们在源代码中可以以更简化的代码完成在finally中关闭流
try语句后面可以追加小括号,我们可以把流的定义放在小括号里
在这里定义的流最终会被编译器改为在finally中关闭
注意:实现了AutoClosable的接口的才可以在这里定义
所有流和RandomAccessFile都实现了这个接口
这个特性为编译器支持,JVM并不认可,只是编译器为我们完成了我们没完成的功能
package exception;
import java.io.FileOutputStream;
public class AutoClosableDemo {
public static void main(String[] args) {
try(
//实现了AutoClosable的接口的才可以在这里定义
//所有流都实现了这个接口
//这个特性为编译器支持,JVM并不认可,只是编译器为我们完成了我们没完成的功能
//在这里定义的流最终会被编译器改为在finally中关闭
FileOutputStream fos = new FileOutputStream("fos.dat");
){
fos.write(1);
}catch(Exception e){
System.out.println("出错了");
}
}
}
二、异常的抛出(throw关键字)
异常的抛出
**throw关键字:**用于主动抛出一个异常
通常以下情况我们会主动抛出异常:
- 程序遇到一个满足语法要求,但是不满足业务逻辑要求时,
我们可以主动抛出异常,告知调用方不应当这样做 - 程序确实出现了异常,但是该异常不应当在当前代码片段被解决时,
可以对外抛出给调用方解决
throws关键字: 当一个方法中使用throw抛出异常时,就要在当前方法上使用throws声明该异常的抛出,告知调用者去解决该异常
注:只有抛出RuntimeException及其子类型异常时可以不这样做
当调用一个含有throws声明异常抛出的方法时
编译器要求必须处理该异常,否则编译不通过
处理异常的方式有两种:
- 使用try-catch捕获并处理该异常
- 在当前方法上继续使用throws声明该异常抛出
使用哪种处理方式,根据具体责任来看,该归哪个方法管就抛出到哪个方法,让他具体处理
但记住,不要在main方法上面抛出throws,这是极不负责的表现
package exception;
/**
* 使用当前类测试异常的抛出(throw)
* @author Tian
*
*/
public class Person {
private int age;
public int getAge() {
return age;
}
// public void setAge(int age) {
// this.age = age;
// }
public void setAge(int age) throws Exception{
if(age<0||age>120){
//RuntimeException的异常可以不用throws抛出异常声明
//throw new RuntimeException("年龄不合法");
throw new Exception("年龄不合法");
}
this.age = age;
}
}
package exception;
public class ThrowDemo {
public static void main(String[] args) {
Person p = new Person();
try {
p.setAge(10);
} catch (Exception e) {
System.out.println("瞎输什么,滚!");
}
System.out.println(p.getAge());
}
}
**注意:**由于throws是写在方法中的,所以要注意
子类重写父类含有throws声明异常抛出的方法时对throws的重写规则
- 允许抛出一模一样的异常
- 允许不再抛出任何异常
- 允许抛出部分异常
- 允许抛出父类抛出异常的子类型异常
不允许的情况:
- 不能抛出额外异常(超类中没有的,而且没有继承关系的)
- 不允许抛出比父类方法抛出异常的父类型异常(子类抛出的异常不能比父类的大)
package exception;
import java.awt.AWTException;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* 子类重写父类含有throws声明异常抛出的方法时
* 对throws的重写规则
* @author Tian
*
*/
public class ThrowsDemo {
public void dosome() throws IOException,AWTException{}
}
class Sub extends ThrowsDemo{
//允许的情况
//一模一样的允许
//public void dosome() throws IOException,AWTException{}
//允许不再抛出任何异常
//public void dosome(){}
//允许抛出部分异常
//public void dosome() throws AWTException{}
//允许抛出父类抛出异常的子类型异常
public void dosome() throws FileNotFoundException{}
//不允许的情况
//不允许抛出额外异常,即:超类方法没有的异常,也不存在继承关系的异常
//public void dosome() throws SQLException{}
//不允许抛出比父类方法抛出异常的父类型异常(子类抛出的异常不能比父类的大)
//public void dosome() throws Exception{}
}
三、异常API(异常中的常见方法)
Java异常可以分为可检测异常、非检测异常:
可检测异常:
可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制执行处理或声明规则,
不捕捉这个异常,编译器就通不过,不允许编译
非检测异常:
非检测异常不遵循处理或者声明规则。
在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这样一个异常
RuntimeException类属于非检测异常
因为普通JVM操作引起的运行时异常随时可能发生, 此类异常一般是由特定操作弓|发。
但这些操作在java应用程序中会频繁出现。因此它们不受编译器检查与处理或声明规则的限制。
常见RuntimeException(非检测异常/运行时异常)
- IllegalArgumentException
抛出的异常表明向方法传递了一个不合法或不正确的参数 - NullPointerException
当应用程序试图在需要对象的地方使用null时,抛出该异常 - ArrayIndexOutOfBoundsException
当使用的数组下标超出数组允许范围时,抛出该异常 - ClassCastException
当试图将对象强制转换为不是实例的子类时,抛出该异常 - NumberFormatException
当应用程序试图将字符串转换成种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
Exception常用API
printStackTrace();
该方法用于输出异常信息,将当前错误信息输出到控制台上
String getMessage();
获取错误消息(不是所有异常都有错误消息,但是大部分有)
package exception;
public class ExceptionApiDemo {
public static void main(String[] args) {
System.out.println("程序开始");
try{
String str = "a";
System.out.println(Integer.parseInt(str));
}catch(Exception e){
//该方法用于输出异常信息,将当前错误信息输出到控制台上
e.printStackTrace();
//获取错误消息(不是所有异常都有错误消息,但是大部分有)
String message = e.getMessage();
System.out.println(message);
}
System.out.println("程序结束");
}
}
四、自定义异常
自定义异常通常用来定义业务逻辑问题,这种异常java是没有提供的
自定义异常几步:
- 定义类,类名要见名知意(看见名字知道是什么错误)
- 需要继承自Exception,可以间接继承Exception
- 提供所有的构造方法
注意:Exception的父类实现了序列化接口的,定义其子类时最好手动添加序列化ID
package exception;
//需要继承自Exception,可以间接继承Exception
public class IllegalAgeException extends Exception{
/**
* 定义其子类时最好手动添加序列化ID
*/
private static final long serialVersionUID = 1L;
/*
* 下面构造器自动生成,Source中选择Constructors from Superclass的构造方法
*/
public IllegalAgeException() {
super();
// TODO Auto-generated constructor stub
}
public IllegalAgeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
}
public IllegalAgeException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public IllegalAgeException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public IllegalAgeException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
}
异常常考面试题
1、简述java中的异常体系
异常体系:
- Thorwable类(表示可抛出)是所有异常和错误的超类
- 两个直接子类为Error和Exception,分别表示错误和异常
- 异常类Exception又分为非检查型异常(Unchecked Exception)和检查异常(CheckedException),
这两种异常有很大的区别,也称之为 运行时异常(RuntimeException)和非运行时异常
这些异常之间的区别与联系:
-
Error与Exception
Error是程序无法处理的错误,它是由JVM产生和抛出的,比如OutOfMemoryError、ThreadDeath等。
这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。
程序中应当尽可能去处理这些异常。 -
非检查型异常(运行时异常):
在Java中所有RuntimeException的派生类都是非检查型异常,
与检查型异常对比,非检查型异常可以不在函数声明中添加throws语句,调用函数上也不需要强制处理。
常见的NullPointException、ClassCastException、IndexOutOfBoundsException是常见的非检查型异常。
非检查型异常可以不使用try…catch进行处理,但是如果有异常产生,则异常将由JVM进行处理。检查型异常(非运行时异常):
在Java中所有不是RuntimeException派生的Exception都是检查型异常。
当函数中存在抛出检查型异常的操作时该函数的函数声明中必须包含throws语句。
调用改函数的函数也必须对该异常进行处理,如不进行处理则必须在调用函数上声明throws语句。检查型异常是JAVA首创的,在编译期对异常的处理有强制性的要求。在JDK代码中大量的异常属于检查型异常,包括IOException,SQLException等等。
2、简述java中的异常处理机制
try 可能出异常的代码块 catch(异常类型 异常) 异常的处理 finally 必定运行的代码块
补充 try、catch、finally三个语句块应注意的问题:
- try、catch、finally三个语句块均不能单独使用,三者可以组成 try…catch…finally、try…catch、try…finally三种结构,catch语句可以有一个或多个,finally语句最多一个。
- try、catch、finally三个代码块中变量的作用域为代码块内部,分别独立而不能相互访问。
如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。 - 多个catch块时候,最多只会匹配其中一个异常类且只会执行该catch块代码,而不会再执行其它的catch块,且匹配catch语句的顺序为从上到下,也可能所有的catch都没执行。
- 先Catch子类异常再Catch父类异常。
- JDK7之后推出了一个特性:AutoCloseable,简化的代码完成在finally中关闭流
try语句后面可以追加小括号,我们可以把流的定义放在小括号里,在这里定义的流最终会被编译器改为在finally中关闭
3、throw和throws的区别
- throw:
throw关键字是用于方法体内部,用来抛出一个Throwable类型的异常。
如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。
该方法的调用者也必须检查处理抛出的异常。
如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。 - throws:
throws关键字用于方法体外部的方法声明部分,用来声明方法可能会抛出某些异常。
仅当抛出了检查异常,该方法的调用者才必须处理或者重新抛出该异常。
当方法的调用者无力处理该异常的时候,应该继续抛出.
4、final、finally、finalize的区别
**final:**关键字,可修饰类、方法、属性
**finally:**try-catch异常处理机制的最后一块,是一定最后会运行的一部分
**finalize:**是Object类定义的一个方法,该方法是当GC释放该对象资源时,调用此方法,调用后该对象即被释放(一个对象的临终遗言,每个对象都会继承的一个方法)
5、列举几个常见的检查型异常和非检查型异常
检查型异常:
- IOException IO流异常
- SQLException
非检查型异常:
- NullPointerException 空指针异常
- ArrayIndexOutOfBoundsException 数组下标越界异常
- ClassCastException 强制类型转换异常
6、如何自定义一个异常
- 定义类,类名要见名知意(看见名字知道是什么错误)
- 需要继承自Exception,可以间接继承Exception
- 提供所有的构造方法